@hearsay-tools/squawk 0.0.0-stage → 0.17.0-dev.5685
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +1846 -2
- package/dist/activity/demand.d.ts +21 -0
- package/dist/activity/demand.js +49 -0
- package/dist/activity/demand.js.map +1 -0
- package/dist/activity/publisher.d.ts +48 -0
- package/dist/activity/publisher.js +175 -0
- package/dist/activity/publisher.js.map +1 -0
- package/dist/activity/read-model.d.ts +50 -0
- package/dist/activity/read-model.js +143 -0
- package/dist/activity/read-model.js.map +1 -0
- package/dist/activity/requests.d.ts +27 -0
- package/dist/activity/requests.js +81 -0
- package/dist/activity/requests.js.map +1 -0
- package/dist/activity/text.d.ts +4 -0
- package/dist/activity/text.js +13 -0
- package/dist/activity/text.js.map +1 -0
- package/dist/activity/tool-activity.d.ts +87 -0
- package/dist/activity/tool-activity.js +250 -0
- package/dist/activity/tool-activity.js.map +1 -0
- package/dist/activity/wait.d.ts +19 -0
- package/dist/activity/wait.js +91 -0
- package/dist/activity/wait.js.map +1 -0
- package/dist/agent-config/account-identity.d.ts +28 -0
- package/dist/agent-config/account-identity.js +132 -0
- package/dist/agent-config/account-identity.js.map +1 -0
- package/dist/agent-config/catalog.d.ts +70 -0
- package/dist/agent-config/catalog.js +384 -0
- package/dist/agent-config/catalog.js.map +1 -0
- package/dist/agent-config/files.d.ts +42 -0
- package/dist/agent-config/files.js +115 -0
- package/dist/agent-config/files.js.map +1 -0
- package/dist/agent-config/model-settings/claude.d.ts +2 -0
- package/dist/agent-config/model-settings/claude.js +11 -0
- package/dist/agent-config/model-settings/claude.js.map +1 -0
- package/dist/agent-config/model-settings/codex.d.ts +2 -0
- package/dist/agent-config/model-settings/codex.js +16 -0
- package/dist/agent-config/model-settings/codex.js.map +1 -0
- package/dist/agent-config/model-settings/opencode.d.ts +2 -0
- package/dist/agent-config/model-settings/opencode.js +8 -0
- package/dist/agent-config/model-settings/opencode.js.map +1 -0
- package/dist/agent-config/model-settings/pi.d.ts +2 -0
- package/dist/agent-config/model-settings/pi.js +38 -0
- package/dist/agent-config/model-settings/pi.js.map +1 -0
- package/dist/agent-config/model-settings/shared.d.ts +12 -0
- package/dist/agent-config/model-settings/shared.js +100 -0
- package/dist/agent-config/model-settings/shared.js.map +1 -0
- package/dist/agent-config/model-settings/types.d.ts +9 -0
- package/dist/agent-config/model-settings/types.js +2 -0
- package/dist/agent-config/model-settings/types.js.map +1 -0
- package/dist/agent-config/models.d.ts +9 -0
- package/dist/agent-config/models.js +40 -0
- package/dist/agent-config/models.js.map +1 -0
- package/dist/agent-config/seed.d.ts +15 -0
- package/dist/agent-config/seed.js +94 -0
- package/dist/agent-config/seed.js.map +1 -0
- package/dist/agent-config/service.d.ts +44 -0
- package/dist/agent-config/service.js +73 -0
- package/dist/agent-config/service.js.map +1 -0
- package/dist/agent-config/validate.d.ts +18 -0
- package/dist/agent-config/validate.js +82 -0
- package/dist/agent-config/validate.js.map +1 -0
- package/dist/application-update/bin-recovery.d.ts +5 -0
- package/dist/application-update/bin-recovery.js +96 -0
- package/dist/application-update/bin-recovery.js.map +1 -0
- package/dist/application-update/discovery.d.ts +23 -0
- package/dist/application-update/discovery.js +131 -0
- package/dist/application-update/discovery.js.map +1 -0
- package/dist/application-update/helper.d.ts +58 -0
- package/dist/application-update/helper.js +363 -0
- package/dist/application-update/helper.js.map +1 -0
- package/dist/application-update/launcher.d.ts +12 -0
- package/dist/application-update/launcher.js +61 -0
- package/dist/application-update/launcher.js.map +1 -0
- package/dist/application-update/lock.d.ts +4 -0
- package/dist/application-update/lock.js +70 -0
- package/dist/application-update/lock.js.map +1 -0
- package/dist/application-update/npm-process.d.ts +17 -0
- package/dist/application-update/npm-process.js +156 -0
- package/dist/application-update/npm-process.js.map +1 -0
- package/dist/application-update/runtime-layout.d.ts +20 -0
- package/dist/application-update/runtime-layout.js +91 -0
- package/dist/application-update/runtime-layout.js.map +1 -0
- package/dist/application-update/scoped-recovery.d.ts +32 -0
- package/dist/application-update/scoped-recovery.js +210 -0
- package/dist/application-update/scoped-recovery.js.map +1 -0
- package/dist/application-update/service.d.ts +86 -0
- package/dist/application-update/service.js +523 -0
- package/dist/application-update/service.js.map +1 -0
- package/dist/archive-on-merge-setting.d.ts +15 -0
- package/dist/archive-on-merge-setting.js +15 -0
- package/dist/archive-on-merge-setting.js.map +1 -0
- package/dist/artifacts/cli.d.ts +1 -0
- package/dist/artifacts/cli.js +54 -0
- package/dist/artifacts/cli.js.map +1 -0
- package/dist/artifacts/lifecycle.d.ts +4 -0
- package/dist/artifacts/lifecycle.js +42 -0
- package/dist/artifacts/lifecycle.js.map +1 -0
- package/dist/artifacts/reports-index.d.ts +46 -0
- package/dist/artifacts/reports-index.js +279 -0
- package/dist/artifacts/reports-index.js.map +1 -0
- package/dist/artifacts/reports-watch.d.ts +43 -0
- package/dist/artifacts/reports-watch.js +129 -0
- package/dist/artifacts/reports-watch.js.map +1 -0
- package/dist/artifacts/resolve.d.ts +18 -0
- package/dist/artifacts/resolve.js +129 -0
- package/dist/artifacts/resolve.js.map +1 -0
- package/dist/artifacts/store.d.ts +40 -0
- package/dist/artifacts/store.js +513 -0
- package/dist/artifacts/store.js.map +1 -0
- package/dist/automations/automation-cli.d.ts +61 -0
- package/dist/automations/automation-cli.js +519 -0
- package/dist/automations/automation-cli.js.map +1 -0
- package/dist/automations/builtin-skill.d.ts +7 -0
- package/dist/automations/builtin-skill.js +14 -0
- package/dist/automations/builtin-skill.js.map +1 -0
- package/dist/automations/coordinator.d.ts +53 -0
- package/dist/automations/coordinator.js +136 -0
- package/dist/automations/coordinator.js.map +1 -0
- package/dist/automations/github-baseline.d.ts +3 -0
- package/dist/automations/github-baseline.js +17 -0
- package/dist/automations/github-baseline.js.map +1 -0
- package/dist/automations/github-poller.d.ts +93 -0
- package/dist/automations/github-poller.js +303 -0
- package/dist/automations/github-poller.js.map +1 -0
- package/dist/automations/project-files.d.ts +6 -0
- package/dist/automations/project-files.js +43 -0
- package/dist/automations/project-files.js.map +1 -0
- package/dist/automations/prompts.d.ts +12 -0
- package/dist/automations/prompts.js +162 -0
- package/dist/automations/prompts.js.map +1 -0
- package/dist/automations/schedule-runner.d.ts +153 -0
- package/dist/automations/schedule-runner.js +529 -0
- package/dist/automations/schedule-runner.js.map +1 -0
- package/dist/automations/scheduler.d.ts +95 -0
- package/dist/automations/scheduler.js +462 -0
- package/dist/automations/scheduler.js.map +1 -0
- package/dist/automations/service.d.ts +793 -0
- package/dist/automations/service.js +444 -0
- package/dist/automations/service.js.map +1 -0
- package/dist/automations/store.d.ts +168 -0
- package/dist/automations/store.js +805 -0
- package/dist/automations/store.js.map +1 -0
- package/dist/automations/task-template.d.ts +59 -0
- package/dist/automations/task-template.js +285 -0
- package/dist/automations/task-template.js.map +1 -0
- package/dist/automations/tool-adapter.d.ts +6 -0
- package/dist/automations/tool-adapter.js +115 -0
- package/dist/automations/tool-adapter.js.map +1 -0
- package/dist/automations/types.d.ts +1 -0
- package/dist/automations/types.js +2 -0
- package/dist/automations/types.js.map +1 -0
- package/dist/autosave-cleanup.d.ts +26 -0
- package/dist/autosave-cleanup.js +87 -0
- package/dist/autosave-cleanup.js.map +1 -0
- package/dist/autosave-git.d.ts +23 -0
- package/dist/autosave-git.js +211 -0
- package/dist/autosave-git.js.map +1 -0
- package/dist/autosave-group.d.ts +8 -0
- package/dist/autosave-group.js +61 -0
- package/dist/autosave-group.js.map +1 -0
- package/dist/autosave-holders.d.ts +34 -0
- package/dist/autosave-holders.js +228 -0
- package/dist/autosave-holders.js.map +1 -0
- package/dist/autosave-windows.d.ts +25 -0
- package/dist/autosave-windows.js +170 -0
- package/dist/autosave-windows.js.map +1 -0
- package/dist/cez.d.ts +2 -0
- package/dist/cez.js +3 -0
- package/dist/cez.js.map +1 -0
- package/dist/cezarion.d.ts +2 -0
- package/dist/cezarion.js +3 -0
- package/dist/cezarion.js.map +1 -0
- package/dist/ci-wait/client.d.ts +28 -0
- package/dist/ci-wait/client.js +123 -0
- package/dist/ci-wait/client.js.map +1 -0
- package/dist/ci-wait/controller.d.ts +824 -0
- package/dist/ci-wait/controller.js +305 -0
- package/dist/ci-wait/controller.js.map +1 -0
- package/dist/ci-wait/errors.d.ts +35 -0
- package/dist/ci-wait/errors.js +273 -0
- package/dist/ci-wait/errors.js.map +1 -0
- package/dist/ci-wait/github.d.ts +105 -0
- package/dist/ci-wait/github.js +324 -0
- package/dist/ci-wait/github.js.map +1 -0
- package/dist/ci-wait/injection.d.ts +3 -0
- package/dist/ci-wait/injection.js +13 -0
- package/dist/ci-wait/injection.js.map +1 -0
- package/dist/ci-wait/mcp.d.ts +3 -0
- package/dist/ci-wait/mcp.js +30 -0
- package/dist/ci-wait/mcp.js.map +1 -0
- package/dist/ci-wait/merge-progress.d.ts +84 -0
- package/dist/ci-wait/merge-progress.js +50 -0
- package/dist/ci-wait/merge-progress.js.map +1 -0
- package/dist/ci-wait/progress.d.ts +56 -0
- package/dist/ci-wait/progress.js +100 -0
- package/dist/ci-wait/progress.js.map +1 -0
- package/dist/ci-wait/resources.d.ts +9 -0
- package/dist/ci-wait/resources.js +46 -0
- package/dist/ci-wait/resources.js.map +1 -0
- package/dist/ci-wait/supervisor.d.ts +47 -0
- package/dist/ci-wait/supervisor.js +334 -0
- package/dist/ci-wait/supervisor.js.map +1 -0
- package/dist/ci-wait/text.d.ts +2 -0
- package/dist/ci-wait/text.js +5 -0
- package/dist/ci-wait/text.js.map +1 -0
- package/dist/ci-wait/tools.d.ts +845 -0
- package/dist/ci-wait/tools.js +232 -0
- package/dist/ci-wait/tools.js.map +1 -0
- package/dist/cli-dispatch.d.ts +33 -0
- package/dist/cli-dispatch.js +239 -0
- package/dist/cli-dispatch.js.map +1 -0
- package/dist/config.d.ts +117 -0
- package/dist/config.js +343 -0
- package/dist/config.js.map +1 -0
- package/dist/contract/active-provider.d.ts +12 -0
- package/dist/contract/activity.d.ts +1478 -0
- package/dist/contract/agent-config.d.ts +160 -0
- package/dist/contract/agent-profiles.d.ts +369 -0
- package/dist/contract/application-update.d.ts +32 -0
- package/dist/contract/artifacts.d.ts +100 -0
- package/dist/contract/ask-schema.d.ts +50 -0
- package/dist/contract/ask.d.ts +11 -0
- package/dist/contract/attachments.d.ts +35 -0
- package/dist/contract/attention.d.ts +99 -0
- package/dist/contract/automation-requests.d.ts +314 -0
- package/dist/contract/automation-schedule.d.ts +89 -0
- package/dist/contract/automation-storage.d.ts +518 -0
- package/dist/contract/automations.d.ts +1560 -0
- package/dist/contract/ci-wait.d.ts +281 -0
- package/dist/contract/config-requests.d.ts +36 -0
- package/dist/contract/control-locations.d.ts +10 -0
- package/dist/contract/conversations.d.ts +415 -0
- package/dist/contract/defined.d.ts +8 -0
- package/dist/contract/delegation.d.ts +2168 -0
- package/dist/contract/discovery.d.ts +228 -0
- package/dist/contract/effort.d.ts +18 -0
- package/dist/contract/events.d.ts +324 -0
- package/dist/contract/github-requests.d.ts +26 -0
- package/dist/contract/github.d.ts +732 -0
- package/dist/contract/handoff.d.ts +43 -0
- package/dist/contract/health.d.ts +128 -0
- package/dist/contract/home-recovery.d.ts +71 -0
- package/dist/contract/image-path.d.ts +6 -0
- package/dist/contract/index.d.ts +68 -0
- package/dist/contract/index.js +6709 -0
- package/dist/contract/live-coordination.d.ts +164 -0
- package/dist/contract/live-limits.d.ts +8 -0
- package/dist/contract/live.d.ts +123 -0
- package/dist/contract/pending-ask.d.ts +58 -0
- package/dist/contract/per-runner-optional-strings.d.ts +10 -0
- package/dist/contract/permissions.d.ts +54 -0
- package/dist/contract/preview.d.ts +568 -0
- package/dist/contract/project-limits.d.ts +35 -0
- package/dist/contract/projects.d.ts +716 -0
- package/dist/contract/provider-requests.d.ts +77 -0
- package/dist/contract/query-requests.d.ts +52 -0
- package/dist/contract/repo-requests.d.ts +12 -0
- package/dist/contract/repo.d.ts +515 -0
- package/dist/contract/report-title.d.ts +3 -0
- package/dist/contract/reports.d.ts +101 -0
- package/dist/contract/run-labels.d.ts +8 -0
- package/dist/contract/run-limits.d.ts +8 -0
- package/dist/contract/run-predicates.d.ts +87 -0
- package/dist/contract/run-requests.d.ts +33 -0
- package/dist/contract/run-search.d.ts +193 -0
- package/dist/contract/runs.d.ts +7108 -0
- package/dist/contract/scheduled-tasks.d.ts +188 -0
- package/dist/contract/self-update.d.ts +255 -0
- package/dist/contract/skill-sources.d.ts +125 -0
- package/dist/contract/skills.d.ts +56 -0
- package/dist/contract/task-options.d.ts +18 -0
- package/dist/contract/team-skill-ref.d.ts +10 -0
- package/dist/contract/tool-display.d.ts +25 -0
- package/dist/contract/ui-state-requests.d.ts +60 -0
- package/dist/contract/version.d.ts +3 -0
- package/dist/contract/voice.d.ts +213 -0
- package/dist/contract/waits.d.ts +1996 -0
- package/dist/contract/workflow-file.d.ts +37 -0
- package/dist/contract/workflow-requests.d.ts +7 -0
- package/dist/contract/workflows.d.ts +250 -0
- package/dist/contract/workspace-events.d.ts +410 -0
- package/dist/contract/workspace.d.ts +631 -0
- package/dist/contract/zoned-time.d.ts +26 -0
- package/dist/core/agent-env.d.ts +34 -0
- package/dist/core/agent-env.js +411 -0
- package/dist/core/agent-env.js.map +1 -0
- package/dist/core/agent-model-policy.d.ts +11 -0
- package/dist/core/agent-model-policy.js +38 -0
- package/dist/core/agent-model-policy.js.map +1 -0
- package/dist/core/agent-profiles.d.ts +50 -0
- package/dist/core/agent-profiles.js +105 -0
- package/dist/core/agent-profiles.js.map +1 -0
- package/dist/core/agent-runner.d.ts +425 -0
- package/dist/core/agent-runner.js +98 -0
- package/dist/core/agent-runner.js.map +1 -0
- package/dist/core/ask.d.ts +26 -0
- package/dist/core/ask.js +35 -0
- package/dist/core/ask.js.map +1 -0
- package/dist/core/backend-detect.d.ts +18 -0
- package/dist/core/backend-detect.js +216 -0
- package/dist/core/backend-detect.js.map +1 -0
- package/dist/core/claude-cli-runner.d.ts +85 -0
- package/dist/core/claude-cli-runner.js +899 -0
- package/dist/core/claude-cli-runner.js.map +1 -0
- package/dist/core/claude-executable.d.ts +2 -0
- package/dist/core/claude-executable.js +15 -0
- package/dist/core/claude-executable.js.map +1 -0
- package/dist/core/claude-model-catalog.d.ts +16 -0
- package/dist/core/claude-model-catalog.js +198 -0
- package/dist/core/claude-model-catalog.js.map +1 -0
- package/dist/core/claude-stream.d.ts +38 -0
- package/dist/core/claude-stream.js +77 -0
- package/dist/core/claude-stream.js.map +1 -0
- package/dist/core/claude-ui-mapper.d.ts +88 -0
- package/dist/core/claude-ui-mapper.js +851 -0
- package/dist/core/claude-ui-mapper.js.map +1 -0
- package/dist/core/codex-app-server-runner.d.ts +76 -0
- package/dist/core/codex-app-server-runner.js +1271 -0
- package/dist/core/codex-app-server-runner.js.map +1 -0
- package/dist/core/codex-app-server-transport.d.ts +52 -0
- package/dist/core/codex-app-server-transport.js +200 -0
- package/dist/core/codex-app-server-transport.js.map +1 -0
- package/dist/core/codex-approvals.d.ts +12 -0
- package/dist/core/codex-approvals.js +180 -0
- package/dist/core/codex-approvals.js.map +1 -0
- package/dist/core/codex-hook-mapper.d.ts +15 -0
- package/dist/core/codex-hook-mapper.js +73 -0
- package/dist/core/codex-hook-mapper.js.map +1 -0
- package/dist/core/codex-model-catalog.d.ts +10 -0
- package/dist/core/codex-model-catalog.js +110 -0
- package/dist/core/codex-model-catalog.js.map +1 -0
- package/dist/core/codex-native-frames.d.ts +35 -0
- package/dist/core/codex-native-frames.js +80 -0
- package/dist/core/codex-native-frames.js.map +1 -0
- package/dist/core/codex-permissions.d.ts +6 -0
- package/dist/core/codex-permissions.js +27 -0
- package/dist/core/codex-permissions.js.map +1 -0
- package/dist/core/codex-stream-error.d.ts +2 -0
- package/dist/core/codex-stream-error.js +12 -0
- package/dist/core/codex-stream-error.js.map +1 -0
- package/dist/core/codex-turn-outcome.d.ts +10 -0
- package/dist/core/codex-turn-outcome.js +35 -0
- package/dist/core/codex-turn-outcome.js.map +1 -0
- package/dist/core/codex-ui-mapper-state.d.ts +84 -0
- package/dist/core/codex-ui-mapper-state.js +8 -0
- package/dist/core/codex-ui-mapper-state.js.map +1 -0
- package/dist/core/codex-ui-mapper.d.ts +44 -0
- package/dist/core/codex-ui-mapper.js +799 -0
- package/dist/core/codex-ui-mapper.js.map +1 -0
- package/dist/core/cursor-cli-flags.d.ts +19 -0
- package/dist/core/cursor-cli-flags.js +47 -0
- package/dist/core/cursor-cli-flags.js.map +1 -0
- package/dist/core/cursor-config-options.d.ts +18 -0
- package/dist/core/cursor-config-options.js +31 -0
- package/dist/core/cursor-config-options.js.map +1 -0
- package/dist/core/cursor-model-catalog.d.ts +50 -0
- package/dist/core/cursor-model-catalog.js +306 -0
- package/dist/core/cursor-model-catalog.js.map +1 -0
- package/dist/core/cursor-print-capabilities.d.ts +14 -0
- package/dist/core/cursor-print-capabilities.js +169 -0
- package/dist/core/cursor-print-capabilities.js.map +1 -0
- package/dist/core/cursor-print-process.d.ts +31 -0
- package/dist/core/cursor-print-process.js +117 -0
- package/dist/core/cursor-print-process.js.map +1 -0
- package/dist/core/cursor-print-runner.d.ts +43 -0
- package/dist/core/cursor-print-runner.js +574 -0
- package/dist/core/cursor-print-runner.js.map +1 -0
- package/dist/core/cursor-print-tool-catalog.d.ts +3 -0
- package/dist/core/cursor-print-tool-catalog.js +13 -0
- package/dist/core/cursor-print-tool-catalog.js.map +1 -0
- package/dist/core/cursor-print-ui-mapper.d.ts +47 -0
- package/dist/core/cursor-print-ui-mapper.js +574 -0
- package/dist/core/cursor-print-ui-mapper.js.map +1 -0
- package/dist/core/cursor-provider-error.d.ts +9 -0
- package/dist/core/cursor-provider-error.js +15 -0
- package/dist/core/cursor-provider-error.js.map +1 -0
- package/dist/core/cursor-runner.d.ts +46 -0
- package/dist/core/cursor-runner.js +144 -0
- package/dist/core/cursor-runner.js.map +1 -0
- package/dist/core/defined.d.ts +1 -0
- package/dist/core/defined.js +2 -0
- package/dist/core/defined.js.map +1 -0
- package/dist/core/disclaim-spawn.d.ts +11 -0
- package/dist/core/disclaim-spawn.js +60 -0
- package/dist/core/disclaim-spawn.js.map +1 -0
- package/dist/core/edit-write-key.d.ts +2 -0
- package/dist/core/edit-write-key.js +6 -0
- package/dist/core/edit-write-key.js.map +1 -0
- package/dist/core/hook-items.d.ts +14 -0
- package/dist/core/hook-items.js +13 -0
- package/dist/core/hook-items.js.map +1 -0
- package/dist/core/host-model-catalog.d.ts +7 -0
- package/dist/core/host-model-catalog.js +21 -0
- package/dist/core/host-model-catalog.js.map +1 -0
- package/dist/core/input-submissions.d.ts +14 -0
- package/dist/core/input-submissions.js +26 -0
- package/dist/core/input-submissions.js.map +1 -0
- package/dist/core/managed-session.d.ts +6 -0
- package/dist/core/managed-session.js +116 -0
- package/dist/core/managed-session.js.map +1 -0
- package/dist/core/model-discovery-options.d.ts +8 -0
- package/dist/core/model-discovery-options.js +2 -0
- package/dist/core/model-discovery-options.js.map +1 -0
- package/dist/core/model-identity.d.ts +108 -0
- package/dist/core/model-identity.js +137 -0
- package/dist/core/model-identity.js.map +1 -0
- package/dist/core/model-presets.d.ts +10 -0
- package/dist/core/model-presets.js +77 -0
- package/dist/core/model-presets.js.map +1 -0
- package/dist/core/model-probe-teardown.d.ts +4 -0
- package/dist/core/model-probe-teardown.js +20 -0
- package/dist/core/model-probe-teardown.js.map +1 -0
- package/dist/core/ndjson.d.ts +6 -0
- package/dist/core/ndjson.js +23 -0
- package/dist/core/ndjson.js.map +1 -0
- package/dist/core/omp-model-catalog.d.ts +39 -0
- package/dist/core/omp-model-catalog.js +183 -0
- package/dist/core/omp-model-catalog.js.map +1 -0
- package/dist/core/omp-runner.d.ts +86 -0
- package/dist/core/omp-runner.js +818 -0
- package/dist/core/omp-runner.js.map +1 -0
- package/dist/core/omp-subagent-rows.d.ts +21 -0
- package/dist/core/omp-subagent-rows.js +99 -0
- package/dist/core/omp-subagent-rows.js.map +1 -0
- package/dist/core/omp-ui-mapper.d.ts +112 -0
- package/dist/core/omp-ui-mapper.js +807 -0
- package/dist/core/omp-ui-mapper.js.map +1 -0
- package/dist/core/opencode-model-catalog.d.ts +27 -0
- package/dist/core/opencode-model-catalog.js +189 -0
- package/dist/core/opencode-model-catalog.js.map +1 -0
- package/dist/core/opencode-permissions.d.ts +52 -0
- package/dist/core/opencode-permissions.js +101 -0
- package/dist/core/opencode-permissions.js.map +1 -0
- package/dist/core/opencode-runtime-config.d.ts +9 -0
- package/dist/core/opencode-runtime-config.js +26 -0
- package/dist/core/opencode-runtime-config.js.map +1 -0
- package/dist/core/opencode-server-runner.d.ts +75 -0
- package/dist/core/opencode-server-runner.js +1690 -0
- package/dist/core/opencode-server-runner.js.map +1 -0
- package/dist/core/opencode-session-error.d.ts +19 -0
- package/dist/core/opencode-session-error.js +42 -0
- package/dist/core/opencode-session-error.js.map +1 -0
- package/dist/core/opencode-task-scope.d.ts +9 -0
- package/dist/core/opencode-task-scope.js +19 -0
- package/dist/core/opencode-task-scope.js.map +1 -0
- package/dist/core/opencode-ui-mapper-state.d.ts +82 -0
- package/dist/core/opencode-ui-mapper-state.js +7 -0
- package/dist/core/opencode-ui-mapper-state.js.map +1 -0
- package/dist/core/opencode-ui-mapper.d.ts +59 -0
- package/dist/core/opencode-ui-mapper.js +724 -0
- package/dist/core/opencode-ui-mapper.js.map +1 -0
- package/dist/core/opencode-usage.d.ts +32 -0
- package/dist/core/opencode-usage.js +105 -0
- package/dist/core/opencode-usage.js.map +1 -0
- package/dist/core/pi-model-catalog.d.ts +34 -0
- package/dist/core/pi-model-catalog.js +247 -0
- package/dist/core/pi-model-catalog.js.map +1 -0
- package/dist/core/pi-rpc-turn.d.ts +179 -0
- package/dist/core/pi-rpc-turn.js +370 -0
- package/dist/core/pi-rpc-turn.js.map +1 -0
- package/dist/core/pi-rpc-wire.d.ts +31 -0
- package/dist/core/pi-rpc-wire.js +88 -0
- package/dist/core/pi-rpc-wire.js.map +1 -0
- package/dist/core/pi-runner.d.ts +58 -0
- package/dist/core/pi-runner.js +587 -0
- package/dist/core/pi-runner.js.map +1 -0
- package/dist/core/pi-ui-mapper.d.ts +52 -0
- package/dist/core/pi-ui-mapper.js +408 -0
- package/dist/core/pi-ui-mapper.js.map +1 -0
- package/dist/core/process-usage.d.ts +74 -0
- package/dist/core/process-usage.js +219 -0
- package/dist/core/process-usage.js.map +1 -0
- package/dist/core/product-env.d.ts +21 -0
- package/dist/core/product-env.js +38 -0
- package/dist/core/product-env.js.map +1 -0
- package/dist/core/provider-auth.d.ts +168 -0
- package/dist/core/provider-auth.js +661 -0
- package/dist/core/provider-auth.js.map +1 -0
- package/dist/core/provider-availability.d.ts +3 -0
- package/dist/core/provider-availability.js +14 -0
- package/dist/core/provider-availability.js.map +1 -0
- package/dist/core/runner-factory.d.ts +11 -0
- package/dist/core/runner-factory.js +36 -0
- package/dist/core/runner-factory.js.map +1 -0
- package/dist/core/runner-facts.d.ts +135 -0
- package/dist/core/runner-facts.js +59 -0
- package/dist/core/runner-facts.js.map +1 -0
- package/dist/core/runner-model-catalog.d.ts +34 -0
- package/dist/core/runner-model-catalog.js +109 -0
- package/dist/core/runner-model-catalog.js.map +1 -0
- package/dist/core/runner-runtime.d.ts +40 -0
- package/dist/core/runner-runtime.js +88 -0
- package/dist/core/runner-runtime.js.map +1 -0
- package/dist/core/runner-stderr.d.ts +2 -0
- package/dist/core/runner-stderr.js +20 -0
- package/dist/core/runner-stderr.js.map +1 -0
- package/dist/core/runner-tool-grants.d.ts +19 -0
- package/dist/core/runner-tool-grants.js +54 -0
- package/dist/core/runner-tool-grants.js.map +1 -0
- package/dist/core/secret-redaction.d.ts +20 -0
- package/dist/core/secret-redaction.js +95 -0
- package/dist/core/secret-redaction.js.map +1 -0
- package/dist/core/session-process.d.ts +43 -0
- package/dist/core/session-process.js +363 -0
- package/dist/core/session-process.js.map +1 -0
- package/dist/core/shell-env.d.ts +17 -0
- package/dist/core/shell-env.js +51 -0
- package/dist/core/shell-env.js.map +1 -0
- package/dist/core/ui-events.d.ts +356 -0
- package/dist/core/ui-events.js +13 -0
- package/dist/core/ui-events.js.map +1 -0
- package/dist/core/usage-limit.d.ts +42 -0
- package/dist/core/usage-limit.js +152 -0
- package/dist/core/usage-limit.js.map +1 -0
- package/dist/core/usage.d.ts +11 -0
- package/dist/core/usage.js +15 -0
- package/dist/core/usage.js.map +1 -0
- package/dist/core/v1-text-coalescer.d.ts +36 -0
- package/dist/core/v1-text-coalescer.js +72 -0
- package/dist/core/v1-text-coalescer.js.map +1 -0
- package/dist/data-gitignore.d.ts +6 -0
- package/dist/data-gitignore.js +80 -0
- package/dist/data-gitignore.js.map +1 -0
- package/dist/delegation/capacity.d.ts +15 -0
- package/dist/delegation/capacity.js +30 -0
- package/dist/delegation/capacity.js.map +1 -0
- package/dist/delegation/cli.d.ts +5 -0
- package/dist/delegation/cli.js +572 -0
- package/dist/delegation/cli.js.map +1 -0
- package/dist/delegation/context.d.ts +40 -0
- package/dist/delegation/context.js +398 -0
- package/dist/delegation/context.js.map +1 -0
- package/dist/delegation/conversation-send.d.ts +64 -0
- package/dist/delegation/conversation-send.js +74 -0
- package/dist/delegation/conversation-send.js.map +1 -0
- package/dist/delegation/conversations.d.ts +2186 -0
- package/dist/delegation/conversations.js +285 -0
- package/dist/delegation/conversations.js.map +1 -0
- package/dist/delegation/credentials.d.ts +21 -0
- package/dist/delegation/credentials.js +66 -0
- package/dist/delegation/credentials.js.map +1 -0
- package/dist/delegation/destroy-observation.d.ts +42 -0
- package/dist/delegation/destroy-observation.js +115 -0
- package/dist/delegation/destroy-observation.js.map +1 -0
- package/dist/delegation/effort.d.ts +1 -0
- package/dist/delegation/effort.js +11 -0
- package/dist/delegation/effort.js.map +1 -0
- package/dist/delegation/execution-identity.d.ts +257 -0
- package/dist/delegation/execution-identity.js +253 -0
- package/dist/delegation/execution-identity.js.map +1 -0
- package/dist/delegation/execution-settings.d.ts +11 -0
- package/dist/delegation/execution-settings.js +65 -0
- package/dist/delegation/execution-settings.js.map +1 -0
- package/dist/delegation/idle-test-app.d.ts +4 -0
- package/dist/delegation/idle-test-app.js +186 -0
- package/dist/delegation/idle-test-app.js.map +1 -0
- package/dist/delegation/input.d.ts +12 -0
- package/dist/delegation/input.js +50 -0
- package/dist/delegation/input.js.map +1 -0
- package/dist/delegation/park-notice.d.ts +45 -0
- package/dist/delegation/park-notice.js +81 -0
- package/dist/delegation/park-notice.js.map +1 -0
- package/dist/delegation/policy.d.ts +27 -0
- package/dist/delegation/policy.js +110 -0
- package/dist/delegation/policy.js.map +1 -0
- package/dist/delegation/process-liveness.d.ts +98 -0
- package/dist/delegation/process-liveness.js +213 -0
- package/dist/delegation/process-liveness.js.map +1 -0
- package/dist/delegation/provision.d.ts +36 -0
- package/dist/delegation/provision.js +218 -0
- package/dist/delegation/provision.js.map +1 -0
- package/dist/delegation/questions.d.ts +24 -0
- package/dist/delegation/questions.js +56 -0
- package/dist/delegation/questions.js.map +1 -0
- package/dist/delegation/readiness.d.ts +13 -0
- package/dist/delegation/readiness.js +19 -0
- package/dist/delegation/readiness.js.map +1 -0
- package/dist/delegation/results.d.ts +13 -0
- package/dist/delegation/results.js +153 -0
- package/dist/delegation/results.js.map +1 -0
- package/dist/delegation/retry-backoff.d.ts +31 -0
- package/dist/delegation/retry-backoff.js +26 -0
- package/dist/delegation/retry-backoff.js.map +1 -0
- package/dist/delegation/routes.d.ts +1017 -0
- package/dist/delegation/routes.js +85 -0
- package/dist/delegation/routes.js.map +1 -0
- package/dist/delegation/scratch-cleanup.d.ts +33 -0
- package/dist/delegation/scratch-cleanup.js +155 -0
- package/dist/delegation/scratch-cleanup.js.map +1 -0
- package/dist/delegation/service.d.ts +264 -0
- package/dist/delegation/service.js +807 -0
- package/dist/delegation/service.js.map +1 -0
- package/dist/delegation/task-discovery.d.ts +6 -0
- package/dist/delegation/task-discovery.js +27 -0
- package/dist/delegation/task-discovery.js.map +1 -0
- package/dist/delegation/transport.d.ts +995 -0
- package/dist/delegation/transport.js +22 -0
- package/dist/delegation/transport.js.map +1 -0
- package/dist/delegation/wait.d.ts +7 -0
- package/dist/delegation/wait.js +44 -0
- package/dist/delegation/wait.js.map +1 -0
- package/dist/delegation/windows-test-app.d.ts +5 -0
- package/dist/delegation/windows-test-app.js +64 -0
- package/dist/delegation/windows-test-app.js.map +1 -0
- package/dist/delegation/windows-workspace-identity.d.ts +3 -0
- package/dist/delegation/windows-workspace-identity.js +23 -0
- package/dist/delegation/windows-workspace-identity.js.map +1 -0
- package/dist/delegation/worker-cleanup.d.ts +53 -0
- package/dist/delegation/worker-cleanup.js +330 -0
- package/dist/delegation/worker-cleanup.js.map +1 -0
- package/dist/delegation/workspace.d.ts +40 -0
- package/dist/delegation/workspace.js +592 -0
- package/dist/delegation/workspace.js.map +1 -0
- package/dist/detached-process.d.ts +134 -0
- package/dist/detached-process.js +460 -0
- package/dist/detached-process.js.map +1 -0
- package/dist/discovery/catalog.d.ts +16 -0
- package/dist/discovery/catalog.js +31 -0
- package/dist/discovery/catalog.js.map +1 -0
- package/dist/discovery/cli.d.ts +8 -0
- package/dist/discovery/cli.js +122 -0
- package/dist/discovery/cli.js.map +1 -0
- package/dist/git-command.d.ts +7 -0
- package/dist/git-command.js +8 -0
- package/dist/git-command.js.map +1 -0
- package/dist/git-diff-base.d.ts +41 -0
- package/dist/git-diff-base.js +116 -0
- package/dist/git-diff-base.js.map +1 -0
- package/dist/git-index-copy.d.ts +4 -0
- package/dist/git-index-copy.js +10 -0
- package/dist/git-index-copy.js.map +1 -0
- package/dist/git-private-index.d.ts +11 -0
- package/dist/git-private-index.js +51 -0
- package/dist/git-private-index.js.map +1 -0
- package/dist/git-refs.d.ts +11 -0
- package/dist/git-refs.js +14 -0
- package/dist/git-refs.js.map +1 -0
- package/dist/git-worktree-lock-helper.d.ts +7 -0
- package/dist/git-worktree-lock-helper.js +178 -0
- package/dist/git-worktree-lock-helper.js.map +1 -0
- package/dist/git-worktree-lock.d.ts +17 -0
- package/dist/git-worktree-lock.js +90 -0
- package/dist/git-worktree-lock.js.map +1 -0
- package/dist/git-worktree-release.d.ts +20 -0
- package/dist/git-worktree-release.js +27 -0
- package/dist/git-worktree-release.js.map +1 -0
- package/dist/git-worktree.d.ts +148 -0
- package/dist/git-worktree.js +858 -0
- package/dist/git-worktree.js.map +1 -0
- package/dist/handoff.d.ts +72 -0
- package/dist/handoff.js +213 -0
- package/dist/handoff.js.map +1 -0
- package/dist/headless-run.d.ts +48 -0
- package/dist/headless-run.js +158 -0
- package/dist/headless-run.js.map +1 -0
- package/dist/home/boundary.d.ts +5 -0
- package/dist/home/boundary.js +76 -0
- package/dist/home/boundary.js.map +1 -0
- package/dist/home/changes.d.ts +3 -0
- package/dist/home/changes.js +11 -0
- package/dist/home/changes.js.map +1 -0
- package/dist/home/command.d.ts +9 -0
- package/dist/home/command.js +37 -0
- package/dist/home/command.js.map +1 -0
- package/dist/home/deprecation.d.ts +9 -0
- package/dist/home/deprecation.js +17 -0
- package/dist/home/deprecation.js.map +1 -0
- package/dist/home/display.d.ts +4 -0
- package/dist/home/display.js +11 -0
- package/dist/home/display.js.map +1 -0
- package/dist/home/entry-authorization.d.ts +17 -0
- package/dist/home/entry-authorization.js +39 -0
- package/dist/home/entry-authorization.js.map +1 -0
- package/dist/home/entry.d.ts +16 -0
- package/dist/home/entry.js +96 -0
- package/dist/home/entry.js.map +1 -0
- package/dist/home/file-location.d.ts +14 -0
- package/dist/home/file-location.js +24 -0
- package/dist/home/file-location.js.map +1 -0
- package/dist/home/home.d.ts +42 -0
- package/dist/home/home.js +70 -0
- package/dist/home/home.js.map +1 -0
- package/dist/home/identity.d.ts +22 -0
- package/dist/home/identity.js +25 -0
- package/dist/home/identity.js.map +1 -0
- package/dist/home/install-marker.d.ts +27 -0
- package/dist/home/install-marker.js +78 -0
- package/dist/home/install-marker.js.map +1 -0
- package/dist/home/kind-roots.d.ts +10 -0
- package/dist/home/kind-roots.js +36 -0
- package/dist/home/kind-roots.js.map +1 -0
- package/dist/home/leases.d.ts +14 -0
- package/dist/home/leases.js +55 -0
- package/dist/home/leases.js.map +1 -0
- package/dist/home/legacy-account-paths.d.ts +4 -0
- package/dist/home/legacy-account-paths.js +41 -0
- package/dist/home/legacy-account-paths.js.map +1 -0
- package/dist/home/legacy-merge.d.ts +3 -0
- package/dist/home/legacy-merge.js +45 -0
- package/dist/home/legacy-merge.js.map +1 -0
- package/dist/home/legacy-recovery.d.ts +5 -0
- package/dist/home/legacy-recovery.js +154 -0
- package/dist/home/legacy-recovery.js.map +1 -0
- package/dist/home/legacy-source.d.ts +15 -0
- package/dist/home/legacy-source.js +57 -0
- package/dist/home/legacy-source.js.map +1 -0
- package/dist/home/migrate.d.ts +19 -0
- package/dist/home/migrate.js +322 -0
- package/dist/home/migrate.js.map +1 -0
- package/dist/home/migration-account-source.d.ts +2 -0
- package/dist/home/migration-account-source.js +17 -0
- package/dist/home/migration-account-source.js.map +1 -0
- package/dist/home/migration-accounts.d.ts +23 -0
- package/dist/home/migration-accounts.js +174 -0
- package/dist/home/migration-accounts.js.map +1 -0
- package/dist/home/migration-boot.d.ts +23 -0
- package/dist/home/migration-boot.js +84 -0
- package/dist/home/migration-boot.js.map +1 -0
- package/dist/home/migration-claim.d.ts +4 -0
- package/dist/home/migration-claim.js +69 -0
- package/dist/home/migration-claim.js.map +1 -0
- package/dist/home/migration-cli.d.ts +3 -0
- package/dist/home/migration-cli.js +64 -0
- package/dist/home/migration-cli.js.map +1 -0
- package/dist/home/migration-files.d.ts +14 -0
- package/dist/home/migration-files.js +192 -0
- package/dist/home/migration-files.js.map +1 -0
- package/dist/home/migration-guard.d.ts +2 -0
- package/dist/home/migration-guard.js +20 -0
- package/dist/home/migration-guard.js.map +1 -0
- package/dist/home/migration-plan.d.ts +20 -0
- package/dist/home/migration-plan.js +71 -0
- package/dist/home/migration-plan.js.map +1 -0
- package/dist/home/migration-record.d.ts +79 -0
- package/dist/home/migration-record.js +39 -0
- package/dist/home/migration-record.js.map +1 -0
- package/dist/home/private-files.d.ts +13 -0
- package/dist/home/private-files.js +113 -0
- package/dist/home/private-files.js.map +1 -0
- package/dist/home/process-identity.d.ts +2 -0
- package/dist/home/process-identity.js +8 -0
- package/dist/home/process-identity.js.map +1 -0
- package/dist/home/protected-roots.d.ts +25 -0
- package/dist/home/protected-roots.js +91 -0
- package/dist/home/protected-roots.js.map +1 -0
- package/dist/home/recovery-watch.d.ts +3 -0
- package/dist/home/recovery-watch.js +75 -0
- package/dist/home/recovery-watch.js.map +1 -0
- package/dist/home/recovery.d.ts +10 -0
- package/dist/home/recovery.js +94 -0
- package/dist/home/recovery.js.map +1 -0
- package/dist/home/registry-content.d.ts +2 -0
- package/dist/home/registry-content.js +17 -0
- package/dist/home/registry-content.js.map +1 -0
- package/dist/home/reset-detection.d.ts +5 -0
- package/dist/home/reset-detection.js +22 -0
- package/dist/home/reset-detection.js.map +1 -0
- package/dist/home/resolve.d.ts +10 -0
- package/dist/home/resolve.js +28 -0
- package/dist/home/resolve.js.map +1 -0
- package/dist/home/store.d.ts +48 -0
- package/dist/home/store.js +324 -0
- package/dist/home/store.js.map +1 -0
- package/dist/home/switch.d.ts +2 -0
- package/dist/home/switch.js +3 -0
- package/dist/home/switch.js.map +1 -0
- package/dist/home/version-log.d.ts +48 -0
- package/dist/home/version-log.js +216 -0
- package/dist/home/version-log.js.map +1 -0
- package/dist/home/write-lock.d.ts +6 -0
- package/dist/home/write-lock.js +96 -0
- package/dist/home/write-lock.js.map +1 -0
- package/dist/hsq.d.ts +2 -0
- package/dist/hsq.js +3 -0
- package/dist/hsq.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +123 -0
- package/dist/index.js.map +1 -0
- package/dist/install-as-command.d.ts +32 -0
- package/dist/install-as-command.js +55 -0
- package/dist/install-as-command.js.map +1 -0
- package/dist/jobs/cli.d.ts +2 -0
- package/dist/jobs/cli.js +53 -0
- package/dist/jobs/cli.js.map +1 -0
- package/dist/jobs/command.d.ts +9 -0
- package/dist/jobs/command.js +19 -0
- package/dist/jobs/command.js.map +1 -0
- package/dist/jobs/job.d.ts +77 -0
- package/dist/jobs/job.js +252 -0
- package/dist/jobs/job.js.map +1 -0
- package/dist/merge-observations.d.ts +13 -0
- package/dist/merge-observations.js +55 -0
- package/dist/merge-observations.js.map +1 -0
- package/dist/merged-streams.d.ts +13 -0
- package/dist/merged-streams.js +33 -0
- package/dist/merged-streams.js.map +1 -0
- package/dist/open-url.d.ts +2 -0
- package/dist/open-url.js +10 -0
- package/dist/open-url.js.map +1 -0
- package/dist/pack-check.d.ts +17 -0
- package/dist/pack-check.js +27 -0
- package/dist/pack-check.js.map +1 -0
- package/dist/paths.d.ts +118 -0
- package/dist/paths.js +192 -0
- package/dist/paths.js.map +1 -0
- package/dist/planner.d.ts +30 -0
- package/dist/planner.js +270 -0
- package/dist/planner.js.map +1 -0
- package/dist/preview/agent-address.d.ts +34 -0
- package/dist/preview/agent-address.js +52 -0
- package/dist/preview/agent-address.js.map +1 -0
- package/dist/preview/agent-marker.d.ts +17 -0
- package/dist/preview/agent-marker.js +71 -0
- package/dist/preview/agent-marker.js.map +1 -0
- package/dist/preview/browser-launch.d.ts +28 -0
- package/dist/preview/browser-launch.js +64 -0
- package/dist/preview/browser-launch.js.map +1 -0
- package/dist/preview/cdp-proxy.d.ts +54 -0
- package/dist/preview/cdp-proxy.js +152 -0
- package/dist/preview/cdp-proxy.js.map +1 -0
- package/dist/preview/cdp.d.ts +49 -0
- package/dist/preview/cdp.js +167 -0
- package/dist/preview/cdp.js.map +1 -0
- package/dist/preview/chromium-launch-progress.d.ts +10 -0
- package/dist/preview/chromium-launch-progress.js +48 -0
- package/dist/preview/chromium-launch-progress.js.map +1 -0
- package/dist/preview/chromium.d.ts +91 -0
- package/dist/preview/chromium.js +500 -0
- package/dist/preview/chromium.js.map +1 -0
- package/dist/preview/density.d.ts +17 -0
- package/dist/preview/density.js +62 -0
- package/dist/preview/density.js.map +1 -0
- package/dist/preview/dev-server.d.ts +68 -0
- package/dist/preview/dev-server.js +318 -0
- package/dist/preview/dev-server.js.map +1 -0
- package/dist/preview/host.d.ts +215 -0
- package/dist/preview/host.js +954 -0
- package/dist/preview/host.js.map +1 -0
- package/dist/preview/registration.d.ts +68 -0
- package/dist/preview/registration.js +111 -0
- package/dist/preview/registration.js.map +1 -0
- package/dist/preview/run-tools.d.ts +9 -0
- package/dist/preview/run-tools.js +72 -0
- package/dist/preview/run-tools.js.map +1 -0
- package/dist/preview/session.d.ts +139 -0
- package/dist/preview/session.js +479 -0
- package/dist/preview/session.js.map +1 -0
- package/dist/preview/url.d.ts +6 -0
- package/dist/preview/url.js +17 -0
- package/dist/preview/url.js.map +1 -0
- package/dist/process-wait/exit-file.d.ts +29 -0
- package/dist/process-wait/exit-file.js +67 -0
- package/dist/process-wait/exit-file.js.map +1 -0
- package/dist/process-wait/supervisor.d.ts +30 -0
- package/dist/process-wait/supervisor.js +71 -0
- package/dist/process-wait/supervisor.js.map +1 -0
- package/dist/project-commands.d.ts +14 -0
- package/dist/project-commands.js +91 -0
- package/dist/project-commands.js.map +1 -0
- package/dist/project-migration/answer.d.ts +23 -0
- package/dist/project-migration/answer.js +37 -0
- package/dist/project-migration/answer.js.map +1 -0
- package/dist/project-migration/auto.d.ts +73 -0
- package/dist/project-migration/auto.js +161 -0
- package/dist/project-migration/auto.js.map +1 -0
- package/dist/project-migration/cli.d.ts +18 -0
- package/dist/project-migration/cli.js +57 -0
- package/dist/project-migration/cli.js.map +1 -0
- package/dist/project-migration/containment.d.ts +52 -0
- package/dist/project-migration/containment.js +200 -0
- package/dist/project-migration/containment.js.map +1 -0
- package/dist/project-migration/durable.d.ts +25 -0
- package/dist/project-migration/durable.js +69 -0
- package/dist/project-migration/durable.js.map +1 -0
- package/dist/project-migration/engine.d.ts +47 -0
- package/dist/project-migration/engine.js +103 -0
- package/dist/project-migration/engine.js.map +1 -0
- package/dist/project-migration/fresh.d.ts +4 -0
- package/dist/project-migration/fresh.js +25 -0
- package/dist/project-migration/fresh.js.map +1 -0
- package/dist/project-migration/journal.d.ts +139 -0
- package/dist/project-migration/journal.js +69 -0
- package/dist/project-migration/journal.js.map +1 -0
- package/dist/project-migration/live.d.ts +48 -0
- package/dist/project-migration/live.js +148 -0
- package/dist/project-migration/live.js.map +1 -0
- package/dist/project-migration/offline.d.ts +25 -0
- package/dist/project-migration/offline.js +54 -0
- package/dist/project-migration/offline.js.map +1 -0
- package/dist/project-migration/plan.d.ts +91 -0
- package/dist/project-migration/plan.js +466 -0
- package/dist/project-migration/plan.js.map +1 -0
- package/dist/project-migration/recovery.d.ts +28 -0
- package/dist/project-migration/recovery.js +78 -0
- package/dist/project-migration/recovery.js.map +1 -0
- package/dist/project-migration/steps.d.ts +18 -0
- package/dist/project-migration/steps.js +312 -0
- package/dist/project-migration/steps.js.map +1 -0
- package/dist/project-paths.d.ts +61 -0
- package/dist/project-paths.js +185 -0
- package/dist/project-paths.js.map +1 -0
- package/dist/release/compatibility.d.ts +12 -0
- package/dist/release/compatibility.js +62 -0
- package/dist/release/compatibility.js.map +1 -0
- package/dist/release/manifests.d.ts +112 -0
- package/dist/release/manifests.js +104 -0
- package/dist/release/manifests.js.map +1 -0
- package/dist/release/publication.d.ts +3 -0
- package/dist/release/publication.js +34 -0
- package/dist/release/publication.js.map +1 -0
- package/dist/release/snapshot.d.ts +67 -0
- package/dist/release/snapshot.js +97 -0
- package/dist/release/snapshot.js.map +1 -0
- package/dist/release/stable.d.ts +38 -0
- package/dist/release/stable.js +55 -0
- package/dist/release/stable.js.map +1 -0
- package/dist/remote-settings-cli.d.ts +24 -0
- package/dist/remote-settings-cli.js +156 -0
- package/dist/remote-settings-cli.js.map +1 -0
- package/dist/remote-settings.d.ts +41 -0
- package/dist/remote-settings.js +110 -0
- package/dist/remote-settings.js.map +1 -0
- package/dist/runs/agent-tmpdir-removal.d.ts +15 -0
- package/dist/runs/agent-tmpdir-removal.js +60 -0
- package/dist/runs/agent-tmpdir-removal.js.map +1 -0
- package/dist/runs/agent-tmpdir.d.ts +119 -0
- package/dist/runs/agent-tmpdir.js +855 -0
- package/dist/runs/agent-tmpdir.js.map +1 -0
- package/dist/runs/archive-family.d.ts +33 -0
- package/dist/runs/archive-family.js +90 -0
- package/dist/runs/archive-family.js.map +1 -0
- package/dist/runs/arm-repo-handle.d.ts +10 -0
- package/dist/runs/arm-repo-handle.js +45 -0
- package/dist/runs/arm-repo-handle.js.map +1 -0
- package/dist/runs/auto-name.d.ts +77 -0
- package/dist/runs/auto-name.js +179 -0
- package/dist/runs/auto-name.js.map +1 -0
- package/dist/runs/claim-families.d.ts +37 -0
- package/dist/runs/claim-families.js +101 -0
- package/dist/runs/claim-families.js.map +1 -0
- package/dist/runs/client-request.d.ts +11 -0
- package/dist/runs/client-request.js +26 -0
- package/dist/runs/client-request.js.map +1 -0
- package/dist/runs/cold-reads.d.ts +40 -0
- package/dist/runs/cold-reads.js +56 -0
- package/dist/runs/cold-reads.js.map +1 -0
- package/dist/runs/conversation-identity.d.ts +19 -0
- package/dist/runs/conversation-identity.js +31 -0
- package/dist/runs/conversation-identity.js.map +1 -0
- package/dist/runs/delegation-state.d.ts +409 -0
- package/dist/runs/delegation-state.js +22 -0
- package/dist/runs/delegation-state.js.map +1 -0
- package/dist/runs/event-history-agent-context.d.ts +15 -0
- package/dist/runs/event-history-agent-context.js +119 -0
- package/dist/runs/event-history-agent-context.js.map +1 -0
- package/dist/runs/event-history-context.d.ts +15 -0
- package/dist/runs/event-history-context.js +109 -0
- package/dist/runs/event-history-context.js.map +1 -0
- package/dist/runs/event-history.d.ts +53 -0
- package/dist/runs/event-history.js +563 -0
- package/dist/runs/event-history.js.map +1 -0
- package/dist/runs/exit-events.d.ts +27 -0
- package/dist/runs/exit-events.js +46 -0
- package/dist/runs/exit-events.js.map +1 -0
- package/dist/runs/history-compressor.d.ts +29 -0
- package/dist/runs/history-compressor.js +116 -0
- package/dist/runs/history-compressor.js.map +1 -0
- package/dist/runs/history-file.d.ts +36 -0
- package/dist/runs/history-file.js +464 -0
- package/dist/runs/history-file.js.map +1 -0
- package/dist/runs/history-stream.d.ts +2 -0
- package/dist/runs/history-stream.js +62 -0
- package/dist/runs/history-stream.js.map +1 -0
- package/dist/runs/legacy-index.d.ts +49 -0
- package/dist/runs/legacy-index.js +156 -0
- package/dist/runs/legacy-index.js.map +1 -0
- package/dist/runs/legacy-run-rows.d.ts +12 -0
- package/dist/runs/legacy-run-rows.js +33 -0
- package/dist/runs/legacy-run-rows.js.map +1 -0
- package/dist/runs/legacy-waits.d.ts +64 -0
- package/dist/runs/legacy-waits.js +335 -0
- package/dist/runs/legacy-waits.js.map +1 -0
- package/dist/runs/live-item-snapshots.d.ts +15 -0
- package/dist/runs/live-item-snapshots.js +95 -0
- package/dist/runs/live-item-snapshots.js.map +1 -0
- package/dist/runs/loaded-run-exits.d.ts +6 -0
- package/dist/runs/loaded-run-exits.js +32 -0
- package/dist/runs/loaded-run-exits.js.map +1 -0
- package/dist/runs/loaded-run-healing.d.ts +3 -0
- package/dist/runs/loaded-run-healing.js +17 -0
- package/dist/runs/loaded-run-healing.js.map +1 -0
- package/dist/runs/outcome-commit.d.ts +119 -0
- package/dist/runs/outcome-commit.js +155 -0
- package/dist/runs/outcome-commit.js.map +1 -0
- package/dist/runs/owned-run-create.d.ts +33 -0
- package/dist/runs/owned-run-create.js +98 -0
- package/dist/runs/owned-run-create.js.map +1 -0
- package/dist/runs/project-run-connection.d.ts +76 -0
- package/dist/runs/project-run-connection.js +287 -0
- package/dist/runs/project-run-connection.js.map +1 -0
- package/dist/runs/project-run-sources.d.ts +21 -0
- package/dist/runs/project-run-sources.js +86 -0
- package/dist/runs/project-run-sources.js.map +1 -0
- package/dist/runs/pull-requests.d.ts +14 -0
- package/dist/runs/pull-requests.js +96 -0
- package/dist/runs/pull-requests.js.map +1 -0
- package/dist/runs/raw-record.d.ts +40 -0
- package/dist/runs/raw-record.js +133 -0
- package/dist/runs/raw-record.js.map +1 -0
- package/dist/runs/record-write-rules.d.ts +129 -0
- package/dist/runs/record-write-rules.js +326 -0
- package/dist/runs/record-write-rules.js.map +1 -0
- package/dist/runs/report-rows.d.ts +36 -0
- package/dist/runs/report-rows.js +102 -0
- package/dist/runs/report-rows.js.map +1 -0
- package/dist/runs/retention.d.ts +91 -0
- package/dist/runs/retention.js +193 -0
- package/dist/runs/retention.js.map +1 -0
- package/dist/runs/review-gate.d.ts +18 -0
- package/dist/runs/review-gate.js +22 -0
- package/dist/runs/review-gate.js.map +1 -0
- package/dist/runs/run-claims.d.ts +30 -0
- package/dist/runs/run-claims.js +52 -0
- package/dist/runs/run-claims.js.map +1 -0
- package/dist/runs/run-database-peer-page.d.ts +12 -0
- package/dist/runs/run-database-peer-page.js +25 -0
- package/dist/runs/run-database-peer-page.js.map +1 -0
- package/dist/runs/run-database-row.d.ts +39 -0
- package/dist/runs/run-database-row.js +83 -0
- package/dist/runs/run-database-row.js.map +1 -0
- package/dist/runs/run-database-version.d.ts +14 -0
- package/dist/runs/run-database-version.js +19 -0
- package/dist/runs/run-database-version.js.map +1 -0
- package/dist/runs/run-database.d.ts +389 -0
- package/dist/runs/run-database.js +957 -0
- package/dist/runs/run-database.js.map +1 -0
- package/dist/runs/run-exits.d.ts +4510 -0
- package/dist/runs/run-exits.js +191 -0
- package/dist/runs/run-exits.js.map +1 -0
- package/dist/runs/run-index.d.ts +36 -0
- package/dist/runs/run-index.js +262 -0
- package/dist/runs/run-index.js.map +1 -0
- package/dist/runs/run-references.d.ts +61 -0
- package/dist/runs/run-references.js +421 -0
- package/dist/runs/run-references.js.map +1 -0
- package/dist/runs/run-row.d.ts +14 -0
- package/dist/runs/run-row.js +56 -0
- package/dist/runs/run-row.js.map +1 -0
- package/dist/runs/run-summary-page.d.ts +36 -0
- package/dist/runs/run-summary-page.js +46 -0
- package/dist/runs/run-summary-page.js.map +1 -0
- package/dist/runs/run-title.d.ts +8 -0
- package/dist/runs/run-title.js +28 -0
- package/dist/runs/run-title.js.map +1 -0
- package/dist/runs/store-open-error.d.ts +24 -0
- package/dist/runs/store-open-error.js +62 -0
- package/dist/runs/store-open-error.js.map +1 -0
- package/dist/runs/store.d.ts +3974 -0
- package/dist/runs/store.js +3516 -0
- package/dist/runs/store.js.map +1 -0
- package/dist/runs/task-markers.d.ts +29 -0
- package/dist/runs/task-markers.js +78 -0
- package/dist/runs/task-markers.js.map +1 -0
- package/dist/runs/task-refs.d.ts +39 -0
- package/dist/runs/task-refs.js +107 -0
- package/dist/runs/task-refs.js.map +1 -0
- package/dist/runs/transcript-facts-fold.d.ts +64 -0
- package/dist/runs/transcript-facts-fold.js +109 -0
- package/dist/runs/transcript-facts-fold.js.map +1 -0
- package/dist/runs/transcript-facts-load.d.ts +11 -0
- package/dist/runs/transcript-facts-load.js +66 -0
- package/dist/runs/transcript-facts-load.js.map +1 -0
- package/dist/runs/transcript-facts-queue.d.ts +30 -0
- package/dist/runs/transcript-facts-queue.js +227 -0
- package/dist/runs/transcript-facts-queue.js.map +1 -0
- package/dist/runs/transcript-facts-worker.d.ts +15 -0
- package/dist/runs/transcript-facts-worker.js +26 -0
- package/dist/runs/transcript-facts-worker.js.map +1 -0
- package/dist/runs/transcript-facts.d.ts +40 -0
- package/dist/runs/transcript-facts.js +193 -0
- package/dist/runs/transcript-facts.js.map +1 -0
- package/dist/runs/ui-event-sink.d.ts +59 -0
- package/dist/runs/ui-event-sink.js +197 -0
- package/dist/runs/ui-event-sink.js.map +1 -0
- package/dist/runs/wait-capacity.d.ts +8 -0
- package/dist/runs/wait-capacity.js +28 -0
- package/dist/runs/wait-capacity.js.map +1 -0
- package/dist/runs/wait-phases.d.ts +4 -0
- package/dist/runs/wait-phases.js +11 -0
- package/dist/runs/wait-phases.js.map +1 -0
- package/dist/runs/wait-registration-write.d.ts +5 -0
- package/dist/runs/wait-registration-write.js +35 -0
- package/dist/runs/wait-registration-write.js.map +1 -0
- package/dist/runs/webhook.d.ts +193 -0
- package/dist/runs/webhook.js +362 -0
- package/dist/runs/webhook.js.map +1 -0
- package/dist/runs/worker-deletion-proof.d.ts +13 -0
- package/dist/runs/worker-deletion-proof.js +13 -0
- package/dist/runs/worker-deletion-proof.js.map +1 -0
- package/dist/runs/worker-execution.d.ts +22 -0
- package/dist/runs/worker-execution.js +30 -0
- package/dist/runs/worker-execution.js.map +1 -0
- package/dist/runs/worker-resource-holders.d.ts +17 -0
- package/dist/runs/worker-resource-holders.js +34 -0
- package/dist/runs/worker-resource-holders.js.map +1 -0
- package/dist/runs/worker-result-commit.d.ts +26 -0
- package/dist/runs/worker-result-commit.js +121 -0
- package/dist/runs/worker-result-commit.js.map +1 -0
- package/dist/self-update/checkouts.d.ts +46 -0
- package/dist/self-update/checkouts.js +284 -0
- package/dist/self-update/checkouts.js.map +1 -0
- package/dist/self-update/cli.d.ts +19 -0
- package/dist/self-update/cli.js +243 -0
- package/dist/self-update/cli.js.map +1 -0
- package/dist/self-update/installer.d.ts +24 -0
- package/dist/self-update/installer.js +139 -0
- package/dist/self-update/installer.js.map +1 -0
- package/dist/self-update/launcher.d.ts +30 -0
- package/dist/self-update/launcher.js +172 -0
- package/dist/self-update/launcher.js.map +1 -0
- package/dist/self-update/layout.d.ts +108 -0
- package/dist/self-update/layout.js +276 -0
- package/dist/self-update/layout.js.map +1 -0
- package/dist/self-update/pulls.d.ts +30 -0
- package/dist/self-update/pulls.js +128 -0
- package/dist/self-update/pulls.js.map +1 -0
- package/dist/self-update/registry.d.ts +37 -0
- package/dist/self-update/registry.js +86 -0
- package/dist/self-update/registry.js.map +1 -0
- package/dist/self-update/restart.d.ts +25 -0
- package/dist/self-update/restart.js +79 -0
- package/dist/self-update/restart.js.map +1 -0
- package/dist/self-update/semver.d.ts +19 -0
- package/dist/self-update/semver.js +79 -0
- package/dist/self-update/semver.js.map +1 -0
- package/dist/self-update/service.d.ts +115 -0
- package/dist/self-update/service.js +332 -0
- package/dist/self-update/service.js.map +1 -0
- package/dist/self-update/supervisor.d.ts +10 -0
- package/dist/self-update/supervisor.js +52 -0
- package/dist/self-update/supervisor.js.map +1 -0
- package/dist/serve-boot.d.ts +60 -0
- package/dist/serve-boot.js +142 -0
- package/dist/serve-boot.js.map +1 -0
- package/dist/serve-command.d.ts +71 -0
- package/dist/serve-command.js +168 -0
- package/dist/serve-command.js.map +1 -0
- package/dist/serve-listener.d.ts +12 -0
- package/dist/serve-listener.js +52 -0
- package/dist/serve-listener.js.map +1 -0
- package/dist/server/activity-project-topic.d.ts +44 -0
- package/dist/server/activity-project-topic.js +137 -0
- package/dist/server/activity-project-topic.js.map +1 -0
- package/dist/server/activity-routes.d.ts +317 -0
- package/dist/server/activity-routes.js +117 -0
- package/dist/server/activity-routes.js.map +1 -0
- package/dist/server/activity-sources.d.ts +26 -0
- package/dist/server/activity-sources.js +45 -0
- package/dist/server/activity-sources.js.map +1 -0
- package/dist/server/activity-topic.d.ts +33 -0
- package/dist/server/activity-topic.js +48 -0
- package/dist/server/activity-topic.js.map +1 -0
- package/dist/server/app-type.d.ts +6 -0
- package/dist/server/app-type.js +2 -0
- package/dist/server/app-type.js.map +1 -0
- package/dist/server/archive-on-merge.d.ts +45 -0
- package/dist/server/archive-on-merge.js +242 -0
- package/dist/server/archive-on-merge.js.map +1 -0
- package/dist/server/automation-log-retry.d.ts +27 -0
- package/dist/server/automation-log-retry.js +88 -0
- package/dist/server/automation-log-retry.js.map +1 -0
- package/dist/server/capabilities.d.ts +69 -0
- package/dist/server/capabilities.js +111 -0
- package/dist/server/capabilities.js.map +1 -0
- package/dist/server/checkout-progress.d.ts +10 -0
- package/dist/server/checkout-progress.js +27 -0
- package/dist/server/checkout-progress.js.map +1 -0
- package/dist/server/checkout.d.ts +153 -0
- package/dist/server/checkout.js +323 -0
- package/dist/server/checkout.js.map +1 -0
- package/dist/server/cockpit-ownership.d.ts +112 -0
- package/dist/server/cockpit-ownership.js +420 -0
- package/dist/server/cockpit-ownership.js.map +1 -0
- package/dist/server/cold-repo-handles.d.ts +17 -0
- package/dist/server/cold-repo-handles.js +82 -0
- package/dist/server/cold-repo-handles.js.map +1 -0
- package/dist/server/completion-actions.d.ts +37 -0
- package/dist/server/completion-actions.js +261 -0
- package/dist/server/completion-actions.js.map +1 -0
- package/dist/server/config-merge.d.ts +11 -0
- package/dist/server/config-merge.js +74 -0
- package/dist/server/config-merge.js.map +1 -0
- package/dist/server/create-project.d.ts +71 -0
- package/dist/server/create-project.js +467 -0
- package/dist/server/create-project.js.map +1 -0
- package/dist/server/forge/draft-pr.d.ts +8 -0
- package/dist/server/forge/draft-pr.js +123 -0
- package/dist/server/forge/draft-pr.js.map +1 -0
- package/dist/server/forge/github-filters.d.ts +22 -0
- package/dist/server/forge/github-filters.js +128 -0
- package/dist/server/forge/github-filters.js.map +1 -0
- package/dist/server/forge/github-merge-requirements.d.ts +18 -0
- package/dist/server/forge/github-merge-requirements.js +148 -0
- package/dist/server/forge/github-merge-requirements.js.map +1 -0
- package/dist/server/forge/github-merge-state.d.ts +27 -0
- package/dist/server/forge/github-merge-state.js +248 -0
- package/dist/server/forge/github-merge-state.js.map +1 -0
- package/dist/server/forge/github-timeline.d.ts +117 -0
- package/dist/server/forge/github-timeline.js +273 -0
- package/dist/server/forge/github-timeline.js.map +1 -0
- package/dist/server/forge/github.d.ts +393 -0
- package/dist/server/forge/github.js +2881 -0
- package/dist/server/forge/github.js.map +1 -0
- package/dist/server/forge/index.d.ts +47 -0
- package/dist/server/forge/index.js +81 -0
- package/dist/server/forge/index.js.map +1 -0
- package/dist/server/forge/repo-handle-discovery.d.ts +16 -0
- package/dist/server/forge/repo-handle-discovery.js +2 -0
- package/dist/server/forge/repo-handle-discovery.js.map +1 -0
- package/dist/server/forge/types.d.ts +278 -0
- package/dist/server/forge/types.js +2 -0
- package/dist/server/forge/types.js.map +1 -0
- package/dist/server/fs-browse.d.ts +82 -0
- package/dist/server/fs-browse.js +217 -0
- package/dist/server/fs-browse.js.map +1 -0
- package/dist/server/git-changes.d.ts +205 -0
- package/dist/server/git-changes.js +531 -0
- package/dist/server/git-changes.js.map +1 -0
- package/dist/server/git.d.ts +52 -0
- package/dist/server/git.js +174 -0
- package/dist/server/git.js.map +1 -0
- package/dist/server/home-recovery-routes.d.ts +171 -0
- package/dist/server/home-recovery-routes.js +56 -0
- package/dist/server/home-recovery-routes.js.map +1 -0
- package/dist/server/launch-key.d.ts +5 -0
- package/dist/server/launch-key.js +35 -0
- package/dist/server/launch-key.js.map +1 -0
- package/dist/server/live-routes.d.ts +73 -0
- package/dist/server/live-routes.js +161 -0
- package/dist/server/live-routes.js.map +1 -0
- package/dist/server/open-in-app.d.ts +57 -0
- package/dist/server/open-in-app.js +178 -0
- package/dist/server/open-in-app.js.map +1 -0
- package/dist/server/open-in-targets.d.ts +53 -0
- package/dist/server/open-in-targets.js +112 -0
- package/dist/server/open-in-targets.js.map +1 -0
- package/dist/server/open-in-terminal.d.ts +73 -0
- package/dist/server/open-in-terminal.js +160 -0
- package/dist/server/open-in-terminal.js.map +1 -0
- package/dist/server/preview-cdp-socket.d.ts +21 -0
- package/dist/server/preview-cdp-socket.js +112 -0
- package/dist/server/preview-cdp-socket.js.map +1 -0
- package/dist/server/preview-socket.d.ts +36 -0
- package/dist/server/preview-socket.js +142 -0
- package/dist/server/preview-socket.js.map +1 -0
- package/dist/server/preview-sockets.d.ts +14 -0
- package/dist/server/preview-sockets.js +18 -0
- package/dist/server/preview-sockets.js.map +1 -0
- package/dist/server/project-context.d.ts +218 -0
- package/dist/server/project-context.js +525 -0
- package/dist/server/project-context.js.map +1 -0
- package/dist/server/project-run-files.d.ts +13 -0
- package/dist/server/project-run-files.js +52 -0
- package/dist/server/project-run-files.js.map +1 -0
- package/dist/server/provider-action-gate.d.ts +5 -0
- package/dist/server/provider-action-gate.js +28 -0
- package/dist/server/provider-action-gate.js.map +1 -0
- package/dist/server/provider-auth-runtime.d.ts +34 -0
- package/dist/server/provider-auth-runtime.js +118 -0
- package/dist/server/provider-auth-runtime.js.map +1 -0
- package/dist/server/repo-branch-forge.d.ts +61 -0
- package/dist/server/repo-branch-forge.js +158 -0
- package/dist/server/repo-branch-forge.js.map +1 -0
- package/dist/server/repo-branch-sweep.d.ts +21 -0
- package/dist/server/repo-branch-sweep.js +64 -0
- package/dist/server/repo-branch-sweep.js.map +1 -0
- package/dist/server/repo-branches.d.ts +103 -0
- package/dist/server/repo-branches.js +804 -0
- package/dist/server/repo-branches.js.map +1 -0
- package/dist/server/repo-pull.d.ts +18 -0
- package/dist/server/repo-pull.js +97 -0
- package/dist/server/repo-pull.js.map +1 -0
- package/dist/server/reports-api.d.ts +90 -0
- package/dist/server/reports-api.js +21 -0
- package/dist/server/reports-api.js.map +1 -0
- package/dist/server/reports-topic.d.ts +5 -0
- package/dist/server/reports-topic.js +37 -0
- package/dist/server/reports-topic.js.map +1 -0
- package/dist/server/run-checkout.d.ts +56 -0
- package/dist/server/run-checkout.js +141 -0
- package/dist/server/run-checkout.js.map +1 -0
- package/dist/server/run-event-feed.d.ts +20 -0
- package/dist/server/run-event-feed.js +203 -0
- package/dist/server/run-event-feed.js.map +1 -0
- package/dist/server/self-update-routes.d.ts +308 -0
- package/dist/server/self-update-routes.js +80 -0
- package/dist/server/self-update-routes.js.map +1 -0
- package/dist/server/server-deps.d.ts +40 -0
- package/dist/server/server-deps.js +132 -0
- package/dist/server/server-deps.js.map +1 -0
- package/dist/server/server.d.ts +35332 -0
- package/dist/server/server.js +5046 -0
- package/dist/server/server.js.map +1 -0
- package/dist/server/skill-sources.d.ts +264 -0
- package/dist/server/skill-sources.js +130 -0
- package/dist/server/skill-sources.js.map +1 -0
- package/dist/server/start-run-options.d.ts +84 -0
- package/dist/server/start-run-options.js +114 -0
- package/dist/server/start-run-options.js.map +1 -0
- package/dist/server/static-ui.d.ts +49 -0
- package/dist/server/static-ui.js +99 -0
- package/dist/server/static-ui.js.map +1 -0
- package/dist/server/upgrade-router.d.ts +23 -0
- package/dist/server/upgrade-router.js +53 -0
- package/dist/server/upgrade-router.js.map +1 -0
- package/dist/server/validators.d.ts +80 -0
- package/dist/server/validators.js +85 -0
- package/dist/server/validators.js.map +1 -0
- package/dist/server/workflow-files.d.ts +9 -0
- package/dist/server/workflow-files.js +27 -0
- package/dist/server/workflow-files.js.map +1 -0
- package/dist/server/workspace-config-patch.d.ts +11 -0
- package/dist/server/workspace-config-patch.js +75 -0
- package/dist/server/workspace-config-patch.js.map +1 -0
- package/dist/server/workspace-live-routes.d.ts +222 -0
- package/dist/server/workspace-live-routes.js +8 -0
- package/dist/server/workspace-live-routes.js.map +1 -0
- package/dist/server/workspace-projects.d.ts +51 -0
- package/dist/server/workspace-projects.js +161 -0
- package/dist/server/workspace-projects.js.map +1 -0
- package/dist/server/workspace-run-feed.d.ts +66 -0
- package/dist/server/workspace-run-feed.js +171 -0
- package/dist/server/workspace-run-feed.js.map +1 -0
- package/dist/server/ws.d.ts +80 -0
- package/dist/server/ws.js +280 -0
- package/dist/server/ws.js.map +1 -0
- package/dist/server/wsl.d.ts +25 -0
- package/dist/server/wsl.js +92 -0
- package/dist/server/wsl.js.map +1 -0
- package/dist/server-command.d.ts +53 -0
- package/dist/server-command.js +180 -0
- package/dist/server-command.js.map +1 -0
- package/dist/server-install/engine.d.ts +51 -0
- package/dist/server-install/engine.js +358 -0
- package/dist/server-install/engine.js.map +1 -0
- package/dist/server-install/group-migrate.d.ts +26 -0
- package/dist/server-install/group-migrate.js +241 -0
- package/dist/server-install/group-migrate.js.map +1 -0
- package/dist/server-install/migrate-cli.d.ts +12 -0
- package/dist/server-install/migrate-cli.js +44 -0
- package/dist/server-install/migrate-cli.js.map +1 -0
- package/dist/server-install/migrate-journal.d.ts +15 -0
- package/dist/server-install/migrate-journal.js +48 -0
- package/dist/server-install/migrate-journal.js.map +1 -0
- package/dist/server-install/migration-report.d.ts +3 -0
- package/dist/server-install/migration-report.js +21 -0
- package/dist/server-install/migration-report.js.map +1 -0
- package/dist/server-install/npx-cache.d.ts +34 -0
- package/dist/server-install/npx-cache.js +142 -0
- package/dist/server-install/npx-cache.js.map +1 -0
- package/dist/server-install/platforms/macosx-launchd-files.d.ts +11 -0
- package/dist/server-install/platforms/macosx-launchd-files.js +60 -0
- package/dist/server-install/platforms/macosx-launchd-files.js.map +1 -0
- package/dist/server-install/platforms/macosx-migration.d.ts +17 -0
- package/dist/server-install/platforms/macosx-migration.js +239 -0
- package/dist/server-install/platforms/macosx-migration.js.map +1 -0
- package/dist/server-install/platforms/macosx-ngrok.d.ts +12 -0
- package/dist/server-install/platforms/macosx-ngrok.js +490 -0
- package/dist/server-install/platforms/macosx-ngrok.js.map +1 -0
- package/dist/server-install/platforms/service-env.d.ts +4 -0
- package/dist/server-install/platforms/service-env.js +17 -0
- package/dist/server-install/platforms/service-env.js.map +1 -0
- package/dist/server-install/platforms/ubuntu-vps-files.d.ts +18 -0
- package/dist/server-install/platforms/ubuntu-vps-files.js +43 -0
- package/dist/server-install/platforms/ubuntu-vps-files.js.map +1 -0
- package/dist/server-install/platforms/ubuntu-vps-migration.d.ts +26 -0
- package/dist/server-install/platforms/ubuntu-vps-migration.js +391 -0
- package/dist/server-install/platforms/ubuntu-vps-migration.js.map +1 -0
- package/dist/server-install/platforms/ubuntu-vps-names.d.ts +30 -0
- package/dist/server-install/platforms/ubuntu-vps-names.js +50 -0
- package/dist/server-install/platforms/ubuntu-vps-names.js.map +1 -0
- package/dist/server-install/platforms/ubuntu-vps-redeploy.d.ts +42 -0
- package/dist/server-install/platforms/ubuntu-vps-redeploy.js +127 -0
- package/dist/server-install/platforms/ubuntu-vps-redeploy.js.map +1 -0
- package/dist/server-install/platforms/ubuntu-vps.d.ts +57 -0
- package/dist/server-install/platforms/ubuntu-vps.js +945 -0
- package/dist/server-install/platforms/ubuntu-vps.js.map +1 -0
- package/dist/server-install/state.d.ts +51 -0
- package/dist/server-install/state.js +178 -0
- package/dist/server-install/state.js.map +1 -0
- package/dist/server-install/steps.d.ts +108 -0
- package/dist/server-install/steps.js +348 -0
- package/dist/server-install/steps.js.map +1 -0
- package/dist/server-install/strategies.d.ts +6 -0
- package/dist/server-install/strategies.js +24 -0
- package/dist/server-install/strategies.js.map +1 -0
- package/dist/server-install/types.d.ts +316 -0
- package/dist/server-install/types.js +118 -0
- package/dist/server-install/types.js.map +1 -0
- package/dist/server-install/ui.d.ts +36 -0
- package/dist/server-install/ui.js +131 -0
- package/dist/server-install/ui.js.map +1 -0
- package/dist/session-context.d.ts +66 -0
- package/dist/session-context.js +266 -0
- package/dist/session-context.js.map +1 -0
- package/dist/skills-provision.d.ts +64 -0
- package/dist/skills-provision.js +325 -0
- package/dist/skills-provision.js.map +1 -0
- package/dist/skills-remote.d.ts +144 -0
- package/dist/skills-remote.js +847 -0
- package/dist/skills-remote.js.map +1 -0
- package/dist/skills.d.ts +81 -0
- package/dist/skills.js +288 -0
- package/dist/skills.js.map +1 -0
- package/dist/subcommands.d.ts +12 -0
- package/dist/subcommands.js +59 -0
- package/dist/subcommands.js.map +1 -0
- package/dist/task-cli/cli.d.ts +39 -0
- package/dist/task-cli/cli.js +773 -0
- package/dist/task-cli/cli.js.map +1 -0
- package/dist/task-cli/discovery.d.ts +32 -0
- package/dist/task-cli/discovery.js +133 -0
- package/dist/task-cli/discovery.js.map +1 -0
- package/dist/task-cli/hint.d.ts +20 -0
- package/dist/task-cli/hint.js +15 -0
- package/dist/task-cli/hint.js.map +1 -0
- package/dist/task-cli/http.d.ts +42 -0
- package/dist/task-cli/http.js +110 -0
- package/dist/task-cli/http.js.map +1 -0
- package/dist/task-cli/projections.d.ts +68 -0
- package/dist/task-cli/projections.js +76 -0
- package/dist/task-cli/projections.js.map +1 -0
- package/dist/task-cli/watch.d.ts +119 -0
- package/dist/task-cli/watch.js +494 -0
- package/dist/task-cli/watch.js.map +1 -0
- package/dist/ui-state.d.ts +41 -0
- package/dist/ui-state.js +93 -0
- package/dist/ui-state.js.map +1 -0
- package/dist/update-check.d.ts +2 -0
- package/dist/update-check.js +28 -0
- package/dist/update-check.js.map +1 -0
- package/dist/use-command-notice.d.ts +1 -0
- package/dist/use-command-notice.js +6 -0
- package/dist/use-command-notice.js.map +1 -0
- package/dist/voice/decide.d.ts +35 -0
- package/dist/voice/decide.js +329 -0
- package/dist/voice/decide.js.map +1 -0
- package/dist/voice/decision-log.d.ts +48 -0
- package/dist/voice/decision-log.js +117 -0
- package/dist/voice/decision-log.js.map +1 -0
- package/dist/voice/engine.d.ts +119 -0
- package/dist/voice/engine.js +61 -0
- package/dist/voice/engine.js.map +1 -0
- package/dist/voice/fake-engine.d.ts +10 -0
- package/dist/voice/fake-engine.js +192 -0
- package/dist/voice/fake-engine.js.map +1 -0
- package/dist/voice/jev-engine.d.ts +17 -0
- package/dist/voice/jev-engine.js +110 -0
- package/dist/voice/jev-engine.js.map +1 -0
- package/dist/voice/pending-ask.d.ts +17 -0
- package/dist/voice/pending-ask.js +51 -0
- package/dist/voice/pending-ask.js.map +1 -0
- package/dist/voice/routes.d.ts +115 -0
- package/dist/voice/routes.js +92 -0
- package/dist/voice/routes.js.map +1 -0
- package/dist/voice/sentence.d.ts +21 -0
- package/dist/voice/sentence.js +61 -0
- package/dist/voice/sentence.js.map +1 -0
- package/dist/voice/thresholds.d.ts +28 -0
- package/dist/voice/thresholds.js +29 -0
- package/dist/voice/thresholds.js.map +1 -0
- package/dist/webhooks/cli.d.ts +18 -0
- package/dist/webhooks/cli.js +127 -0
- package/dist/webhooks/cli.js.map +1 -0
- package/dist/webhooks/prompt.d.ts +5 -0
- package/dist/webhooks/prompt.js +26 -0
- package/dist/webhooks/prompt.js.map +1 -0
- package/dist/workflows/active-run.d.ts +46 -0
- package/dist/workflows/active-run.js +46 -0
- package/dist/workflows/active-run.js.map +1 -0
- package/dist/workflows/attachment-path.d.ts +6 -0
- package/dist/workflows/attachment-path.js +40 -0
- package/dist/workflows/attachment-path.js.map +1 -0
- package/dist/workflows/completion-policy.d.ts +98 -0
- package/dist/workflows/completion-policy.js +77 -0
- package/dist/workflows/completion-policy.js.map +1 -0
- package/dist/workflows/continuation-admission.d.ts +1229 -0
- package/dist/workflows/continuation-admission.js +277 -0
- package/dist/workflows/continuation-admission.js.map +1 -0
- package/dist/workflows/execution-account.d.ts +13 -0
- package/dist/workflows/execution-account.js +18 -0
- package/dist/workflows/execution-account.js.map +1 -0
- package/dist/workflows/grouped-command.d.ts +31 -0
- package/dist/workflows/grouped-command.js +147 -0
- package/dist/workflows/grouped-command.js.map +1 -0
- package/dist/workflows/input-acknowledgement.d.ts +233 -0
- package/dist/workflows/input-acknowledgement.js +41 -0
- package/dist/workflows/input-acknowledgement.js.map +1 -0
- package/dist/workflows/line-order.d.ts +5 -0
- package/dist/workflows/line-order.js +25 -0
- package/dist/workflows/line-order.js.map +1 -0
- package/dist/workflows/load.d.ts +19 -0
- package/dist/workflows/load.js +74 -0
- package/dist/workflows/load.js.map +1 -0
- package/dist/workflows/outcome-batches.d.ts +32 -0
- package/dist/workflows/outcome-batches.js +55 -0
- package/dist/workflows/outcome-batches.js.map +1 -0
- package/dist/workflows/pasted-content.d.ts +60 -0
- package/dist/workflows/pasted-content.js +69 -0
- package/dist/workflows/pasted-content.js.map +1 -0
- package/dist/workflows/question-routing.d.ts +16 -0
- package/dist/workflows/question-routing.js +28 -0
- package/dist/workflows/question-routing.js.map +1 -0
- package/dist/workflows/run/attachment-writer.d.ts +34 -0
- package/dist/workflows/run/attachment-writer.js +98 -0
- package/dist/workflows/run/attachment-writer.js.map +1 -0
- package/dist/workflows/run/auto-resume.d.ts +91 -0
- package/dist/workflows/run/auto-resume.js +247 -0
- package/dist/workflows/run/auto-resume.js.map +1 -0
- package/dist/workflows/run/cezar-tools.d.ts +45 -0
- package/dist/workflows/run/cezar-tools.js +117 -0
- package/dist/workflows/run/cezar-tools.js.map +1 -0
- package/dist/workflows/run/completion-gate.d.ts +77 -0
- package/dist/workflows/run/completion-gate.js +93 -0
- package/dist/workflows/run/completion-gate.js.map +1 -0
- package/dist/workflows/run/compose.d.ts +181 -0
- package/dist/workflows/run/compose.js +413 -0
- package/dist/workflows/run/compose.js.map +1 -0
- package/dist/workflows/run/constants.d.ts +48 -0
- package/dist/workflows/run/constants.js +49 -0
- package/dist/workflows/run/constants.js.map +1 -0
- package/dist/workflows/run/continuation-acceptance.d.ts +70 -0
- package/dist/workflows/run/continuation-acceptance.js +188 -0
- package/dist/workflows/run/continuation-acceptance.js.map +1 -0
- package/dist/workflows/run/continuation-session.d.ts +130 -0
- package/dist/workflows/run/continuation-session.js +597 -0
- package/dist/workflows/run/continuation-session.js.map +1 -0
- package/dist/workflows/run/drop-active.d.ts +4 -0
- package/dist/workflows/run/drop-active.js +11 -0
- package/dist/workflows/run/drop-active.js.map +1 -0
- package/dist/workflows/run/family-reconciler.d.ts +78 -0
- package/dist/workflows/run/family-reconciler.js +308 -0
- package/dist/workflows/run/family-reconciler.js.map +1 -0
- package/dist/workflows/run/human-waits.d.ts +85 -0
- package/dist/workflows/run/human-waits.js +163 -0
- package/dist/workflows/run/human-waits.js.map +1 -0
- package/dist/workflows/run/inbox-claims.d.ts +38 -0
- package/dist/workflows/run/inbox-claims.js +108 -0
- package/dist/workflows/run/inbox-claims.js.map +1 -0
- package/dist/workflows/run/input-batch.d.ts +15 -0
- package/dist/workflows/run/input-batch.js +32 -0
- package/dist/workflows/run/input-batch.js.map +1 -0
- package/dist/workflows/run/input-delivery.d.ts +202 -0
- package/dist/workflows/run/input-delivery.js +818 -0
- package/dist/workflows/run/input-delivery.js.map +1 -0
- package/dist/workflows/run/input-receipts.d.ts +65 -0
- package/dist/workflows/run/input-receipts.js +221 -0
- package/dist/workflows/run/input-receipts.js.map +1 -0
- package/dist/workflows/run/input-timers.d.ts +10 -0
- package/dist/workflows/run/input-timers.js +32 -0
- package/dist/workflows/run/input-timers.js.map +1 -0
- package/dist/workflows/run/launch-context.d.ts +62 -0
- package/dist/workflows/run/launch-context.js +119 -0
- package/dist/workflows/run/launch-context.js.map +1 -0
- package/dist/workflows/run/launch-read-resolver.d.ts +510 -0
- package/dist/workflows/run/launch-read-resolver.js +658 -0
- package/dist/workflows/run/launch-read-resolver.js.map +1 -0
- package/dist/workflows/run/launch-settings.d.ts +10 -0
- package/dist/workflows/run/launch-settings.js +11 -0
- package/dist/workflows/run/launch-settings.js.map +1 -0
- package/dist/workflows/run/live-session-state.d.ts +49 -0
- package/dist/workflows/run/live-session-state.js +83 -0
- package/dist/workflows/run/live-session-state.js.map +1 -0
- package/dist/workflows/run/maintenance-claims.d.ts +47 -0
- package/dist/workflows/run/maintenance-claims.js +83 -0
- package/dist/workflows/run/maintenance-claims.js.map +1 -0
- package/dist/workflows/run/memory-guard.d.ts +25 -0
- package/dist/workflows/run/memory-guard.js +64 -0
- package/dist/workflows/run/memory-guard.js.map +1 -0
- package/dist/workflows/run/opening.d.ts +30 -0
- package/dist/workflows/run/opening.js +6 -0
- package/dist/workflows/run/opening.js.map +1 -0
- package/dist/workflows/run/pending-ask-facts.d.ts +10 -0
- package/dist/workflows/run/pending-ask-facts.js +19 -0
- package/dist/workflows/run/pending-ask-facts.js.map +1 -0
- package/dist/workflows/run/prompts.d.ts +74 -0
- package/dist/workflows/run/prompts.js +140 -0
- package/dist/workflows/run/prompts.js.map +1 -0
- package/dist/workflows/run/queued-input.d.ts +108 -0
- package/dist/workflows/run/queued-input.js +249 -0
- package/dist/workflows/run/queued-input.js.map +1 -0
- package/dist/workflows/run/queued-revival.d.ts +44 -0
- package/dist/workflows/run/queued-revival.js +124 -0
- package/dist/workflows/run/queued-revival.js.map +1 -0
- package/dist/workflows/run/readonly-collections.d.ts +24 -0
- package/dist/workflows/run/readonly-collections.js +33 -0
- package/dist/workflows/run/readonly-collections.js.map +1 -0
- package/dist/workflows/run/repo-root-lease.d.ts +34 -0
- package/dist/workflows/run/repo-root-lease.js +71 -0
- package/dist/workflows/run/repo-root-lease.js.map +1 -0
- package/dist/workflows/run/resolved-wait-input-batch.d.ts +7 -0
- package/dist/workflows/run/resolved-wait-input-batch.js +23 -0
- package/dist/workflows/run/resolved-wait-input-batch.js.map +1 -0
- package/dist/workflows/run/run-cancel.d.ts +36 -0
- package/dist/workflows/run/run-cancel.js +130 -0
- package/dist/workflows/run/run-cancel.js.map +1 -0
- package/dist/workflows/run/run-finish.d.ts +72 -0
- package/dist/workflows/run/run-finish.js +256 -0
- package/dist/workflows/run/run-finish.js.map +1 -0
- package/dist/workflows/run/run-guards.d.ts +15 -0
- package/dist/workflows/run/run-guards.js +39 -0
- package/dist/workflows/run/run-guards.js.map +1 -0
- package/dist/workflows/run/run-line.d.ts +187 -0
- package/dist/workflows/run/run-line.js +438 -0
- package/dist/workflows/run/run-line.js.map +1 -0
- package/dist/workflows/run/run-recovery.d.ts +116 -0
- package/dist/workflows/run/run-recovery.js +422 -0
- package/dist/workflows/run/run-recovery.js.map +1 -0
- package/dist/workflows/run/run-settlement.d.ts +177 -0
- package/dist/workflows/run/run-settlement.js +504 -0
- package/dist/workflows/run/run-settlement.js.map +1 -0
- package/dist/workflows/run/run-skills.d.ts +114 -0
- package/dist/workflows/run/run-skills.js +233 -0
- package/dist/workflows/run/run-skills.js.map +1 -0
- package/dist/workflows/run/run-start.d.ts +64 -0
- package/dist/workflows/run/run-start.js +126 -0
- package/dist/workflows/run/run-start.js.map +1 -0
- package/dist/workflows/run/run-workspace.d.ts +25 -0
- package/dist/workflows/run/run-workspace.js +66 -0
- package/dist/workflows/run/run-workspace.js.map +1 -0
- package/dist/workflows/run/session-events.d.ts +155 -0
- package/dist/workflows/run/session-events.js +282 -0
- package/dist/workflows/run/session-events.js.map +1 -0
- package/dist/workflows/run/step-decisions.d.ts +37 -0
- package/dist/workflows/run/step-decisions.js +43 -0
- package/dist/workflows/run/step-decisions.js.map +1 -0
- package/dist/workflows/run/step-loop.d.ts +143 -0
- package/dist/workflows/run/step-loop.js +488 -0
- package/dist/workflows/run/step-loop.js.map +1 -0
- package/dist/workflows/run/step-session.d.ts +65 -0
- package/dist/workflows/run/step-session.js +280 -0
- package/dist/workflows/run/step-session.js.map +1 -0
- package/dist/workflows/run/steps.d.ts +13 -0
- package/dist/workflows/run/steps.js +14 -0
- package/dist/workflows/run/steps.js.map +1 -0
- package/dist/workflows/run/subagent-waits.d.ts +138 -0
- package/dist/workflows/run/subagent-waits.js +305 -0
- package/dist/workflows/run/subagent-waits.js.map +1 -0
- package/dist/workflows/run/tracked-work.d.ts +12 -0
- package/dist/workflows/run/tracked-work.js +29 -0
- package/dist/workflows/run/tracked-work.js.map +1 -0
- package/dist/workflows/run/turn-bookkeeping.d.ts +67 -0
- package/dist/workflows/run/turn-bookkeeping.js +173 -0
- package/dist/workflows/run/turn-bookkeeping.js.map +1 -0
- package/dist/workflows/run/turn-boundary.d.ts +215 -0
- package/dist/workflows/run/turn-boundary.js +329 -0
- package/dist/workflows/run/turn-boundary.js.map +1 -0
- package/dist/workflows/run/turn-markers.d.ts +62 -0
- package/dist/workflows/run/turn-markers.js +86 -0
- package/dist/workflows/run/turn-markers.js.map +1 -0
- package/dist/workflows/run/turn-nudges.d.ts +104 -0
- package/dist/workflows/run/turn-nudges.js +244 -0
- package/dist/workflows/run/turn-nudges.js.map +1 -0
- package/dist/workflows/run/turn-settler.d.ts +161 -0
- package/dist/workflows/run/turn-settler.js +469 -0
- package/dist/workflows/run/turn-settler.js.map +1 -0
- package/dist/workflows/run/types.d.ts +293 -0
- package/dist/workflows/run/types.js +2 -0
- package/dist/workflows/run/types.js.map +1 -0
- package/dist/workflows/run/usage-accounting.d.ts +49 -0
- package/dist/workflows/run/usage-accounting.js +213 -0
- package/dist/workflows/run/usage-accounting.js.map +1 -0
- package/dist/workflows/run/wait-engine.d.ts +1210 -0
- package/dist/workflows/run/wait-engine.js +456 -0
- package/dist/workflows/run/wait-engine.js.map +1 -0
- package/dist/workflows/run/wait-kinds.d.ts +336 -0
- package/dist/workflows/run/wait-kinds.js +471 -0
- package/dist/workflows/run/wait-kinds.js.map +1 -0
- package/dist/workflows/run/wait-registration-tool.d.ts +3 -0
- package/dist/workflows/run/wait-registration-tool.js +68 -0
- package/dist/workflows/run/wait-registration-tool.js.map +1 -0
- package/dist/workflows/run/wait-registration.d.ts +7 -0
- package/dist/workflows/run/wait-registration.js +39 -0
- package/dist/workflows/run/wait-registration.js.map +1 -0
- package/dist/workflows/run/wait-registry.d.ts +120 -0
- package/dist/workflows/run/wait-registry.js +365 -0
- package/dist/workflows/run/wait-registry.js.map +1 -0
- package/dist/workflows/run/wait-watches.d.ts +13 -0
- package/dist/workflows/run/wait-watches.js +12 -0
- package/dist/workflows/run/wait-watches.js.map +1 -0
- package/dist/workflows/run/wake-admission.d.ts +150 -0
- package/dist/workflows/run/wake-admission.js +397 -0
- package/dist/workflows/run/wake-admission.js.map +1 -0
- package/dist/workflows/run/wake-ledger.d.ts +61 -0
- package/dist/workflows/run/wake-ledger.js +78 -0
- package/dist/workflows/run/wake-ledger.js.map +1 -0
- package/dist/workflows/run/wake-retries.d.ts +31 -0
- package/dist/workflows/run/wake-retries.js +93 -0
- package/dist/workflows/run/wake-retries.js.map +1 -0
- package/dist/workflows/run/worker-executions.d.ts +54 -0
- package/dist/workflows/run/worker-executions.js +149 -0
- package/dist/workflows/run/worker-executions.js.map +1 -0
- package/dist/workflows/run/worker-orphans.d.ts +91 -0
- package/dist/workflows/run/worker-orphans.js +275 -0
- package/dist/workflows/run/worker-orphans.js.map +1 -0
- package/dist/workflows/run/worker-questions.d.ts +169 -0
- package/dist/workflows/run/worker-questions.js +464 -0
- package/dist/workflows/run/worker-questions.js.map +1 -0
- package/dist/workflows/run/workflow-position.d.ts +7 -0
- package/dist/workflows/run/workflow-position.js +9 -0
- package/dist/workflows/run/workflow-position.js.map +1 -0
- package/dist/workflows/run/worktree-autosave.d.ts +48 -0
- package/dist/workflows/run/worktree-autosave.js +175 -0
- package/dist/workflows/run/worktree-autosave.js.map +1 -0
- package/dist/workflows/run-scheduling.d.ts +38 -0
- package/dist/workflows/run-scheduling.js +76 -0
- package/dist/workflows/run-scheduling.js.map +1 -0
- package/dist/workflows/run-states.d.ts +73 -0
- package/dist/workflows/run-states.js +233 -0
- package/dist/workflows/run-states.js.map +1 -0
- package/dist/workflows/run.d.ts +275 -0
- package/dist/workflows/run.js +626 -0
- package/dist/workflows/run.js.map +1 -0
- package/dist/workflows/turn-end.d.ts +119 -0
- package/dist/workflows/turn-end.js +74 -0
- package/dist/workflows/turn-end.js.map +1 -0
- package/dist/workflows/types.d.ts +83 -0
- package/dist/workflows/types.js +192 -0
- package/dist/workflows/types.js.map +1 -0
- package/dist/workflows/wait-scope.d.ts +75 -0
- package/dist/workflows/wait-scope.js +115 -0
- package/dist/workflows/wait-scope.js.map +1 -0
- package/dist/workflows/waits.d.ts +58 -0
- package/dist/workflows/waits.js +89 -0
- package/dist/workflows/waits.js.map +1 -0
- package/dist/workspace/agent-accounts.d.ts +135 -0
- package/dist/workspace/agent-accounts.js +353 -0
- package/dist/workspace/agent-accounts.js.map +1 -0
- package/dist/workspace/agent-profiles.d.ts +125 -0
- package/dist/workspace/agent-profiles.js +190 -0
- package/dist/workspace/agent-profiles.js.map +1 -0
- package/dist/workspace/backup-cli.d.ts +9 -0
- package/dist/workspace/backup-cli.js +147 -0
- package/dist/workspace/backup-cli.js.map +1 -0
- package/dist/workspace/backups.d.ts +36 -0
- package/dist/workspace/backups.js +249 -0
- package/dist/workspace/backups.js.map +1 -0
- package/dist/workspace/config.d.ts +179 -0
- package/dist/workspace/config.js +396 -0
- package/dist/workspace/config.js.map +1 -0
- package/dist/workspace/migrations.d.ts +37 -0
- package/dist/workspace/migrations.js +149 -0
- package/dist/workspace/migrations.js.map +1 -0
- package/dist/workspace/projects-cli.d.ts +27 -0
- package/dist/workspace/projects-cli.js +175 -0
- package/dist/workspace/projects-cli.js.map +1 -0
- package/dist/workspace/projects.d.ts +131 -0
- package/dist/workspace/projects.js +274 -0
- package/dist/workspace/projects.js.map +1 -0
- package/dist/workspace/root-probe-cache.d.ts +10 -0
- package/dist/workspace/root-probe-cache.js +43 -0
- package/dist/workspace/root-probe-cache.js.map +1 -0
- package/dist/workspace/semaphore.d.ts +147 -0
- package/dist/workspace/semaphore.js +196 -0
- package/dist/workspace/semaphore.js.map +1 -0
- package/dist/workspace/ui-state.d.ts +17 -0
- package/dist/workspace/ui-state.js +59 -0
- package/dist/workspace/ui-state.js.map +1 -0
- package/dist/workspace/webhooks.d.ts +40 -0
- package/dist/workspace/webhooks.js +146 -0
- package/dist/workspace/webhooks.js.map +1 -0
- package/dist/worktree-manifest.d.ts +165 -0
- package/dist/worktree-manifest.js +535 -0
- package/dist/worktree-manifest.js.map +1 -0
- package/dist/worktree-setup.d.ts +88 -0
- package/dist/worktree-setup.js +170 -0
- package/dist/worktree-setup.js.map +1 -0
- package/package.json +80 -4
- package/scripts/benchmark-family-reconcile.ts +119 -0
- package/scripts/benchmark-home.ts +32 -0
- package/scripts/benchmark-project-probes.mjs +158 -0
- package/scripts/benchmark-run-store-fixture.ts +432 -0
- package/scripts/benchmark-run-store.ts +767 -0
- package/scripts/benchmark-stuck-destroys.ts +144 -0
- package/scripts/benchmark-workspace-events.ts +339 -0
- package/scripts/check-pack.mjs +48 -0
- package/scripts/inline-contract.mjs +165 -0
- package/scripts/install-as-command.mjs +125 -0
- package/scripts/measure-transcript-facts-health.mjs +103 -0
- package/scripts/measure-transcript-facts-lag.mjs +69 -0
- package/scripts/mock-autonomous.mjs +13 -0
- package/scripts/mock-ci-tool.mjs +159 -0
- package/scripts/mock-claude-model-refusal.mjs +76 -0
- package/scripts/mock-claude-subagents.mjs +54 -0
- package/scripts/mock-claude.mjs +800 -0
- package/scripts/mock-codex-app-server.mjs +767 -0
- package/scripts/mock-codex-coalesce-steer.mjs +59 -0
- package/scripts/mock-codex-reverse-steer.mjs +143 -0
- package/scripts/mock-codex-subagents.mjs +52 -0
- package/scripts/mock-controls.d.mts +11 -0
- package/scripts/mock-controls.mjs +13 -0
- package/scripts/mock-cursor-hang-stdin-late.mjs +35 -0
- package/scripts/mock-cursor-hang-stdin.mjs +30 -0
- package/scripts/mock-cursor-print.mjs +237 -0
- package/scripts/mock-deferred-turn-start.mjs +16 -0
- package/scripts/mock-delivery-control.d.mts +23 -0
- package/scripts/mock-delivery-control.mjs +80 -0
- package/scripts/mock-env-record.mjs +11 -0
- package/scripts/mock-failure-control.d.mts +13 -0
- package/scripts/mock-failure-control.mjs +68 -0
- package/scripts/mock-lifecycle.mjs +51 -0
- package/scripts/mock-multi-pr.mjs +10 -0
- package/scripts/mock-nudge.mjs +43 -0
- package/scripts/mock-omp-rpc.mjs +536 -0
- package/scripts/mock-opencode-serve.mjs +1000 -0
- package/scripts/mock-pi-rpc.mjs +421 -0
- package/scripts/mock-publish.mjs +13 -0
- package/scripts/mock-runner-crash.mjs +64 -0
- package/scripts/mock-scenario.d.mts +71 -0
- package/scripts/mock-scenario.mjs +295 -0
- package/scripts/mock-session-context.d.mts +5 -0
- package/scripts/mock-session-context.mjs +56 -0
- package/scripts/mock-silent-tail.mjs +93 -0
- package/scripts/mock-turn-messages.mjs +5 -0
- package/scripts/mock-wire-evidence.config.mjs +12 -0
- package/scripts/mock-wire-recorder.mjs +66 -0
- package/scripts/mock-wire-tools.mjs +7 -0
- package/scripts/omp-ci-wait.mjs +29 -0
- package/scripts/opencode-task-scope.d.mts +18 -0
- package/scripts/opencode-task-scope.mjs +59 -0
- package/scripts/parity-rows.ts +160 -0
- package/scripts/pi-ci-wait.mjs +17 -0
- package/scripts/pi-default-tools.mjs +22 -0
- package/scripts/pi-project-skills.mjs +59 -0
- package/scripts/pi-retry-degraded-service.mjs +16 -0
- package/scripts/probe-cursor-print.mjs +699 -0
- package/scripts/probe-session-env.ts +8 -0
- package/scripts/product-env.mjs +6 -0
- package/scripts/product-identity.mjs +12 -0
- package/scripts/record-mock-wires.mjs +16 -0
- package/scripts/seed-archive-profile.ts +53 -0
- package/scripts/sync-readme.mjs +20 -0
- package/scripts/test-process-usage.mjs +47 -0
- package/scripts/transcript-facts-fixture.mjs +53 -0
- package/scripts/transcript-facts-timing.mjs +42 -0
- package/scripts/voice-replay.ts +50 -0
- package/scripts/windows-workspace-identity.cs +208 -0
- package/scripts/windows-workspace-identity.ps1 +10 -0
- package/web/dist/assets/activity-motion-0wHCeUs7.css +1 -0
- package/web/dist/assets/activity-motion-SGth5vR4.js +2 -0
- package/web/dist/assets/activity-route-D8otXwOA.js +1 -0
- package/web/dist/assets/agent-accounts-6EuZhy-N.js +18 -0
- package/web/dist/assets/alert-dialog-vFjLgfK4.js +1 -0
- package/web/dist/assets/app-window-CSHwDX9N.js +1 -0
- package/web/dist/assets/arrow-right-Dh_Lx3um.js +1 -0
- package/web/dist/assets/bundle-mjs-BT31bpU6.js +1 -0
- package/web/dist/assets/button-BEK7XKhS.js +3 -0
- package/web/dist/assets/centered-state-CPgI8cCO.js +1 -0
- package/web/dist/assets/cez-idle-BT0U4GN_.js +1 -0
- package/web/dist/assets/chunk-BO2N2NFS-DE6qKn3r.js +131 -0
- package/web/dist/assets/commit-diff-panel-DzyWgn1Z.js +1 -0
- package/web/dist/assets/control-locations-CexZsd3M.js +1 -0
- package/web/dist/assets/core-BCsw8oQw.js +12 -0
- package/web/dist/assets/createLucideIcon-BHp8sXmF.js +1 -0
- package/web/dist/assets/css-BsVw1vtW.js +1 -0
- package/web/dist/assets/design-icons-DmYTv2r1.js +1 -0
- package/web/dist/assets/diff-DBDiZl58.js +3 -0
- package/web/dist/assets/diff-controls-DXyB0aDl.js +1 -0
- package/web/dist/assets/diff-view-DiKAwVMH.js +11 -0
- package/web/dist/assets/diff-woXpYk--.js +1 -0
- package/web/dist/assets/dropdown-menu-DYflHa4m.js +1 -0
- package/web/dist/assets/engine-javascript-FQvzRocB.js +141 -0
- package/web/dist/assets/es2015-BO600Xfi.js +41 -0
- package/web/dist/assets/eye-B8okNWXU.js +1 -0
- package/web/dist/assets/file-preview-DTSRkmMo.js +2 -0
- package/web/dist/assets/git-toolbar-U4s46kUd.js +1 -0
- package/web/dist/assets/github-UaBp2yRF.js +1 -0
- package/web/dist/assets/github-sidebar-model-czjLsgVJ.js +1 -0
- package/web/dist/assets/global-activity-route-B2N2Nz0H.js +1 -0
- package/web/dist/assets/go-rLFTqkRN.js +1 -0
- package/web/dist/assets/highlighted-body-OFNGDK62-BL-MfFgl.js +1 -0
- package/web/dist/assets/hourglass-B0lPAJvT.js +1 -0
- package/web/dist/assets/html-BY76lXLH.js +1 -0
- package/web/dist/assets/image-preview-XPCPclpG.js +1 -0
- package/web/dist/assets/index-B18AqW0Z.css +1 -0
- package/web/dist/assets/index-xj-3O89_.js +14 -0
- package/web/dist/assets/index.global-D3Ga_D3Y.js +1 -0
- package/web/dist/assets/info-DA1x_4bz.js +1 -0
- package/web/dist/assets/javascript-BgS3c2Ky.js +1 -0
- package/web/dist/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- package/web/dist/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- package/web/dist/assets/json-qhed-kSA.js +1 -0
- package/web/dist/assets/jsonc-CYpm1nAK.js +1 -0
- package/web/dist/assets/jsx-Bz0zcwM4.js +1 -0
- package/web/dist/assets/lib-BxQXEXDF.js +1 -0
- package/web/dist/assets/live-protocol-BDY7JJpT.js +64 -0
- package/web/dist/assets/live-refetch-C-Ony2Yr.js +1 -0
- package/web/dist/assets/live-worker-BWdeGzQX.ts +34 -0
- package/web/dist/assets/loader-circle-CZkGEdoI.js +1 -0
- package/web/dist/assets/markdown-BBDDg-4t.js +1 -0
- package/web/dist/assets/markdown-BYOwaDjH.js +1 -0
- package/web/dist/assets/markdown-UdZ_toya.js +6 -0
- package/web/dist/assets/mermaid-GHXKKRXX-C4jxqUUb.js +1 -0
- package/web/dist/assets/mermaid.min-3O9YjB2l.js +1 -0
- package/web/dist/assets/message-square-B-MD-8rD.js +1 -0
- package/web/dist/assets/nav-row-styles-BE99gD3_.js +1 -0
- package/web/dist/assets/popover-CpULuQSm.js +1 -0
- package/web/dist/assets/poppins-devanagari-400-normal-CJDn6rn8.woff2 +0 -0
- package/web/dist/assets/poppins-devanagari-400-normal-CqVvlrh5.woff +0 -0
- package/web/dist/assets/poppins-devanagari-500-normal-BIdkeU1p.woff2 +0 -0
- package/web/dist/assets/poppins-devanagari-500-normal-DMPDjHtT.woff +0 -0
- package/web/dist/assets/poppins-devanagari-600-normal-ClASKHrr.woff +0 -0
- package/web/dist/assets/poppins-devanagari-600-normal-STEjXBNN.woff2 +0 -0
- package/web/dist/assets/poppins-latin-400-normal-BOb3E3N0.woff +0 -0
- package/web/dist/assets/poppins-latin-400-normal-cpxAROuN.woff2 +0 -0
- package/web/dist/assets/poppins-latin-500-normal-C8OXljZJ.woff2 +0 -0
- package/web/dist/assets/poppins-latin-500-normal-DGXqpDMm.woff +0 -0
- package/web/dist/assets/poppins-latin-600-normal-BJdTmd5m.woff +0 -0
- package/web/dist/assets/poppins-latin-600-normal-zEkxB9Mr.woff2 +0 -0
- package/web/dist/assets/poppins-latin-ext-400-normal-DaBSavcJ.woff +0 -0
- package/web/dist/assets/poppins-latin-ext-400-normal-by3JarPu.woff2 +0 -0
- package/web/dist/assets/poppins-latin-ext-500-normal-CK-6C4Hw.woff2 +0 -0
- package/web/dist/assets/poppins-latin-ext-500-normal-CgAe2rWW.woff +0 -0
- package/web/dist/assets/poppins-latin-ext-600-normal-CAhIAdZj.woff2 +0 -0
- package/web/dist/assets/poppins-latin-ext-600-normal-Df5ffKXP.woff +0 -0
- package/web/dist/assets/preview-pane-connected-CgRK7GzB.js +3 -0
- package/web/dist/assets/project-router-CimAoN1H.js +1 -0
- package/web/dist/assets/project-scope-context-DMpvJqIm.js +4 -0
- package/web/dist/assets/python-gzcpVVnB.js +1 -0
- package/web/dist/assets/queries-BIOp8jLx.js +1 -0
- package/web/dist/assets/react-runtime-CCIEwYL0.js +9 -0
- package/web/dist/assets/repo-git-DVXNOJDT.css +1 -0
- package/web/dist/assets/repo-git-XkyTfP9G.js +1 -0
- package/web/dist/assets/report-reader-CZjYS5Aw.js +1 -0
- package/web/dist/assets/report-title-D4Mbu2Er.js +1 -0
- package/web/dist/assets/reports-B7QAzibP.css +1 -0
- package/web/dist/assets/reports-D_2sFE1y.js +1 -0
- package/web/dist/assets/rolldown-runtime-QTnfLwEv.js +1 -0
- package/web/dist/assets/run-header-BwPDVPpg.js +2 -0
- package/web/dist/assets/run-header-CC7VUTg9.css +1 -0
- package/web/dist/assets/rust-Cfkwpbl8.js +1 -0
- package/web/dist/assets/shellscript-CLZ0U2zV.js +1 -0
- package/web/dist/assets/skill-empty-hint-YDmkqgo-.js +1 -0
- package/web/dist/assets/skills-BkXe4p4q.js +1 -0
- package/web/dist/assets/skills-DFBiAuJG.js +1 -0
- package/web/dist/assets/skills-workflows-C21NystW.css +1 -0
- package/web/dist/assets/sortable.esm-zp_qkYGV.js +5 -0
- package/web/dist/assets/sql-BsFa4tDR.js +1 -0
- package/web/dist/assets/tab-link-BIweqlIZ.js +1 -0
- package/web/dist/assets/task-changes-USNHNm_T.js +1 -0
- package/web/dist/assets/task-commits-kHqlOJe_.js +1 -0
- package/web/dist/assets/task-files-wTWwLR39.js +1 -0
- package/web/dist/assets/task-github-item-GzaQjkF1.js +1 -0
- package/web/dist/assets/task-thread-BZX863UN.js +9 -0
- package/web/dist/assets/task-thread-DtQ6uGfQ.css +1 -0
- package/web/dist/assets/textarea-FAUkHz1K.js +1 -0
- package/web/dist/assets/thread-state-CBzx2hmX.js +4 -0
- package/web/dist/assets/toaster-pthKGWiE.js +1 -0
- package/web/dist/assets/toml-CcmNWLt0.js +1 -0
- package/web/dist/assets/tsx-udAQXfEw.js +1 -0
- package/web/dist/assets/typescript-bsJCZSQ-.js +1 -0
- package/web/dist/assets/use-hand-to-agent-state-Bav9eT3b.js +1 -0
- package/web/dist/assets/use-hand-to-agent-state-C7esPJ-N.css +1 -0
- package/web/dist/assets/useInfiniteQuery-CQI4BO4C.js +1 -0
- package/web/dist/assets/useQuery-Bkq-x4Id.js +1 -0
- package/web/dist/assets/workflows-D871Gx--.js +7 -0
- package/web/dist/assets/ws-DspW9wBH.js +1 -0
- package/web/dist/assets/yaml-rwi0_p6S.js +1 -0
- package/web/dist/assets/zoomable-image-DNc6ORaF.js +1 -0
- package/web/dist/cezarion-lockup-dark.svg +9 -0
- package/web/dist/cezarion-lockup-light.svg +9 -0
- package/web/dist/cezarion-mark-dark.svg +4 -0
- package/web/dist/cezarion-mark-light.svg +4 -0
- package/web/dist/index.html +90 -0
- package/web/dist/live-worker.js +4 -0
|
@@ -0,0 +1,3516 @@
|
|
|
1
|
+
import { legacyIndexRows } from './legacy-run-rows.js';
|
|
2
|
+
import { dataReadDirs } from '../project-paths.js';
|
|
3
|
+
import { addRunSource, copyFallbackFamily, fallbackRunSource, bindWorkerResultPath, fallbackRunDeleteError, workerResultsDirectory } from './project-run-sources.js';
|
|
4
|
+
import { productName, logPrefix } from '../home/display.js';
|
|
5
|
+
import { productEnv } from '../core/product-env.js';
|
|
6
|
+
import { registeredWaitRecord, registeredReceiptRecord } from './wait-registration-write.js';
|
|
7
|
+
import { reportsIndex } from '../artifacts/reports-index.js';
|
|
8
|
+
import { stageCommit, stageLoaded, stageDurableUpdate, stageDebouncedUpdate, stageConversation, stageArchiveFields, stagePullRequests, stageStepUpdate, redactPatch, redactText, clearPin } from './record-write-rules.js';
|
|
9
|
+
import { workerDeletionProof } from './worker-deletion-proof.js';
|
|
10
|
+
import { workerResourceAbsentHolders, workerResourceAuthorized, workerResourceLiveHolders, workerResourcePathsGone } from './worker-resource-holders.js';
|
|
11
|
+
import { listPeerTaskSummaries, parseStoredSummary } from './run-summary-page.js';
|
|
12
|
+
export { parseStoredSummary } from './run-summary-page.js';
|
|
13
|
+
import { defined } from '../core/defined.js';
|
|
14
|
+
import { autosaveCleanupBlocker, clearAutosaveCleanup } from '../autosave-cleanup.js';
|
|
15
|
+
import { EventEmitter } from 'node:events';
|
|
16
|
+
import { workerEvidenceRunIds, workerExecutionSchema } from './worker-execution.js';
|
|
17
|
+
import { agentTmpDirLocations, agentTmpDirMayExist, agentTmpDirOwnershipProven, removeAgentTmpDir } from './agent-tmpdir.js';
|
|
18
|
+
import { removeArtifacts } from '../artifacts/lifecycle.js';
|
|
19
|
+
import { randomUUID, createHash } from 'node:crypto';
|
|
20
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
21
|
+
import { appendFileSync, closeSync, constants, fstatSync, fsyncSync, lstatSync, openSync, readSync, realpathSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
22
|
+
import { join, resolve } from 'node:path';
|
|
23
|
+
import { z } from 'zod';
|
|
24
|
+
import { agentInputSchema, inboxClaimSchema, delegationStateSchema, workerResultFileSchema, workerDestroyRetrySchema, } from '../contract/index.js';
|
|
25
|
+
import { automationIdOf, stepStateSchema as contractStepStateSchema, queuedMessageSchema, toRunSummary, runRecordSchema as contractRunRecordSchema, runPullRequestSchema, mergedPullRequestSchema, } from '../contract/index.js';
|
|
26
|
+
import { previewServerSchema } from '../contract/index.js';
|
|
27
|
+
import { isUsageLimitHold, waitSchema, } from '../contract/index.js';
|
|
28
|
+
import { UNREAD_RUN_RULE, SWEEPABLE_RUN_RULE, RETENTION_RUN_RULE, REFERENCED_RUN_RULE } from '../contract/index.js';
|
|
29
|
+
import { matchesRunSearch, sqlPrefilterTokens } from '../contract/index.js';
|
|
30
|
+
import { storedDelegationStateSchema } from './delegation-state.js';
|
|
31
|
+
import { HistoryCompressor } from './history-compressor.js';
|
|
32
|
+
import { LiveItemSnapshots } from './live-item-snapshots.js';
|
|
33
|
+
import { emptyFacts, stampOf, TranscriptFactsIndex } from './transcript-facts.js';
|
|
34
|
+
import { hasPlainHistory, historyPaths, readHistoryText, readHistoryTextAsync, removeHistory, restoreHistory } from './history-file.js';
|
|
35
|
+
import { workerExecutionIdentitySchema } from '../delegation/execution-identity.js';
|
|
36
|
+
import { inspectGeneration, processStartToken } from '../delegation/process-liveness.js';
|
|
37
|
+
import { collectSecretValues, redactDeep } from '../core/secret-redaction.js';
|
|
38
|
+
// Pure, dependency-free reference helpers — the same sanity bound the marker parser applies.
|
|
39
|
+
import { archivePullRequests, pullRequestKey } from './pull-requests.js';
|
|
40
|
+
import { isRepoScopedRef, promptNamesRepo, refUrlRepo } from './task-refs.js';
|
|
41
|
+
// Type-only module (zod + nothing else), so this cannot cycle back into the store.
|
|
42
|
+
import { workflowDefSchema } from '../workflows/types.js';
|
|
43
|
+
import { applyOutcomes } from './outcome-commit.js';
|
|
44
|
+
import { ISSUE_URL_RE, originatingIssue, refUrlNumber, referencedPrDeclaration, rescopeRun, resolveIssueReference, resolveReferencedRef, restoreOriginatingIssue, scanEventReferences, trackReferencedIssues, trackReferencedPrs } from './run-references.js';
|
|
45
|
+
import { applyExit, doneCommits, exitDurability, RUN_EXITS } from './run-exits.js';
|
|
46
|
+
import { assertConversationIdFree } from './conversation-identity.js';
|
|
47
|
+
export { ConversationIdInUseError } from './conversation-identity.js';
|
|
48
|
+
import { RUNNER_IDS } from '../core/agent-runner.js';
|
|
49
|
+
import { ARCHIVED_ROOT, RUNS_DB_FILE, RUNS_IMPORT_COMPLETE_KEY, RunConflictError, RunDatabase, RunDatabaseBusyError, } from './run-database.js';
|
|
50
|
+
import { restoreLostReferencedPullRequest } from './loaded-run-healing.js';
|
|
51
|
+
import { failLoadedInterruptedRun, restoreLoadedUsageLimitHold, settleLoadedStop } from './loaded-run-exits.js';
|
|
52
|
+
import { hasLegacyWaitFields, upgradeLegacyWaits, UNREADABLE_WAIT } from './legacy-waits.js';
|
|
53
|
+
export { ciWaitEntry, holdsWorkerWait, upgradeLegacyWaits, workerWaitEntry } from './legacy-waits.js';
|
|
54
|
+
import { ExitEvents } from './exit-events.js';
|
|
55
|
+
import { encodeRunRow, isLiveRecord, isLiveStatus } from './run-row.js';
|
|
56
|
+
import { collectRawExtras, encodeRawRecord } from './raw-record.js';
|
|
57
|
+
import { ColdReadCache, storedRow } from './cold-reads.js';
|
|
58
|
+
import { assertNoLegacyCockpit, assertNoLegacyWriter, backUpLegacyIndex, LEGACY_INDEX_FILE, LegacyWriterError, readLegacyIndex } from './legacy-index.js';
|
|
59
|
+
import { toRunStoreOpenError } from './store-open-error.js';
|
|
60
|
+
import { archiveStoreFamily } from './archive-family.js';
|
|
61
|
+
import { claimStoreFamilies } from './claim-families.js';
|
|
62
|
+
import { createStoreOwnedRun } from './owned-run-create.js';
|
|
63
|
+
import { commitStoreWorkerResult } from './worker-result-commit.js';
|
|
64
|
+
import { claimOwnerLive, closeClaimSession, familyKey, foreignClaimedRunIds, liveForeignClaimedRunIds, openClaimSession, rowFamily } from './run-claims.js';
|
|
65
|
+
import { assertProjectWritable, projectWriteRefused } from '../server/cockpit-ownership.js';
|
|
66
|
+
/**
|
|
67
|
+
* A runner id as it may appear in a PERSISTED record, normalized to the three
|
|
68
|
+
* ids the rest of cezar speaks (#547).
|
|
69
|
+
*
|
|
70
|
+
* `claude-cli` is the legacy spelling of `claude` — still a member of
|
|
71
|
+
* `AgentBackend` and still accepted by `createRunner`, and accepted in `runs.json`. The enum
|
|
72
|
+
* here once failed to accept it: the loader `safeParse`s the
|
|
73
|
+
* WHOLE array, so one record carrying it would have dropped every run in the
|
|
74
|
+
* file.
|
|
75
|
+
*
|
|
76
|
+
* Parse-and-fold rather than widen: the legacy id is accepted on the way in and
|
|
77
|
+
* collapsed to `claude`, so no consumer, wire type or contract schema ever sees
|
|
78
|
+
* a fourth runner. The narrowing is one-way and permanent (the index is
|
|
79
|
+
* re-serialized from the parsed records), which is what "old run records
|
|
80
|
+
* normalise identically to `claude`" in `core/model-identity.ts` has always
|
|
81
|
+
* claimed. Use ONLY for read-back of stored state — request bodies, settings and
|
|
82
|
+
* workflow step defs stay the three selectable ids (`RunnerId`), because nothing
|
|
83
|
+
* should be able to ASK for the legacy spelling.
|
|
84
|
+
*/
|
|
85
|
+
const storedRunnerSchema = z
|
|
86
|
+
.enum([...RUNNER_IDS, 'claude-cli'])
|
|
87
|
+
.transform((id) => (id === 'claude-cli' ? 'claude' : id));
|
|
88
|
+
const stepStateSchema = contractStepStateSchema.extend({ backend: storedRunnerSchema.optional() });
|
|
89
|
+
/** Exported for `./run-index.ts`, the read-only reader of the same records. Nothing else should
|
|
90
|
+
* parse a stored run — see `reconcileLoadedRun` for why a second parser is a correctness risk. */
|
|
91
|
+
export const runRecordSchema = contractRunRecordSchema.extend({
|
|
92
|
+
delegation: storedDelegationStateSchema,
|
|
93
|
+
lastWaitsError: contractRunRecordSchema.shape.lastWaitsError.catch(undefined),
|
|
94
|
+
automaticWakes: contractRunRecordSchema.shape.automaticWakes.catch(undefined),
|
|
95
|
+
waitDeliveryFailures: contractRunRecordSchema.shape.waitDeliveryFailures.catch(undefined),
|
|
96
|
+
runner: storedRunnerSchema.optional(),
|
|
97
|
+
notify: z.boolean().optional().catch(undefined),
|
|
98
|
+
webhook: contractRunRecordSchema.shape.webhook.catch(undefined),
|
|
99
|
+
destroyRetry: contractRunRecordSchema.shape.destroyRetry.catch(undefined),
|
|
100
|
+
stopping: contractRunRecordSchema.shape.stopping.catch(undefined),
|
|
101
|
+
hasPendingPermission: contractRunRecordSchema.shape.hasPendingPermission.catch(undefined),
|
|
102
|
+
autoResumeAt: z.string().datetime().optional().catch(undefined),
|
|
103
|
+
autoResumeAttempts: z.number().int().min(0).optional().catch(undefined),
|
|
104
|
+
pullRequests: z.preprocess((value) => Array.isArray(value)
|
|
105
|
+
? value.flatMap((entry) => {
|
|
106
|
+
const parsed = runPullRequestSchema.safeParse(entry);
|
|
107
|
+
return parsed.success ? [parsed.data] : [];
|
|
108
|
+
})
|
|
109
|
+
: undefined, z.array(runPullRequestSchema).optional()),
|
|
110
|
+
worktreeSetup: contractRunRecordSchema.shape.worktreeSetup.catch(undefined),
|
|
111
|
+
archived: z.boolean().default(false),
|
|
112
|
+
workflowDef: workflowDefSchema.optional().catch(undefined),
|
|
113
|
+
steps: z.array(stepStateSchema),
|
|
114
|
+
automation: contractRunRecordSchema.shape.automation.unwrap().extend({
|
|
115
|
+
automationRevision: z.number().int().positive(),
|
|
116
|
+
githubUrl: z.string().url(),
|
|
117
|
+
}).optional(),
|
|
118
|
+
automationTrigger: contractRunRecordSchema.shape.automationTrigger.unwrap().extend({
|
|
119
|
+
automationRevision: z.number().int().positive(),
|
|
120
|
+
}).optional(),
|
|
121
|
+
/** The handoff file's autosave records (hearsay-tools/cezarion#1809); unreadable drops the field. */
|
|
122
|
+
handoffRecords: contractRunRecordSchema.shape.handoffRecords.catch(undefined),
|
|
123
|
+
});
|
|
124
|
+
/** Read legacy wait fields into the wait list and salvage per entry, identically for live stores
|
|
125
|
+
* and the read-only workspace index. */
|
|
126
|
+
export function parseRunRecords(raw) {
|
|
127
|
+
if (Array.isArray(raw))
|
|
128
|
+
for (const row of raw) {
|
|
129
|
+
if (!row || typeof row !== 'object')
|
|
130
|
+
continue;
|
|
131
|
+
upgradeLegacyWaits(row);
|
|
132
|
+
// One unreadable wait must not evict the run, nor its readable siblings.
|
|
133
|
+
for (const key of ['waits', 'lastWaits']) {
|
|
134
|
+
if (row[key] === undefined)
|
|
135
|
+
continue;
|
|
136
|
+
const entries = Array.isArray(row[key]) ? row[key] : [];
|
|
137
|
+
const readable = entries.filter((entry) => waitSchema.safeParse(entry).success);
|
|
138
|
+
if (!Array.isArray(row[key]) || readable.length !== entries.length)
|
|
139
|
+
row.lastWaitsError = UNREADABLE_WAIT;
|
|
140
|
+
if (readable.length)
|
|
141
|
+
row[key] = readable;
|
|
142
|
+
else
|
|
143
|
+
delete row[key];
|
|
144
|
+
}
|
|
145
|
+
// One unreadable registration must not evict the run, nor its readable siblings (#781).
|
|
146
|
+
if (row.previewServers !== undefined) {
|
|
147
|
+
if (Array.isArray(row.previewServers))
|
|
148
|
+
row.previewServers = row.previewServers.filter((entry) => previewServerSchema.safeParse(entry).success);
|
|
149
|
+
else
|
|
150
|
+
delete row.previewServers;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return z.array(runRecordSchema).safeParse(raw);
|
|
154
|
+
}
|
|
155
|
+
/** One stored record (a row's `data`) through `parseRunRecords`' salvage and schema, with what the
|
|
156
|
+
* schema dropped. Read after the salvage ran: what it removes (an unreadable CI wait, a preview
|
|
157
|
+
* server entry) the runtime has decided to drop, and keeps dropping on the next write, as it always
|
|
158
|
+
* has; only what the schema does not know is put back. */
|
|
159
|
+
function decodeStoredRecord(raw) {
|
|
160
|
+
const upgraded = hasLegacyWaitFields(raw);
|
|
161
|
+
const parsed = parseRunRecords([raw]);
|
|
162
|
+
if (!parsed.success)
|
|
163
|
+
return undefined;
|
|
164
|
+
const run = parsed.data[0];
|
|
165
|
+
return { run, extras: collectRawExtras(raw, run), ...(upgraded ? { upgraded: true } : {}) };
|
|
166
|
+
}
|
|
167
|
+
/** One database row's `data` back into a record and its extras. Undefined when it does not parse:
|
|
168
|
+
* one unreadable row costs that row only. */
|
|
169
|
+
export function decodeRunRow(data) {
|
|
170
|
+
try {
|
|
171
|
+
return decodeStoredRecord(JSON.parse(data));
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/** One database row's `data` back into a record, for a reader that never writes it back. */
|
|
178
|
+
export function decodeRunRecord(data) {
|
|
179
|
+
try {
|
|
180
|
+
const parsed = parseRunRecords([JSON.parse(data)]);
|
|
181
|
+
return parsed.success ? parsed.data[0] : undefined;
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
return undefined;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
export { LEGACY_INDEX_BACKUP_FILE, LEGACY_INDEX_FILE } from './legacy-index.js';
|
|
188
|
+
/** The pauses between open attempts while the database is busy, when `open` may wait (a boot):
|
|
189
|
+
* about 3.5 s in all, with each attempt's own busy timeout. Long enough for another process to
|
|
190
|
+
* finish importing a large `runs.json`. */
|
|
191
|
+
const OPEN_RETRY_DELAYS_MS = [50, 100, 200, 400, 800, 1600];
|
|
192
|
+
/** Block this thread for `ms`. Only `open` does, at boot, when there is nothing else to run. */
|
|
193
|
+
function sleepSync(ms) {
|
|
194
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
195
|
+
}
|
|
196
|
+
/** Runs (and conversation roots, one family query each) the open-time facts warm-up enumerates
|
|
197
|
+
* per event-loop turn (hearsay-tools/cezarion#949), so each slice stays a few milliseconds. */
|
|
198
|
+
const WARM_IDS_PER_SLICE = 100;
|
|
199
|
+
const MAX_RUNS_KEPT = 300;
|
|
200
|
+
/** How many archived root runs the run lists carry per project (#864): `GET /run-summaries?archived=recent`
|
|
201
|
+
* and `GET /workspace/runs-index`. Unarchived runs are never cut; older archived runs page in
|
|
202
|
+
* through `GET /run-summaries/archived` and the workspace search. */
|
|
203
|
+
export const ARCHIVED_WINDOW = 200;
|
|
204
|
+
/**
|
|
205
|
+
* Reconcile one record just read off disk with the fact that whichever process wrote it is gone.
|
|
206
|
+
*
|
|
207
|
+
* Mutates and returns `run`. Extracted from `RunStore.open` so the read-only index reader
|
|
208
|
+
* (`./run-index.ts`) answers the SAME question about a `running` row on disk. Two parsers that
|
|
209
|
+
* disagree here is a visible bug, not an internal one: the cockpit would show a task as running
|
|
210
|
+
* in the ⌘K index and failed the moment you opened it.
|
|
211
|
+
*
|
|
212
|
+
* `keepLive` (#367): leave `queued`/`running`/`waiting` untouched so the caller can recover them
|
|
213
|
+
* (RunManager.recover re-queues queued runs, resumes interrupted ones). Without it — one-shot CLI
|
|
214
|
+
* paths that never recover, and the index reader, which has no manager at all — live-looking runs
|
|
215
|
+
* are marked failed so no ghost stays behind.
|
|
216
|
+
*/
|
|
217
|
+
export function reconcileLoadedRun(run, opts) {
|
|
218
|
+
settleLoadedStop(run);
|
|
219
|
+
restoreLoadedUsageLimitHold(run);
|
|
220
|
+
failLoadedInterruptedRun(run, opts);
|
|
221
|
+
clearLoadedStaleResume(run);
|
|
222
|
+
restoreLostReferencedPullRequest(run, referencedPrDeclaration, resolveReferencedRef);
|
|
223
|
+
restoreOriginatingIssue(run);
|
|
224
|
+
// Repository scoping follows reconciliation: stores arm a handle asynchronously, while
|
|
225
|
+
// the read-only index applies its cached handle to these fresh records (#97). This ordering
|
|
226
|
+
// also scopes any referenced PR restored above without filtering candidate evidence first.
|
|
227
|
+
return run;
|
|
228
|
+
}
|
|
229
|
+
function clearLoadedStaleResume(run) {
|
|
230
|
+
// Recovery owns waits; a usage-limit appointment survives while status is waiting.
|
|
231
|
+
stageLoaded(run);
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Settle an adopted dead owner’s live run without starting work. Untouched runs cancel; started
|
|
235
|
+
* runs become interrupted. Stop explicitly cancels its target. Mutates run.
|
|
236
|
+
*/
|
|
237
|
+
export function settleOrphanedRun(run, opts = {}) {
|
|
238
|
+
if (opts.stop && (run.status === 'queued' || run.status === 'running' || run.status === 'waiting'))
|
|
239
|
+
run.stopping = true;
|
|
240
|
+
if (run.status === 'queued' && !run.startedAt && run.workflowDef !== undefined &&
|
|
241
|
+
run.steps.every((step) => step.status === 'pending' && !step.startedAt && !step.sessionId)) {
|
|
242
|
+
Object.assign(run, applyExit(run, 'cancel'));
|
|
243
|
+
}
|
|
244
|
+
return reconcileLoadedRun(run, { keepLive: false });
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Every field `RunStore.open` may rewrite while loading a record: what `reconcileLoadedRun`
|
|
248
|
+
* assigns. Compared before and after, it tells open() which rows normalization changed, so it
|
|
249
|
+
* marks those dirty and no others. Edit it together with that function, or a normalized row stays unsaved (the store tests pin each field).
|
|
250
|
+
*/
|
|
251
|
+
function loadNormalizedFields(run) {
|
|
252
|
+
return JSON.stringify([
|
|
253
|
+
run.stopping, run.status, run.finishedAt, run.error, run.autoResumeAt,
|
|
254
|
+
run.referencedPullRequestUrl, run.pullRequests,
|
|
255
|
+
run.issueNumber, run.referencedIssueUrl, run.referencedIssueNumberSeeded, run.hasPendingPermission, run.steps.map((step) => step.status),
|
|
256
|
+
]);
|
|
257
|
+
}
|
|
258
|
+
/** Test seam (#779): `beforeTransaction` runs once `runs.json` is read and parsed, before the
|
|
259
|
+
* import asks for the write lock; `beforeCommit` runs inside the import transaction, after every
|
|
260
|
+
* row is written and before the last check that no older cezar wrote `runs.json` meanwhile. */
|
|
261
|
+
let legacyImportHook;
|
|
262
|
+
export function __setLegacyImportHookForTests(hook) {
|
|
263
|
+
legacyImportHook = hook;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Import legacy rows and the completion marker atomically, once. Recheck live-writer evidence
|
|
267
|
+
* before commit and preserve exact input bytes in a synced backup. Leave runs.json untouched;
|
|
268
|
+
* invalid entries are skipped and reported.
|
|
269
|
+
*/
|
|
270
|
+
function importLegacyIndex(db, dataDir) {
|
|
271
|
+
assertNoLegacyCockpit(dataDir);
|
|
272
|
+
const snapshot = readLegacyIndex(join(dataDir, LEGACY_INDEX_FILE));
|
|
273
|
+
const parsed = snapshot ? legacyIndexRows(snapshot.bytes, parseRunRecords) : undefined;
|
|
274
|
+
const rows = parsed?.rows ?? [];
|
|
275
|
+
const skipped = parsed?.skipped ?? 0;
|
|
276
|
+
const source = !snapshot ? 'none' : parsed ? LEGACY_INDEX_FILE : `${LEGACY_INDEX_FILE} (unparseable)`;
|
|
277
|
+
assertNoLegacyWriter(dataDir, snapshot);
|
|
278
|
+
legacyImportHook?.beforeTransaction?.();
|
|
279
|
+
let backup;
|
|
280
|
+
const commit = db.transaction({
|
|
281
|
+
upserts: rows,
|
|
282
|
+
deletes: [],
|
|
283
|
+
onlyIfMetaAbsent: RUNS_IMPORT_COMPLETE_KEY,
|
|
284
|
+
beforeCommit: () => {
|
|
285
|
+
legacyImportHook?.beforeCommit?.();
|
|
286
|
+
assertNoLegacyWriter(dataDir, snapshot);
|
|
287
|
+
backup = snapshot ? backUpLegacyIndex(dataDir, snapshot) : undefined;
|
|
288
|
+
const read = snapshot ? { bytes: snapshot.bytes.length, sha256: snapshot.sha256, backup } : {};
|
|
289
|
+
return { [RUNS_IMPORT_COMPLETE_KEY]: JSON.stringify({ at: new Date().toISOString(), source, records: rows.length, ...(skipped > 0 ? { skipped } : {}), ...read }) };
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
// Told once, by the import that committed: the marker means no later open reads runs.json again.
|
|
293
|
+
if (commit.skipped || backup === undefined)
|
|
294
|
+
return;
|
|
295
|
+
const kept = `Its exact bytes are kept in ${join(dataDir, backup)}; https://github.com/hearsay-tools/cezarion/blob/main/docs/run-history-recovery.md says how to import it after a repair.`;
|
|
296
|
+
if (!parsed)
|
|
297
|
+
console.warn(`${logPrefix()} ${LEGACY_INDEX_FILE} could not be read; no run was imported, and this project starts with an empty history. ${kept}`);
|
|
298
|
+
else if (skipped > 0)
|
|
299
|
+
console.warn(`${logPrefix()} ${skipped} of ${parsed.total} runs in ${LEGACY_INDEX_FILE} could not be read and were not imported. ${kept}`);
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Finished rows whose stored summary an open-time normalization would still change: a referenced
|
|
303
|
+
* PR an older cezar's created-PR declaration erased, or issue attribution displaced by a
|
|
304
|
+
* follow-up declaration (see `reconcileLoadedRun`). Not live, so not
|
|
305
|
+
* in the `live` column; open decodes these few and keeps the ones it actually repaired.
|
|
306
|
+
*/
|
|
307
|
+
const LEGACY_REFERENCE_HEAL_SQL = "(json_extract(summary, '$.markerRefs.pr') IS NOT NULL AND json_extract(summary, '$.referencedPullRequestUrl') IS NULL)" +
|
|
308
|
+
" OR (NOT EXISTS (SELECT 1 FROM meta WHERE key = 'issue-attribution:' || runs.id AND value = CAST(runs.revision AS TEXT)) AND json_extract(summary, '$.markerRefs.issue') IS NOT NULL)";
|
|
309
|
+
/**
|
|
310
|
+
* Rows whose record names the automation that launched it while the stored summary does not
|
|
311
|
+
* (hearsay-tools/cezarion#1332): summaries a cezar wrote before `RunSummary.automationId`
|
|
312
|
+
* existed. Open rewrites each one's summary once, so the sidebar's Automations tab classifies a
|
|
313
|
+
* finished task nobody has opened since the upgrade. `instr` keeps the JSON parse off every
|
|
314
|
+
* other row.
|
|
315
|
+
*/
|
|
316
|
+
const AUTOMATION_ORIGIN_HEAL_SQL = "instr(data, '\"automation') > 0 AND json_extract(summary, '$.automationId') IS NULL" +
|
|
317
|
+
" AND (json_extract(data, '$.automation.automationId') IS NOT NULL OR json_extract(data, '$.automationTrigger.automationId') IS NOT NULL)";
|
|
318
|
+
/** A record as a branch owner, or undefined when it owns no branch and created no PR: the
|
|
319
|
+
* projection `listBranchOwners` reads off a row's summary and columns, computed from memory. */
|
|
320
|
+
export function branchOwnerOf(run) {
|
|
321
|
+
const branch = encodeRunRow(run).branch;
|
|
322
|
+
if (!branch && run.pullRequestUrl === undefined)
|
|
323
|
+
return undefined;
|
|
324
|
+
return {
|
|
325
|
+
id: run.id, title: run.title, status: run.status, archived: run.archived, createdAt: run.createdAt,
|
|
326
|
+
...(branch ? { branch } : {}),
|
|
327
|
+
...(run.baseBranch === undefined ? {} : { baseBranch: run.baseBranch }),
|
|
328
|
+
...(run.pullRequestUrl === undefined ? {} : { pullRequestUrl: run.pullRequestUrl }),
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
/** The one answer every control gives for a run another cezar process owns (`409 { error }`). */
|
|
332
|
+
export const RUN_IN_USE_ELSEWHERE = `run is in use by another ${productName('cezar')} process`;
|
|
333
|
+
/** The answer for a run this process stopped writing after a conflicting write. */
|
|
334
|
+
export const RUN_QUARANTINED = `run changed under this ${productName('cezar')} process — restart ${productName('cezar')} to reload it`;
|
|
335
|
+
/** A write this store must not make: another process owns the run, its owner died with work
|
|
336
|
+
* still live (adopt it first), or a conflict quarantined it. Nothing was changed. */
|
|
337
|
+
export class RunWriteRefusedError extends Error {
|
|
338
|
+
runId;
|
|
339
|
+
ownership;
|
|
340
|
+
constructor(runId, ownership, fallbackError) {
|
|
341
|
+
super(ownership === 'fallback' ? fallbackError ?? `run ${runId} is read only in the fallback project directory`
|
|
342
|
+
: ownership === 'quarantined' ? `${RUN_QUARANTINED}: ${runId}`
|
|
343
|
+
: ownership === 'orphaned' ? `run ${runId} belongs to a ${productName('cezar')} process that exited; take it over before writing it`
|
|
344
|
+
: `${RUN_IN_USE_ELSEWHERE}: ${runId}`);
|
|
345
|
+
this.runId = runId;
|
|
346
|
+
this.ownership = ownership;
|
|
347
|
+
this.name = 'RunWriteRefusedError';
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
/** Make `target` hold exactly `source`'s fields, keeping the object every holder references. */
|
|
351
|
+
function replaceRecord(target, source) {
|
|
352
|
+
for (const key of Object.keys(target))
|
|
353
|
+
if (!(key in source))
|
|
354
|
+
delete target[key];
|
|
355
|
+
Object.assign(target, source);
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* A worker's destroy moving between two unfinished phases, and nothing else changing: nothing the
|
|
359
|
+
* family reconcile reads changed, so it may skip that pass (#880). The first request and the
|
|
360
|
+
* `complete` phase still reconcile: one changes delivery eligibility, the other settles requests.
|
|
361
|
+
*/
|
|
362
|
+
function isDestroyProgress(before, after) {
|
|
363
|
+
if (before?.role !== 'worker' || after.role !== 'worker' || !before.destroy || !after.destroy)
|
|
364
|
+
return false;
|
|
365
|
+
if (before.destroy.phase === 'complete' || after.destroy.phase === 'complete')
|
|
366
|
+
return false;
|
|
367
|
+
return isDeepStrictEqual({ ...before, destroy: undefined }, { ...after, destroy: undefined });
|
|
368
|
+
}
|
|
369
|
+
/** NDJSON transcript text to events; a damaged line is skipped, never fatal. */
|
|
370
|
+
function parseEvents(raw) {
|
|
371
|
+
return raw
|
|
372
|
+
.split('\n')
|
|
373
|
+
.filter(Boolean)
|
|
374
|
+
.map((line) => {
|
|
375
|
+
try {
|
|
376
|
+
return JSON.parse(line);
|
|
377
|
+
}
|
|
378
|
+
catch {
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
})
|
|
382
|
+
.filter((e) => e !== null);
|
|
383
|
+
}
|
|
384
|
+
function newestFirst(a, b) {
|
|
385
|
+
if (a.createdAt !== b.createdAt)
|
|
386
|
+
return a.createdAt < b.createdAt ? 1 : -1;
|
|
387
|
+
return a.seq < b.seq ? -1 : a.seq > b.seq ? 1 : 0;
|
|
388
|
+
}
|
|
389
|
+
/** An archived page's cursor: the last row's order, opaque to clients. A held run with no row yet
|
|
390
|
+
* lists after every row (`seq` is infinite), which JSON cannot carry, so it travels as null. */
|
|
391
|
+
function encodeArchivedCursor(key) {
|
|
392
|
+
return Buffer.from(JSON.stringify({ c: key.createdAt, s: Number.isFinite(key.seq) ? key.seq : null })).toString('base64url');
|
|
393
|
+
}
|
|
394
|
+
function decodeArchivedCursor(cursor) {
|
|
395
|
+
try {
|
|
396
|
+
const parsed = JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8'));
|
|
397
|
+
if (typeof parsed !== 'object' || parsed === null)
|
|
398
|
+
return undefined;
|
|
399
|
+
const { c, s } = parsed;
|
|
400
|
+
if (typeof c !== 'string' || !(s === null || (typeof s === 'number' && Number.isInteger(s))))
|
|
401
|
+
return undefined;
|
|
402
|
+
return { createdAt: c, seq: s ?? Number.POSITIVE_INFINITY };
|
|
403
|
+
}
|
|
404
|
+
catch {
|
|
405
|
+
return undefined;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
/** The items newest first, each by the order beside it. Stable: ties keep their input order. */
|
|
409
|
+
function sortNewestFirst(entries) {
|
|
410
|
+
return [...entries].sort(([, a], [, b]) => newestFirst(a, b)).map(([item]) => item);
|
|
411
|
+
}
|
|
412
|
+
/** The delegation family a run belongs to, by its root's id: a root is its own, a worker its
|
|
413
|
+
* parent's. Workers cannot delegate, so a family is one root and its direct workers, never more. */
|
|
414
|
+
function familyRootOf(run) {
|
|
415
|
+
if (run.delegation?.role === 'root')
|
|
416
|
+
return run.id;
|
|
417
|
+
if (run.delegation?.role === 'worker')
|
|
418
|
+
return run.delegation.parentRunId;
|
|
419
|
+
return undefined;
|
|
420
|
+
}
|
|
421
|
+
/** Tests freeze the copies of cold runs, so an in-place write to one throws instead of vanishing. */
|
|
422
|
+
function deepFreeze(value) {
|
|
423
|
+
if (value && typeof value === 'object' && !Object.isFrozen(value)) {
|
|
424
|
+
Object.freeze(value);
|
|
425
|
+
for (const child of Object.values(value))
|
|
426
|
+
deepFreeze(child);
|
|
427
|
+
}
|
|
428
|
+
return value;
|
|
429
|
+
}
|
|
430
|
+
const WORKER_PROCESS_CAP = 32;
|
|
431
|
+
const recordedProcessSchema = z.object({ pid: z.number().int().positive(), startToken: z.string().min(1).max(128).optional(),
|
|
432
|
+
pgid: z.number().int().positive().optional() }).strict();
|
|
433
|
+
const workerProcessRecordSchema = z.object({ generation: z.string().uuid(), controller: recordedProcessSchema,
|
|
434
|
+
processes: z.array(recordedProcessSchema).max(WORKER_PROCESS_CAP) }).strict();
|
|
435
|
+
const startToken = (pid) => { const token = processStartToken(pid); return token === undefined ? {} : { startToken: token }; };
|
|
436
|
+
/**
|
|
437
|
+
* Run rows in runs.db plus append-only NDJSON events and an in-process event bus. Hold live,
|
|
438
|
+
* pinned, related and unsettled records only. Optimistic writes debounce; durable commits
|
|
439
|
+
* publish after persistence. Family claims fence every write.
|
|
440
|
+
*/
|
|
441
|
+
export class RunStore extends EventEmitter {
|
|
442
|
+
dataDir;
|
|
443
|
+
/** The held set: every record this store keeps in memory, by id. */
|
|
444
|
+
held = new Map();
|
|
445
|
+
/** Who holds a run beyond its record, by holder, so one holder's unpin never releases another. */
|
|
446
|
+
pins = new Map();
|
|
447
|
+
/** Staged ids while a durable commit installs and announces them: never evicted mid-commit. */
|
|
448
|
+
committing = new Set();
|
|
449
|
+
saveTimer = null;
|
|
450
|
+
/** `null` once the store is closed, and in a `RunStore.unavailable` store (nothing is written,
|
|
451
|
+
* nothing is evicted). */
|
|
452
|
+
db = null;
|
|
453
|
+
get reports() { return reportsIndex(this.db, this, this.dataDir, this.openFailure); }
|
|
454
|
+
/** Why `open` failed, in a `RunStore.unavailable` store. */
|
|
455
|
+
openFailure;
|
|
456
|
+
persistence;
|
|
457
|
+
/** Runs whose held record is ahead of its row; the next save or commit writes them. */
|
|
458
|
+
dirty = new Set();
|
|
459
|
+
/** The lifecycle events run exits write (B12). */
|
|
460
|
+
exitEvents = new ExitEvents((id, event) => this.appendEvent(id, event));
|
|
461
|
+
/** Runs gone from memory whose rows the next save or commit deletes. */
|
|
462
|
+
deleted = new Set();
|
|
463
|
+
/** Deleted runs whose history files (events, handoff, images, artifacts) go once the delete of
|
|
464
|
+
* their row commits: until then a conflict can undo the delete and bring the row back. */
|
|
465
|
+
historyOwed = new Set();
|
|
466
|
+
/** The repository this project IS (#945), armed after `open()` by `setRepoHandle`. Undefined
|
|
467
|
+
* until it arrives and `null` when it cannot be known — both mean "unscoped", which is
|
|
468
|
+
* exactly the pre-#945 behavior. */
|
|
469
|
+
repoHandle;
|
|
470
|
+
/** `open()`'s `keepLive`, which a run read later from `runs.db` is reconciled with too. */
|
|
471
|
+
keepLive = false;
|
|
472
|
+
/** This store's identity in `run_claims`; undefined without a database. */
|
|
473
|
+
owner;
|
|
474
|
+
/** The families this store claims, with the generation each acquisition got. */
|
|
475
|
+
claimed = new Map();
|
|
476
|
+
/** Families judged free whose claim a busy database kept this store from taking: the next write
|
|
477
|
+
* of their rows takes it in its own transaction, if it is still as judged (null: absent). */
|
|
478
|
+
pendingClaims = new Map();
|
|
479
|
+
/** The revision and `data` of each held row (and pending deletion) as this store last read or
|
|
480
|
+
* wrote it: what a write is fenced against, and a conflict's "original". No entry: this store
|
|
481
|
+
* created the row and has not written it yet. `extras` is what that `data` holds beyond the
|
|
482
|
+
* schema, which the next write of the row puts back (raw-record.ts). */
|
|
483
|
+
base = new Map();
|
|
484
|
+
/** Rows read cold in the current synchronous turn, so a commit staged from one is fenced against
|
|
485
|
+
* the version it was computed from. `owned`: its family was already this store's when read.
|
|
486
|
+
* Cleared at the next microtask: no commit spans a turn. */
|
|
487
|
+
coldBase = new Map();
|
|
488
|
+
coldReads = new ColdReadCache();
|
|
489
|
+
/** The family of each pending deletion, whose claim the deleting write is fenced against. */
|
|
490
|
+
deletedFamilies = new Map();
|
|
491
|
+
/** Rows a conflicting write excluded: this store refuses to write them until it is reopened. */
|
|
492
|
+
quarantined = new Set();
|
|
493
|
+
/** Rows whose `data` this store tried to decode and could not: left in the database untouched,
|
|
494
|
+
* and left out of the list rows from then on, as the cold reader leaves them out (run-index.ts). */
|
|
495
|
+
unreadable = new Set();
|
|
496
|
+
/** Background one-at-a-time compressor for archived transcripts (#818). */
|
|
497
|
+
compressor;
|
|
498
|
+
/** Transcript facts the delegation paths ask about, so they never re-read a transcript (#880). */
|
|
499
|
+
facts;
|
|
500
|
+
liveItems = new LiveItemSnapshots();
|
|
501
|
+
fallbackFacts;
|
|
502
|
+
factsFor(id) { return this.isFallbackRun(id) ? this.fallbackFacts ??= new TranscriptFactsIndex(this.sourceDataDir(id), true) : this.facts; }
|
|
503
|
+
/** Capture both sources on the same synchronous sequence boundary before history IO. */
|
|
504
|
+
liveReadSnapshot(runId) {
|
|
505
|
+
return { events: this.liveItems.read(runId), throughSeq: this.seqs.get(runId) ?? Infinity };
|
|
506
|
+
}
|
|
507
|
+
constructor(dataDir) {
|
|
508
|
+
super();
|
|
509
|
+
this.dataDir = dataDir;
|
|
510
|
+
this.setMaxListeners(100);
|
|
511
|
+
this.facts = new TranscriptFactsIndex(dataDir);
|
|
512
|
+
this.compressor = new HistoryCompressor(dataDir, (id) => this.isHistoryCompressEligible(id), (id, plain, archive) => this.facts.adoptArchive(id, plain, stampOf(archive)));
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Open the project's run store. See `reconcileLoadedRun` for what `keepLive` (#367) decides
|
|
516
|
+
* about live-looking rows.
|
|
517
|
+
*
|
|
518
|
+
* Reads `runs.db` once its import is complete. Before that, `runs.json` is imported into it in
|
|
519
|
+
* one transaction (`importLegacyIndex`) and left exactly as it was, for older cezars to read.
|
|
520
|
+
* Either way only the held set is decoded: the live rows (by the indexed `live` column), the
|
|
521
|
+
* few finished rows a normalization still repairs, and the delegation families of the live
|
|
522
|
+
* ones.
|
|
523
|
+
*
|
|
524
|
+
* A store that cannot be opened is never an empty one (#779, plan step 4): `open` throws a
|
|
525
|
+
* `RunStoreOpenError` naming the cause, and nothing is reset, restored or deleted. `retryBusy`
|
|
526
|
+
* waits out a busy database (and a cockpit that may still be importing) with growing pauses,
|
|
527
|
+
* about 3.5 s in all, before it gives up. Open is synchronous, so the wait blocks this thread:
|
|
528
|
+
* only a boot passes it, while nothing else runs yet. Without it a busy database fails after
|
|
529
|
+
* one busy timeout.
|
|
530
|
+
*/
|
|
531
|
+
static open(dataDir, opts) {
|
|
532
|
+
const path = join(dataDir, RUNS_DB_FILE);
|
|
533
|
+
const delays = opts?.retryBusy ? OPEN_RETRY_DELAYS_MS : [];
|
|
534
|
+
const started = Date.now();
|
|
535
|
+
for (let attempt = 0;; attempt++) {
|
|
536
|
+
try {
|
|
537
|
+
return RunStore.openOnce(dataDir, path, opts?.keepLive === true, opts?.maintenance === true, opts?.persist);
|
|
538
|
+
}
|
|
539
|
+
catch (error) {
|
|
540
|
+
const transient = error instanceof RunDatabaseBusyError || (error instanceof LegacyWriterError && error.reason === 'cockpit');
|
|
541
|
+
if (!transient || attempt >= delays.length) {
|
|
542
|
+
throw toRunStoreOpenError(error, path, attempt > 0 ? { waitedMs: Date.now() - started } : {});
|
|
543
|
+
}
|
|
544
|
+
sleepSync(delays[attempt]);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
static openOnce(dataDir, path, keepLive, maintenance, persist) {
|
|
549
|
+
const source = fallbackRunSource(dataDir, bytes => legacyIndexRows(bytes, parseRunRecords)?.rows);
|
|
550
|
+
const deferred = source && !existsSync(path) && !existsSync(join(dataDir, LEGACY_INDEX_FILE));
|
|
551
|
+
if (!deferred)
|
|
552
|
+
mkdirSync(join(dataDir, 'runs'), { recursive: true });
|
|
553
|
+
const store = new RunStore(dataDir);
|
|
554
|
+
store.keepLive = keepLive;
|
|
555
|
+
store.persistence = persist;
|
|
556
|
+
let db = RunDatabase.open(deferred ? ':memory:' : path);
|
|
557
|
+
try {
|
|
558
|
+
if (deferred)
|
|
559
|
+
db.transaction({ upserts: [], deletes: [], meta: { [RUNS_IMPORT_COMPLETE_KEY]: 'none' } });
|
|
560
|
+
else if (db.getMeta(RUNS_IMPORT_COMPLETE_KEY) === undefined)
|
|
561
|
+
importLegacyIndex(db, dataDir);
|
|
562
|
+
if (source)
|
|
563
|
+
db = addRunSource(db, dataDir, source);
|
|
564
|
+
store.db = db;
|
|
565
|
+
store.owner = openClaimSession();
|
|
566
|
+
if (!maintenance) {
|
|
567
|
+
store.loadHeldRows();
|
|
568
|
+
store.warmTranscriptFacts();
|
|
569
|
+
store.exitEvents.writeLoaded();
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
catch (error) {
|
|
573
|
+
if (store.owner) {
|
|
574
|
+
try {
|
|
575
|
+
db.releaseClaims(store.owner.session);
|
|
576
|
+
}
|
|
577
|
+
catch { /* the session closes below: its claims are provably dead */ }
|
|
578
|
+
closeClaimSession(store.owner.session);
|
|
579
|
+
}
|
|
580
|
+
store.owner = undefined;
|
|
581
|
+
store.db = null;
|
|
582
|
+
store.held.clear();
|
|
583
|
+
store.dirty.clear();
|
|
584
|
+
store.claimed.clear();
|
|
585
|
+
store.base.clear();
|
|
586
|
+
db.close();
|
|
587
|
+
throw error;
|
|
588
|
+
}
|
|
589
|
+
return store;
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* The store `serve` keeps for its boot project when `open` failed: no database, nothing held,
|
|
593
|
+
* and every attempt to create or save a run refused with `failure`. The cockpit answers every
|
|
594
|
+
* route of that project with `failure`'s message, and boot skips everything that would read
|
|
595
|
+
* its empty run list as "no runs" (orphan pruning, retention, recovery, scratch sweeps). The
|
|
596
|
+
* way out is a restart once the cause is gone.
|
|
597
|
+
*/
|
|
598
|
+
static unavailable(dataDir, failure) {
|
|
599
|
+
const store = new RunStore(dataDir);
|
|
600
|
+
store.openFailure = failure;
|
|
601
|
+
return store;
|
|
602
|
+
}
|
|
603
|
+
/** Why this store has no database, when it is `RunStore.unavailable`'s: its runs are unknown,
|
|
604
|
+
* not absent. */
|
|
605
|
+
get unavailable() { return this.openFailure; }
|
|
606
|
+
/** Every sidecar for a run follows the winning record, never file-by-file fallback. */
|
|
607
|
+
sourceDataDir(id) { return this.isFallbackRun(id) ? dataReadDirs(this.dataDir)[1] : this.dataDir; }
|
|
608
|
+
isFallbackRun(id) { return this.db?.isFallback(id) ?? false; }
|
|
609
|
+
hasFallbackRun(id) { return this.db?.hasFallback(id) ?? false; }
|
|
610
|
+
fallbackDeleteError(id, kind = 'record') { return fallbackRunDeleteError(this.dataDir, kind === 'record' ? this.hasFallbackRun(id) : this.isFallbackRun(id), this.peek(id)?.worktreePath); }
|
|
611
|
+
/**
|
|
612
|
+
* Open-time load: the live rows, the finished rows a load normalization repairs, then the
|
|
613
|
+
* delegation families of the live ones. A row that does not parse is skipped and left in the
|
|
614
|
+
* database untouched; one warning says how many.
|
|
615
|
+
*
|
|
616
|
+
* Only rows this store can claim are loaded (#779, plan step 3). A live row whose family another
|
|
617
|
+
* live process claims — `serve`'s parked run, seen by a headless `cez run` — is that process's:
|
|
618
|
+
* it is not loaded, normalized or recovered here, and stays readable through `getRun` and the
|
|
619
|
+
* list rows. A live row nobody alive claims is taken over, as before: this open recovers it
|
|
620
|
+
* (`keepLive`) or settles it as interrupted.
|
|
621
|
+
*/
|
|
622
|
+
loadHeldRows() {
|
|
623
|
+
let unreadable = 0;
|
|
624
|
+
const decode = (row) => {
|
|
625
|
+
const decoded = decodeRunRow(row.data);
|
|
626
|
+
if (!decoded) {
|
|
627
|
+
unreadable++;
|
|
628
|
+
this.unreadable.add(row.id);
|
|
629
|
+
}
|
|
630
|
+
return decoded;
|
|
631
|
+
};
|
|
632
|
+
const live = this.db.listLive();
|
|
633
|
+
const owned = this.claimFamilies(live.map(rowFamily), { allowLive: true, wait: true });
|
|
634
|
+
for (const row of live) {
|
|
635
|
+
if (!owned.has(rowFamily(row)))
|
|
636
|
+
continue;
|
|
637
|
+
const decoded = decode(row);
|
|
638
|
+
if (decoded)
|
|
639
|
+
this.adoptLoadedRun(decoded, { keepLive: this.keepLive }, row);
|
|
640
|
+
}
|
|
641
|
+
const heal = this.db.listWhere(LEGACY_REFERENCE_HEAL_SQL).filter((row) => !this.held.has(row.id));
|
|
642
|
+
const healable = this.claimFamilies(heal.map(rowFamily));
|
|
643
|
+
for (const row of heal) {
|
|
644
|
+
if (!healable.has(rowFamily(row)))
|
|
645
|
+
continue;
|
|
646
|
+
const decoded = decode(row);
|
|
647
|
+
// Held only when the repair changed it: the next save writes it, then it leaves.
|
|
648
|
+
if (decoded) {
|
|
649
|
+
// Persist verification once even when legacy issue attribution was already correct.
|
|
650
|
+
const needsIssueCheck = decoded.run.markerRefs?.issue !== undefined &&
|
|
651
|
+
this.db.getMeta(`issue-attribution:${row.id}`) !== String(row.revision);
|
|
652
|
+
this.adoptLoadedRun(decoded, { keepLive: this.keepLive, onlyIfChanged: !needsIssueCheck }, row);
|
|
653
|
+
if (needsIssueCheck)
|
|
654
|
+
this.dirty.add(row.id);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
const stale = this.db.listWhere(AUTOMATION_ORIGIN_HEAL_SQL).filter((row) => !this.held.has(row.id));
|
|
658
|
+
const restamp = this.claimFamilies(stale.map(rowFamily));
|
|
659
|
+
for (const row of stale) {
|
|
660
|
+
if (!restamp.has(rowFamily(row)))
|
|
661
|
+
continue;
|
|
662
|
+
const decoded = decode(row);
|
|
663
|
+
// Nothing in the record changes, only its summary: held dirty, the next save rewrites it.
|
|
664
|
+
if (decoded) {
|
|
665
|
+
this.adoptLoadedRun(decoded, { keepLive: this.keepLive }, row);
|
|
666
|
+
this.dirty.add(row.id);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
for (const rootId of this.anchoredFamilies())
|
|
670
|
+
this.holdFamily(rootId, decode);
|
|
671
|
+
this.releaseUnheldClaims();
|
|
672
|
+
if (unreadable > 0)
|
|
673
|
+
console.warn(`${logPrefix()} ${unreadable} run(s) in ${RUNS_DB_FILE} could not be read; they are left in the database untouched.`);
|
|
674
|
+
}
|
|
675
|
+
/** Normalize a record just read from `row` (see `reconcileLoadedRun`) and hold it. Marks the row
|
|
676
|
+
* dirty only when normalization changed it, so the next save persists exactly the runs open()
|
|
677
|
+
* rewrote. A row read up from schema-3 wait fields counts as changed: its rewrite drops them
|
|
678
|
+
* and recomputes its summary and live column (`upgradeLegacyWaits`). The caller has claimed
|
|
679
|
+
* the row's family. */
|
|
680
|
+
adoptLoadedRun({ run, extras, upgraded }, opts, row) {
|
|
681
|
+
if (this.db?.familyHasFallback(rowFamily(row)))
|
|
682
|
+
return;
|
|
683
|
+
const before = loadNormalizedFields(run);
|
|
684
|
+
const wasCancelled = run.status === 'cancelled';
|
|
685
|
+
if (opts.settle)
|
|
686
|
+
settleOrphanedRun(run, { stop: opts.stop });
|
|
687
|
+
else
|
|
688
|
+
reconcileLoadedRun(run, opts);
|
|
689
|
+
const changed = upgraded === true || loadNormalizedFields(run) !== before;
|
|
690
|
+
if (opts.onlyIfChanged && !changed)
|
|
691
|
+
return;
|
|
692
|
+
this.held.set(run.id, run);
|
|
693
|
+
this.base.set(run.id, { revision: row.revision, seq: row.seq, data: row.data, extras });
|
|
694
|
+
if (changed)
|
|
695
|
+
this.dirty.add(run.id);
|
|
696
|
+
if (!wasCancelled && run.status === 'cancelled')
|
|
697
|
+
this.exitEvents.loadCancelled(run.id);
|
|
698
|
+
}
|
|
699
|
+
/**
|
|
700
|
+
* Hold one delegation family: its root and the root's direct workers, read through the
|
|
701
|
+
* `parent_run_id` index. No recursion: workers cannot delegate, so a worker's own id never
|
|
702
|
+
* names a family. The caller has claimed it. `settle` settles its live rows instead of keeping
|
|
703
|
+
* them for recovery (`settleOrphanedRun`); `stopId` is the run a Stop settles as cancelled.
|
|
704
|
+
*/
|
|
705
|
+
holdFamily(rootId, decode = (row) => decodeRunRow(row.data), settle = false, stopId) {
|
|
706
|
+
const rows = [this.db.get(rootId), ...this.db.listByParent(rootId)];
|
|
707
|
+
for (const row of rows) {
|
|
708
|
+
if (!row || this.held.has(row.id) || this.deleted.has(row.id))
|
|
709
|
+
continue;
|
|
710
|
+
const decoded = decode(row);
|
|
711
|
+
if (decoded)
|
|
712
|
+
this.adoptLoadedRun(decoded, { keepLive: this.keepLive, settle, stop: row.id === stopId }, row);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
/** Make sure this store claims each of `families`; `claimStoreFamilies` takes the free ones. */
|
|
716
|
+
claimFamilies(families, opts = {}) {
|
|
717
|
+
return projectWriteRefused(this.dataDir) ? new Set() : claimStoreFamilies({
|
|
718
|
+
claimed: this.claimed,
|
|
719
|
+
pendingClaims: this.pendingClaims,
|
|
720
|
+
db: this.db,
|
|
721
|
+
owner: this.owner,
|
|
722
|
+
scheduleSave: () => this.scheduleSave(),
|
|
723
|
+
}, families, opts);
|
|
724
|
+
}
|
|
725
|
+
/** Release the claim of every family with nothing left in memory, once nothing is pending: a
|
|
726
|
+
* failed save keeps its families claimed until it succeeds. A failed release retries at the
|
|
727
|
+
* next sweep; `close()` releases the rest. */
|
|
728
|
+
releaseUnheldClaims() {
|
|
729
|
+
if (!this.db || !this.owner || this.dirty.size > 0 || this.deleted.size > 0)
|
|
730
|
+
return;
|
|
731
|
+
const kept = new Set();
|
|
732
|
+
for (const run of this.held.values())
|
|
733
|
+
kept.add(familyKey(run));
|
|
734
|
+
// A claim never taken has nothing to release in the database.
|
|
735
|
+
for (const family of this.pendingClaims.keys())
|
|
736
|
+
if (!kept.has(family))
|
|
737
|
+
this.pendingClaims.delete(family);
|
|
738
|
+
const released = [...this.claimed.keys()].filter((family) => !kept.has(family));
|
|
739
|
+
if (released.length === 0)
|
|
740
|
+
return;
|
|
741
|
+
try {
|
|
742
|
+
this.db.releaseClaims(this.owner.session, released);
|
|
743
|
+
for (const family of released)
|
|
744
|
+
this.claimed.delete(family);
|
|
745
|
+
}
|
|
746
|
+
catch {
|
|
747
|
+
// Busy or failing: the next sweep tries again.
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* Who may change this run right now (see `RunOwnership`), or undefined when there is no such
|
|
752
|
+
* run. Read-only: nothing is claimed. Controls ask this first, so a run another process owns is
|
|
753
|
+
* refused with `RUN_IN_USE_ELSEWHERE` and a dead process's run is adopted before it is changed.
|
|
754
|
+
*/
|
|
755
|
+
runOwnership(id) {
|
|
756
|
+
if (this.quarantined.has(id))
|
|
757
|
+
return 'quarantined';
|
|
758
|
+
const held = this.held.get(id);
|
|
759
|
+
if (!this.db)
|
|
760
|
+
return held ? 'held' : undefined;
|
|
761
|
+
const family = held ? familyKey(held) : this.deleted.has(id) ? undefined : this.db.familyOf(id);
|
|
762
|
+
if (family === undefined)
|
|
763
|
+
return undefined;
|
|
764
|
+
if (this.db.familyHasFallback(family))
|
|
765
|
+
return 'fallback';
|
|
766
|
+
if (this.claimed.has(family) || this.pendingClaims.has(family))
|
|
767
|
+
return 'held';
|
|
768
|
+
const claim = this.db.getClaim(family);
|
|
769
|
+
if (claim && claimOwnerLive(claim))
|
|
770
|
+
return 'foreign';
|
|
771
|
+
// The same rule `claimFamilies` writes by: a dead owner left only settled runs → free.
|
|
772
|
+
return this.db.familyHasLive(family) ? 'orphaned' : 'free';
|
|
773
|
+
}
|
|
774
|
+
/** Why a control on this run must be refused here, or undefined when this store may change it. */
|
|
775
|
+
writeRefusal(id) {
|
|
776
|
+
const ownership = this.runOwnership(id);
|
|
777
|
+
if (ownership === 'quarantined')
|
|
778
|
+
return RUN_QUARANTINED;
|
|
779
|
+
if (ownership === 'fallback')
|
|
780
|
+
return this.fallbackRefusal(id).message;
|
|
781
|
+
return ownership === 'foreign' || ownership === 'orphaned' ? RUN_IN_USE_ELSEWHERE : undefined;
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* Claim an orphaned family before a control, then settle its live rows.
|
|
785
|
+
* Stop cancels its target. Return the root id, or undefined for a live owner or missing run.
|
|
786
|
+
*/
|
|
787
|
+
adoptFamily(id, opts = {}) {
|
|
788
|
+
const family = this.db?.familyOf(id);
|
|
789
|
+
if (family === undefined || !this.claimFamilies([family], { allowLive: true, wait: true }).has(family))
|
|
790
|
+
return undefined;
|
|
791
|
+
this.holdFamily(family, undefined, true, opts.stop ? id : undefined);
|
|
792
|
+
this.exitEvents.writeLoaded();
|
|
793
|
+
return family;
|
|
794
|
+
}
|
|
795
|
+
/** Explicit controls only (`requireRunControl`): copy a fallback family in so it can be written. */
|
|
796
|
+
adoptFallbackFamily(id) {
|
|
797
|
+
const members = copyFallbackFamily(this.db, id, this.owner);
|
|
798
|
+
for (const member of members)
|
|
799
|
+
this.coldReads.forget(member);
|
|
800
|
+
return members.length > 0;
|
|
801
|
+
}
|
|
802
|
+
fallbackRefusal(id) { return new RunWriteRefusedError(id, 'fallback', fallbackRunDeleteError(this.dataDir, true)); }
|
|
803
|
+
/** Run files are written only for a family this store may write, never a fallback family. */
|
|
804
|
+
writeDir(id) { if (this.runOwnership(id) === 'fallback')
|
|
805
|
+
throw this.fallbackRefusal(id); return this.dataDir; }
|
|
806
|
+
/** Every run whose family another process owns (`foreignClaimedRunIds`). */
|
|
807
|
+
listForeignClaimedRunIds() { return foreignClaimedRunIds(this.db, this.owner); }
|
|
808
|
+
/** Non-terminal rows in the database (both directories) and the runs of families another live store claims. */
|
|
809
|
+
persistedLiveRunIds() { return { live: this.db?.listLive().map((row) => row.id) ?? [], foreign: liveForeignClaimedRunIds(this.db, this.owner) }; }
|
|
810
|
+
/**
|
|
811
|
+
* Whether a held run anchors itself (and its family) in memory. Anything else held is a family
|
|
812
|
+
* member or a settled write, and leaves at the next sweep (`evictSettled`).
|
|
813
|
+
*
|
|
814
|
+
* The held set is: every run that is live (`isLiveRecord`), pinned by a RunManager (`pin`),
|
|
815
|
+
* dirty, or inside a durable commit; plus the whole delegation family (root and direct
|
|
816
|
+
* workers) of any such run. Families are held because the RunManager reads a live run's family
|
|
817
|
+
* on every run event — `GET /runs/:id/relationships`, the global `reconcileWorkerWaits` pass
|
|
818
|
+
* (about twenty call sites), completion blockers and conversations — and decoding a cold
|
|
819
|
+
* record costs about 200 µs (JSON.parse plus the schema). Measured on a copy of a real
|
|
820
|
+
* cockpit's `runs.json` (958 runs, 957 of them delegated): a running root with 26 workers
|
|
821
|
+
* paid about 5 ms per family read, and the live families together about 10 ms per global
|
|
822
|
+
* pass, all on the event loop. Holding them costs memory in proportion to active work, not to
|
|
823
|
+
* history.
|
|
824
|
+
*/
|
|
825
|
+
isAnchor(id, run) {
|
|
826
|
+
return isLiveRecord(run) || this.pins.has(id) || this.dirty.has(id) || this.committing.has(id);
|
|
827
|
+
}
|
|
828
|
+
/** The family roots with an anchoring member in memory. */
|
|
829
|
+
anchoredFamilies() {
|
|
830
|
+
const roots = new Set();
|
|
831
|
+
for (const [id, run] of this.held) {
|
|
832
|
+
const root = familyRootOf(run);
|
|
833
|
+
if (root !== undefined && this.isAnchor(id, run))
|
|
834
|
+
roots.add(root);
|
|
835
|
+
}
|
|
836
|
+
return roots;
|
|
837
|
+
}
|
|
838
|
+
/** Drop every held run that is neither an anchor nor in an anchored family. A store without a
|
|
839
|
+
* database keeps everything: memory is the only copy it has. */
|
|
840
|
+
evictSettled() {
|
|
841
|
+
if (!this.db)
|
|
842
|
+
return;
|
|
843
|
+
const families = this.anchoredFamilies();
|
|
844
|
+
for (const [id, run] of this.held) {
|
|
845
|
+
if (this.isAnchor(id, run))
|
|
846
|
+
continue;
|
|
847
|
+
const root = familyRootOf(run);
|
|
848
|
+
if (root !== undefined && families.has(root))
|
|
849
|
+
continue;
|
|
850
|
+
this.held.delete(id);
|
|
851
|
+
this.base.delete(id);
|
|
852
|
+
this.coldReads.forget(id);
|
|
853
|
+
}
|
|
854
|
+
this.releaseUnheldClaims();
|
|
855
|
+
}
|
|
856
|
+
/** A run read from `runs.db`, normalized like a loaded one (`reconcileLoadedRun`, then the
|
|
857
|
+
* repository scope) but held by nobody. Undefined when absent, deleted or unreadable. */
|
|
858
|
+
loadCold(id) {
|
|
859
|
+
if (!this.db || this.deleted.has(id))
|
|
860
|
+
return undefined;
|
|
861
|
+
const row = this.db.get(id);
|
|
862
|
+
return row ? this.decodeCold(row) : undefined;
|
|
863
|
+
}
|
|
864
|
+
decodeCold(row) {
|
|
865
|
+
this.coldReads.at(row.id, row.revision); // the fence below moves to this revision
|
|
866
|
+
const decoded = decodeRunRow(row.data);
|
|
867
|
+
if (!decoded) {
|
|
868
|
+
this.unreadable.add(row.id);
|
|
869
|
+
return undefined;
|
|
870
|
+
}
|
|
871
|
+
this.unreadable.delete(row.id);
|
|
872
|
+
const { run, extras } = decoded;
|
|
873
|
+
if (this.coldBase.size === 0)
|
|
874
|
+
queueMicrotask(() => this.coldBase.clear());
|
|
875
|
+
const family = rowFamily(row);
|
|
876
|
+
this.coldBase.set(row.id, { revision: row.revision, seq: row.seq, data: row.data, extras, owned: this.claimed.has(family) || this.pendingClaims.has(family) });
|
|
877
|
+
reconcileLoadedRun(run, { keepLive: this.keepLive });
|
|
878
|
+
rescopeRun(run, this.repoHandle);
|
|
879
|
+
return run;
|
|
880
|
+
}
|
|
881
|
+
/** Hold a writable row. A fallback family's reads stay detached (the cold-read contract). */
|
|
882
|
+
holdDecoded(run) {
|
|
883
|
+
if (this.db?.familyHasFallback(familyKey(run)))
|
|
884
|
+
return process.env.VITEST ? deepFreeze(run) : run;
|
|
885
|
+
const read = this.coldBase.get(run.id);
|
|
886
|
+
this.held.set(run.id, run);
|
|
887
|
+
if (read)
|
|
888
|
+
this.base.set(run.id, storedRow(read));
|
|
889
|
+
return run;
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* Hold a claimed run for a write; leave after settlement unless anchored.
|
|
893
|
+
* Take the family claim before reading its row. Return undefined for a live foreign owner
|
|
894
|
+
* or a fallback family: neither claim is granted.
|
|
895
|
+
*/
|
|
896
|
+
hold(id) {
|
|
897
|
+
const held = this.held.get(id);
|
|
898
|
+
if (held)
|
|
899
|
+
return held;
|
|
900
|
+
const family = this.db && !this.deleted.has(id) ? this.db.familyOf(id) : undefined;
|
|
901
|
+
if (family === undefined || !this.claimFamilies([family]).has(family))
|
|
902
|
+
return undefined;
|
|
903
|
+
const run = this.coldReads.claimed(id, this.coldBase.get(id), () => this.db?.revisionOf(id)) ?? this.loadCold(id);
|
|
904
|
+
if (!run)
|
|
905
|
+
return undefined;
|
|
906
|
+
this.holdDecoded(run);
|
|
907
|
+
this.scheduleSave();
|
|
908
|
+
return run;
|
|
909
|
+
}
|
|
910
|
+
/** The record a store write changes in place: held, or loaded to be. Undefined for a run this
|
|
911
|
+
* store may not write (another process's, or quarantined), so the write changes nothing. */
|
|
912
|
+
record(id) {
|
|
913
|
+
if (this.quarantined.has(id))
|
|
914
|
+
return undefined;
|
|
915
|
+
return this.held.get(id) ?? this.hold(id);
|
|
916
|
+
}
|
|
917
|
+
/** Read held legacy family members, or a detached cold record. */
|
|
918
|
+
peek(id) {
|
|
919
|
+
const held = this.held.get(id);
|
|
920
|
+
if (held)
|
|
921
|
+
return held;
|
|
922
|
+
const run = this.loadCold(id);
|
|
923
|
+
return run && this.answerCold(run, false);
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* A run just read off its row, as reads answer it: held when its family is in memory (a live
|
|
927
|
+
* family's members are read on every run event), else a copy nobody holds — frozen under
|
|
928
|
+
* vitest when it leaves the store (`freeze`).
|
|
929
|
+
*/
|
|
930
|
+
answerCold(run, freeze) {
|
|
931
|
+
const root = familyRootOf(run);
|
|
932
|
+
if (root !== undefined && this.anchoredFamilies().has(root))
|
|
933
|
+
return this.holdDecoded(run);
|
|
934
|
+
return freeze && process.env.VITEST ? deepFreeze(run) : run;
|
|
935
|
+
}
|
|
936
|
+
/** `getRun` for a row a query already read: the held object, else decoded from `row`. */
|
|
937
|
+
fromRow(row) {
|
|
938
|
+
const held = this.held.get(row.id);
|
|
939
|
+
if (held)
|
|
940
|
+
return held;
|
|
941
|
+
if (this.deleted.has(row.id))
|
|
942
|
+
return undefined;
|
|
943
|
+
const cached = this.coldReads.at(row.id, row.revision);
|
|
944
|
+
if (cached)
|
|
945
|
+
return cached;
|
|
946
|
+
const run = this.decodeCold(row);
|
|
947
|
+
return run && this.coldReads.keep(this.answerCold(run, true), this.coldBase.get(row.id), this.held.has(row.id));
|
|
948
|
+
}
|
|
949
|
+
/** Run `fn` reading each run that is not held at most once (`ColdReadCache`, hearsay-tools/cezarion#2378). */
|
|
950
|
+
withReadCache(fn) { return this.coldReads.scope(fn); }
|
|
951
|
+
/** Whether the run exists, held or not, without decoding it. */
|
|
952
|
+
hasRun(id) {
|
|
953
|
+
return this.held.has(id) || (!!this.db && !this.deleted.has(id) && this.db.has(id));
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
956
|
+
* Keep a run in memory for `holder` until `unpin(id, holder)`: a RunManager pins the runs it is
|
|
957
|
+
* executing (`active`) and the run a Continue is admitting (`continue`). A finished run is
|
|
958
|
+
* loaded. Pins are per holder, so releasing one never releases another. Returns the held record.
|
|
959
|
+
*/
|
|
960
|
+
pin(id, holder) {
|
|
961
|
+
const run = this.record(id);
|
|
962
|
+
if (!run)
|
|
963
|
+
return undefined;
|
|
964
|
+
let holders = this.pins.get(id);
|
|
965
|
+
if (!holders)
|
|
966
|
+
this.pins.set(id, holders = new Set());
|
|
967
|
+
holders.add(holder);
|
|
968
|
+
return run;
|
|
969
|
+
}
|
|
970
|
+
/** Publish a run's current record again without a write: something read beside the record
|
|
971
|
+
* changed, such as the run leaving the manager's live registries, which a detail's finish
|
|
972
|
+
* verdict reads (hearsay-tools/cezarion#1301). */
|
|
973
|
+
republish(id) {
|
|
974
|
+
const run = this.peek(id);
|
|
975
|
+
if (run)
|
|
976
|
+
this.emit('run', run, 'live-release');
|
|
977
|
+
}
|
|
978
|
+
/** Release `holder`'s pin. The run leaves memory at the next sweep once nothing else holds it. */
|
|
979
|
+
unpin(id, holder) {
|
|
980
|
+
const holders = this.pins.get(id);
|
|
981
|
+
if (!holders?.delete(holder))
|
|
982
|
+
return;
|
|
983
|
+
if (holders.size === 0)
|
|
984
|
+
this.pins.delete(id);
|
|
985
|
+
this.sweepIfSettled([id]);
|
|
986
|
+
}
|
|
987
|
+
/**
|
|
988
|
+
* Set discovered repository identity without blocking boot. Recompute conclusions from retained
|
|
989
|
+
* evidence, including cold records; null retains unknown-handle behavior.
|
|
990
|
+
*/
|
|
991
|
+
setRepoHandle(handle) {
|
|
992
|
+
this.repoHandle = handle;
|
|
993
|
+
if (!handle)
|
|
994
|
+
return; // nothing to prove foreign against
|
|
995
|
+
// `touch` per healed run: the cockpit is already live when the handle lands, so a corrected
|
|
996
|
+
// chip has to reach the open page over SSE, not just the next `runs.json` write.
|
|
997
|
+
let healed = false;
|
|
998
|
+
const own = `${handle.owner}/${handle.name}`.toLowerCase();
|
|
999
|
+
const foreign = (url) => url !== undefined && refUrlRepo(url) !== own;
|
|
1000
|
+
const stored = this.db?.listWhere(REFERENCED_RUN_RULE.sql).filter((row) => {
|
|
1001
|
+
if (this.held.has(row.id) || this.deleted.has(row.id))
|
|
1002
|
+
return false;
|
|
1003
|
+
const summary = parseStoredSummary(row.summary);
|
|
1004
|
+
return !summary || foreign(summary.referencedPullRequestUrl) || foreign(summary.referencedIssueUrl);
|
|
1005
|
+
}) ?? [];
|
|
1006
|
+
for (const run of [...this.held.values()]) {
|
|
1007
|
+
if (rescopeRun(run, this.repoHandle)) {
|
|
1008
|
+
this.touch(run);
|
|
1009
|
+
healed = true;
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
// Only rows this store may write: another process's runs heal when their owner arms its handle.
|
|
1013
|
+
const writable = this.claimFamilies(stored.map(rowFamily));
|
|
1014
|
+
for (const row of stored) {
|
|
1015
|
+
if (!writable.has(rowFamily(row)))
|
|
1016
|
+
continue;
|
|
1017
|
+
const run = this.decodeCold(row);
|
|
1018
|
+
// `decodeCold` already scoped it with the handle just armed; write it when that changed it.
|
|
1019
|
+
if (!run || !this.storedScopeDiffers(row, run))
|
|
1020
|
+
continue;
|
|
1021
|
+
this.holdDecoded(run);
|
|
1022
|
+
this.touch(run);
|
|
1023
|
+
healed = true;
|
|
1024
|
+
}
|
|
1025
|
+
// Discovery may finish after headless shutdown's final flush. Persist repairs now: the
|
|
1026
|
+
// debounced save is unref'd, so it cannot keep the CLI alive once the lookup completes.
|
|
1027
|
+
if (healed)
|
|
1028
|
+
this.flush();
|
|
1029
|
+
}
|
|
1030
|
+
/** Whether the stored summary still names a reference the scoped record no longer has. */
|
|
1031
|
+
storedScopeDiffers(row, run) {
|
|
1032
|
+
const summary = parseStoredSummary(row.summary);
|
|
1033
|
+
return summary?.referencedPullRequestUrl !== run.referencedPullRequestUrl ||
|
|
1034
|
+
summary?.referencedIssueUrl !== run.referencedIssueUrl || summary?.issueNumber !== run.issueNumber;
|
|
1035
|
+
}
|
|
1036
|
+
/**
|
|
1037
|
+
* The live set, newest first: runs whose record is live (`isLiveRecord`) or that a RunManager
|
|
1038
|
+
* pins. Finished runs are not here — read one by id (`getRun`), by an indexed query
|
|
1039
|
+
* (`listWorkersOf`, `findRunByClientRequestId`, `listRunsWithWorktree`,
|
|
1040
|
+
* `listBranchOwners`), or as list rows (`listRunSummaries`).
|
|
1041
|
+
*/
|
|
1042
|
+
listRuns() {
|
|
1043
|
+
return sortNewestFirst([...this.held].filter(([id, run]) => isLiveRecord(run) || this.pins.has(id)).map(([, run]) => [run, this.listOrder(run)]));
|
|
1044
|
+
}
|
|
1045
|
+
/** Where a held run lists: its row's insertion order, or after every row while this store has
|
|
1046
|
+
* not written it yet. The held set keeps such runs in creation order, and so does every list
|
|
1047
|
+
* that sorts them in held-set order (`sortNewestFirst` is stable). */
|
|
1048
|
+
listOrder(run) {
|
|
1049
|
+
return { createdAt: run.createdAt, seq: this.base.get(run.id)?.seq ?? Number.POSITIVE_INFINITY };
|
|
1050
|
+
}
|
|
1051
|
+
/**
|
|
1052
|
+
* EVERY run as a full record, newest first — decoded row by row. Only the legacy `GET /runs`
|
|
1053
|
+
* (older clients; the cockpit reads `GET /run-summaries`) may call this: it is exactly the
|
|
1054
|
+
* whole-history parse #779 exists to remove from everything else.
|
|
1055
|
+
*/
|
|
1056
|
+
listAllRunsForLegacyRoute() {
|
|
1057
|
+
const runs = new Map();
|
|
1058
|
+
for (const row of this.db?.listAll() ?? []) {
|
|
1059
|
+
if (this.deleted.has(row.id) || this.held.has(row.id))
|
|
1060
|
+
continue;
|
|
1061
|
+
const run = this.decodeCold(row);
|
|
1062
|
+
if (run)
|
|
1063
|
+
runs.set(row.id, [run, row]);
|
|
1064
|
+
}
|
|
1065
|
+
for (const [id, run] of this.held)
|
|
1066
|
+
runs.set(id, [run, this.listOrder(run)]);
|
|
1067
|
+
return sortNewestFirst(runs.values());
|
|
1068
|
+
}
|
|
1069
|
+
/** A bounded page of discoverable peer roots, including the current held overlay. */
|
|
1070
|
+
listPeerTaskSummaries(options) {
|
|
1071
|
+
return listPeerTaskSummaries({ db: this.db, held: this.held, skipped: this.deleted,
|
|
1072
|
+
order: run => this.listOrder(run), cold: id => this.coldSummary(id) }, options);
|
|
1073
|
+
}
|
|
1074
|
+
/**
|
|
1075
|
+
* Every run as its list row, newest first: the stored `summary` column, with each held run's
|
|
1076
|
+
* own projection laid over it — a debounced save keeps memory up to 300 ms ahead of the row,
|
|
1077
|
+
* and a run created since the last save has no row yet. `usage` is the caller's to attach.
|
|
1078
|
+
*
|
|
1079
|
+
* With `archivedWindow` (#864), the window the cockpit's lists read: every unarchived run, plus
|
|
1080
|
+
* the newest `archivedWindow` archived ROOT runs, and whether older archived roots were left
|
|
1081
|
+
* out. Archived workers are never in it (see `RunDatabase.listWindowSummaries`); with `roots`,
|
|
1082
|
+
* no worker is — the runs index, whose readers list no workers. A held run
|
|
1083
|
+
* lists by its record, so one archived or unarchived since the last save is ranked as it is now.
|
|
1084
|
+
*
|
|
1085
|
+
* A row this store found unreadable is left out, as the cold reader leaves out a row it had to
|
|
1086
|
+
* decode and could not. A finished row nobody has decoded is served from its summary by both,
|
|
1087
|
+
* which decodes nothing; reading it is what finds out (`isUnreadable`).
|
|
1088
|
+
*/
|
|
1089
|
+
listRunSummaries(options = {}) {
|
|
1090
|
+
const { archivedWindow, roots = false } = options;
|
|
1091
|
+
// Every skipped row may be an archived root, so ask for that many more: one past the window
|
|
1092
|
+
// after skipping is how "older ones were left out" is known without counting them.
|
|
1093
|
+
const rows = (archivedWindow === undefined
|
|
1094
|
+
? this.db?.listSummaries()
|
|
1095
|
+
: this.db?.listWindowSummaries(archivedWindow + 1 + this.deleted.size + this.unreadable.size + this.held.size, { roots })) ?? [];
|
|
1096
|
+
const summaries = new Map();
|
|
1097
|
+
for (const row of rows) {
|
|
1098
|
+
if (this.deleted.has(row.id) || this.held.has(row.id) || this.unreadable.has(row.id))
|
|
1099
|
+
continue;
|
|
1100
|
+
const summary = parseStoredSummary(row.summary) ?? this.coldSummary(row.id);
|
|
1101
|
+
if (summary)
|
|
1102
|
+
summaries.set(row.id, [summary, row]);
|
|
1103
|
+
}
|
|
1104
|
+
for (const [id, run] of this.held) {
|
|
1105
|
+
if (archivedWindow !== undefined && (run.archived || roots) && run.delegation?.role === 'worker')
|
|
1106
|
+
continue;
|
|
1107
|
+
summaries.set(id, [toRunSummary(run), this.listOrder(run)]);
|
|
1108
|
+
}
|
|
1109
|
+
const runs = sortNewestFirst(summaries.values());
|
|
1110
|
+
if (archivedWindow === undefined)
|
|
1111
|
+
return { runs, truncated: false };
|
|
1112
|
+
let archived = 0;
|
|
1113
|
+
const kept = runs.filter((run) => !run.archived || ++archived <= archivedWindow);
|
|
1114
|
+
return { runs: kept, truncated: archived > archivedWindow };
|
|
1115
|
+
}
|
|
1116
|
+
/**
|
|
1117
|
+
* One page of archived ROOT runs, newest first (#864): `GET /run-summaries/archived`, the
|
|
1118
|
+
* archived runs past the window. Held runs list by their record, as in `listRunSummaries`.
|
|
1119
|
+
* `before` is the previous page's `nextCursor`; with `q`, only runs `matchesRunQuery` keeps, and
|
|
1120
|
+
* `total` counts those. `automationNames` lets `q` match a task by its automation's name, as the
|
|
1121
|
+
* list's origin line shows it (hearsay-tools/cezarion#1616). A cursor this store did not write
|
|
1122
|
+
* answers `{ error }`.
|
|
1123
|
+
*
|
|
1124
|
+
* Ranking reads every archived root's key (id, created_at, seq: no record, no summary), so the
|
|
1125
|
+
* page and `total` agree with the held overlay; only the page's own rows are read in full.
|
|
1126
|
+
*/
|
|
1127
|
+
listArchivedRuns(options) {
|
|
1128
|
+
let after;
|
|
1129
|
+
if (options.before !== undefined) {
|
|
1130
|
+
after = decodeArchivedCursor(options.before);
|
|
1131
|
+
if (!after)
|
|
1132
|
+
return { error: 'before is not a cursor this server wrote' };
|
|
1133
|
+
}
|
|
1134
|
+
const q = options.q?.trim() ?? '';
|
|
1135
|
+
const ranked = this.rankArchivedRows(q, options.automationNames);
|
|
1136
|
+
this.overlayHeldArchived(ranked, q, options.automationNames);
|
|
1137
|
+
const ordered = [...ranked.entries()].sort(([, a], [, b]) => newestFirst(a, b));
|
|
1138
|
+
return this.archivedPage(ordered, after, options.limit);
|
|
1139
|
+
}
|
|
1140
|
+
/** The archived roots in `runs.db`, each by its key: every one without `q` (no record, no
|
|
1141
|
+
* summary read), and with `q` the searched rows `matchesRunSearch` keeps, which carry the summary
|
|
1142
|
+
* the search read. A deleted, held or unreadable run's row is left out; a held run joins in
|
|
1143
|
+
* `overlayHeldArchived`. */
|
|
1144
|
+
rankArchivedRows(q, automationNames) {
|
|
1145
|
+
const skipped = (id) => this.deleted.has(id) || this.held.has(id) || this.unreadable.has(id);
|
|
1146
|
+
const ranked = new Map();
|
|
1147
|
+
if (q === '') {
|
|
1148
|
+
for (const key of this.db?.listKeysWhere(ARCHIVED_ROOT) ?? [])
|
|
1149
|
+
if (!skipped(key.id))
|
|
1150
|
+
ranked.set(key.id, key);
|
|
1151
|
+
}
|
|
1152
|
+
else {
|
|
1153
|
+
for (const row of this.db?.searchRootSummaries(sqlPrefilterTokens(q, { automationNames })) ?? []) {
|
|
1154
|
+
if (skipped(row.id))
|
|
1155
|
+
continue;
|
|
1156
|
+
const summary = parseStoredSummary(row.summary) ?? this.coldSummary(row.id);
|
|
1157
|
+
if (summary?.archived && matchesRunSearch(summary, q, { automationNames }))
|
|
1158
|
+
ranked.set(row.id, { ...row, summary });
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
return ranked;
|
|
1162
|
+
}
|
|
1163
|
+
/** Lay the held archived roots over `ranked`, each by its record: it replaces its stored row
|
|
1164
|
+
* (already left out of `ranked`), and with `q` it must match. A held worker never lists. */
|
|
1165
|
+
overlayHeldArchived(ranked, q, automationNames) {
|
|
1166
|
+
for (const [id, run] of this.held) {
|
|
1167
|
+
if (!run.archived || run.delegation?.role === 'worker')
|
|
1168
|
+
continue;
|
|
1169
|
+
const summary = toRunSummary(run);
|
|
1170
|
+
if (q === '' || matchesRunSearch(summary, q, { automationNames }))
|
|
1171
|
+
ranked.set(id, { ...this.listOrder(run), summary });
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
/** The page of `ordered` after the cursor `after`: its summaries, the cursor of the next page,
|
|
1175
|
+
* and `total`, the count of all of `ordered`. */
|
|
1176
|
+
archivedPage(ordered, after, limit) {
|
|
1177
|
+
const rest = after ? ordered.filter(([, key]) => newestFirst(key, after) > 0) : ordered;
|
|
1178
|
+
const page = rest.slice(0, limit);
|
|
1179
|
+
const runs = this.hydrateArchivedPage(page);
|
|
1180
|
+
const last = page.at(-1)?.[1];
|
|
1181
|
+
return {
|
|
1182
|
+
runs,
|
|
1183
|
+
nextCursor: last && rest.length > page.length ? encodeArchivedCursor(last) : null,
|
|
1184
|
+
total: ordered.length,
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
/** The page's summaries: a ranked key's own, else its stored one, read for the rows that have
|
|
1188
|
+
* none in a single query. A row whose stored summary does not fit the contract is projected from
|
|
1189
|
+
* its record; a row that cannot be read at all is left out. */
|
|
1190
|
+
hydrateArchivedPage(page) {
|
|
1191
|
+
const cold = new Map((this.db?.getSummaries(page.filter(([, key]) => !key.summary).map(([id]) => id)) ?? [])
|
|
1192
|
+
.map((row) => [row.id, parseStoredSummary(row.summary) ?? this.coldSummary(row.id)]));
|
|
1193
|
+
return page.flatMap(([id, key]) => {
|
|
1194
|
+
const summary = key.summary ?? cold.get(id);
|
|
1195
|
+
return summary ? [summary] : [];
|
|
1196
|
+
});
|
|
1197
|
+
}
|
|
1198
|
+
/**
|
|
1199
|
+
* The ROOT runs, archived or not, that match `query` (`matchesRunSearch`), newest first: at most
|
|
1200
|
+
* `limit`, and whether more matched. The owned half of the workspace search (#864); held runs
|
|
1201
|
+
* match by their record.
|
|
1202
|
+
*/
|
|
1203
|
+
searchRunSummaries(query, limit) {
|
|
1204
|
+
const matched = new Map();
|
|
1205
|
+
// Rows come newest first, so the newest `limit + 1` row matches are all a page can use; held
|
|
1206
|
+
// runs, which may be newer than any row, are ranked in below.
|
|
1207
|
+
this.db?.visitRootSummaries(sqlPrefilterTokens(query), (row) => {
|
|
1208
|
+
if (this.deleted.has(row.id) || this.held.has(row.id) || this.unreadable.has(row.id))
|
|
1209
|
+
return true;
|
|
1210
|
+
const summary = parseStoredSummary(row.summary) ?? this.coldSummary(row.id);
|
|
1211
|
+
if (summary && matchesRunSearch(summary, query))
|
|
1212
|
+
matched.set(row.id, [summary, row]);
|
|
1213
|
+
return matched.size <= limit;
|
|
1214
|
+
});
|
|
1215
|
+
for (const [id, run] of this.held) {
|
|
1216
|
+
if (run.delegation?.role === 'worker')
|
|
1217
|
+
continue;
|
|
1218
|
+
const summary = toRunSummary(run);
|
|
1219
|
+
if (matchesRunSearch(summary, query))
|
|
1220
|
+
matched.set(id, [summary, this.listOrder(run)]);
|
|
1221
|
+
}
|
|
1222
|
+
const runs = sortNewestFirst(matched.values());
|
|
1223
|
+
return { runs: runs.slice(0, limit), truncated: runs.length > limit };
|
|
1224
|
+
}
|
|
1225
|
+
/** A row whose stored summary does not fit the contract any more, projected from its record. */
|
|
1226
|
+
coldSummary(id) {
|
|
1227
|
+
const run = this.loadCold(id);
|
|
1228
|
+
return run ? toRunSummary(run) : undefined;
|
|
1229
|
+
}
|
|
1230
|
+
/**
|
|
1231
|
+
* The run, or undefined. A held run is the held object (writes install into it). Any other run
|
|
1232
|
+
* is a fresh copy of its row each call, normalized like a loaded record — frozen under vitest —
|
|
1233
|
+
* so changing it means a store method, never an assignment.
|
|
1234
|
+
*/
|
|
1235
|
+
getRun(id) {
|
|
1236
|
+
const held = this.held.get(id);
|
|
1237
|
+
if (held)
|
|
1238
|
+
return held;
|
|
1239
|
+
const cached = this.deleted.has(id) || !this.db ? undefined : this.coldReads.at(id, this.db.revisionOf(id));
|
|
1240
|
+
if (cached)
|
|
1241
|
+
return cached;
|
|
1242
|
+
const run = this.loadCold(id);
|
|
1243
|
+
return run && this.coldReads.keep(this.answerCold(run, true), this.coldBase.get(id), this.held.has(id));
|
|
1244
|
+
}
|
|
1245
|
+
/** Whether the run's row is in `runs.db` but its record could not be read here: what tells a
|
|
1246
|
+
* run that cannot open from one that does not exist. Known once something tried to read it. */
|
|
1247
|
+
isUnreadable(id) {
|
|
1248
|
+
return this.unreadable.has(id) && !this.held.has(id) && !this.deleted.has(id);
|
|
1249
|
+
}
|
|
1250
|
+
/** The ids in memory right now: the held set, for diagnostics and the benchmark's heap pass. */
|
|
1251
|
+
heldIds() {
|
|
1252
|
+
return [...this.held.keys()];
|
|
1253
|
+
}
|
|
1254
|
+
/**
|
|
1255
|
+
* The workers a root run owns, newest first, read off its own `receipts` (#659). Every
|
|
1256
|
+
* `createOwnedRun` writes the receipt and the worker record in one transaction, and the
|
|
1257
|
+
* receipt list is capped at 1,024 creations (#816), so this is a bounded lookup rather than a walk of the whole
|
|
1258
|
+
* project index. An ordinary or worker run owns nothing and costs one map read; a receipt
|
|
1259
|
+
* whose record has been deleted is skipped, never invented. A parent quarantined to
|
|
1260
|
+
* `invalid` has no readable receipts and reports none, which is also what the cockpit
|
|
1261
|
+
* assumes for that role.
|
|
1262
|
+
*/
|
|
1263
|
+
listOwnedWorkers(parentId) {
|
|
1264
|
+
const parent = this.getRun(parentId);
|
|
1265
|
+
if (parent?.delegation?.role !== 'root')
|
|
1266
|
+
return [];
|
|
1267
|
+
const workers = [];
|
|
1268
|
+
for (const receipt of parent.delegation.receipts) {
|
|
1269
|
+
const worker = this.getRun(receipt.workerId);
|
|
1270
|
+
if (worker?.delegation?.role === 'worker' && worker.delegation.parentRunId === parentId)
|
|
1271
|
+
workers.push(worker);
|
|
1272
|
+
}
|
|
1273
|
+
return workers.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
1274
|
+
}
|
|
1275
|
+
/** Every worker filed under `parentId` (the `parent_run_id` index plus memory), newest first:
|
|
1276
|
+
* receipts or not, which is what the reconcile and deletion checks ask. */
|
|
1277
|
+
listWorkersOf(parentId) {
|
|
1278
|
+
const workers = new Map();
|
|
1279
|
+
for (const run of this.held.values()) {
|
|
1280
|
+
if (run.delegation?.role === 'worker' && run.delegation.parentRunId === parentId)
|
|
1281
|
+
workers.set(run.id, [run, this.listOrder(run)]);
|
|
1282
|
+
}
|
|
1283
|
+
// Ids first: a live family's workers are usually all held, and then no record is read at all.
|
|
1284
|
+
const cold = (this.db?.listIdsByParent(parentId) ?? []).filter((id) => !this.held.has(id) && !this.deleted.has(id));
|
|
1285
|
+
for (const row of cold.length > 0 ? this.db.getMany(cold) : []) {
|
|
1286
|
+
const run = this.fromRow(row);
|
|
1287
|
+
if (run?.delegation?.role === 'worker' && run.delegation.parentRunId === parentId)
|
|
1288
|
+
workers.set(row.id, [run, row]);
|
|
1289
|
+
}
|
|
1290
|
+
return sortNewestFirst(workers.values());
|
|
1291
|
+
}
|
|
1292
|
+
/** The id of every worker, held or stored, without decoding any (recovery's worker passes). */
|
|
1293
|
+
listWorkerIds() {
|
|
1294
|
+
const ids = new Set(this.db?.listWorkerIds() ?? []);
|
|
1295
|
+
for (const id of this.deleted)
|
|
1296
|
+
ids.delete(id);
|
|
1297
|
+
for (const run of this.held.values())
|
|
1298
|
+
if (run.delegation?.role === 'worker')
|
|
1299
|
+
ids.add(run.id);
|
|
1300
|
+
return [...ids];
|
|
1301
|
+
}
|
|
1302
|
+
/** The id of every run whose delegation is quarantined (`invalid`), held or stored: its stored
|
|
1303
|
+
* summary says so, so nothing is decoded (scratch cleanup). */
|
|
1304
|
+
listQuarantinedRunIds() {
|
|
1305
|
+
const ids = new Set((this.db?.listKeysWhere("json_extract(summary, '$.delegation.role') = 'invalid'") ?? []).map((key) => key.id));
|
|
1306
|
+
for (const id of this.deleted)
|
|
1307
|
+
ids.delete(id);
|
|
1308
|
+
for (const [id, run] of this.held) {
|
|
1309
|
+
if (run.delegation?.role === 'invalid')
|
|
1310
|
+
ids.add(id);
|
|
1311
|
+
else
|
|
1312
|
+
ids.delete(id);
|
|
1313
|
+
}
|
|
1314
|
+
return [...ids];
|
|
1315
|
+
}
|
|
1316
|
+
/** All retained tasks this automation started, including variants, finished and archived runs.
|
|
1317
|
+
* Logs and receipts may compact independently. Read only matching keys in SQLite and overlay
|
|
1318
|
+
* held records so a debounced provenance update or deletion is reflected immediately. */
|
|
1319
|
+
countAutomationRuns(automationId) {
|
|
1320
|
+
const where = "json_extract(data, '$.automation.automationId') = ? OR " +
|
|
1321
|
+
"json_extract(data, '$.automationTrigger.automationId') = ?";
|
|
1322
|
+
const ids = new Set((this.db?.listKeysWhere(where, [automationId, automationId]) ?? [])
|
|
1323
|
+
.map((key) => key.id));
|
|
1324
|
+
for (const id of this.deleted)
|
|
1325
|
+
ids.delete(id);
|
|
1326
|
+
for (const [id, run] of this.held) {
|
|
1327
|
+
if (run.automation?.automationId === automationId || run.automationTrigger?.automationId === automationId)
|
|
1328
|
+
ids.add(id);
|
|
1329
|
+
else
|
|
1330
|
+
ids.delete(id);
|
|
1331
|
+
}
|
|
1332
|
+
return ids.size;
|
|
1333
|
+
}
|
|
1334
|
+
/** First non-archived task referencing a project issue, including secondary references. */
|
|
1335
|
+
findNonArchivedRunByIssue(repo, number) {
|
|
1336
|
+
const url = `https://github.com/${repo}/issues/${number}`.toLowerCase();
|
|
1337
|
+
const matches = (run) => {
|
|
1338
|
+
if (run.archived)
|
|
1339
|
+
return false;
|
|
1340
|
+
const urls = [run.referencedIssueUrl, ...(run.referencedIssueCandidates ?? []),
|
|
1341
|
+
...(run.automation?.event.startsWith('issue.') ? [run.automation.githubUrl] : []),
|
|
1342
|
+
...[...run.task.matchAll(new RegExp(ISSUE_URL_RE.source, 'gi'))].map((match) => match[0])]
|
|
1343
|
+
.filter((value) => value !== undefined)
|
|
1344
|
+
.map((value) => value.replace(/\/+$/, '').toLowerCase())
|
|
1345
|
+
.filter((value) => value.endsWith(`/issues/${number}`));
|
|
1346
|
+
// The legacy issueNumber is display-only and cannot establish repository identity.
|
|
1347
|
+
// Explicit task markers remain project-scoped unless a qualified URL contradicts them.
|
|
1348
|
+
return urls.includes(url) || (urls.length === 0 && run.markerRefs?.issue === number);
|
|
1349
|
+
};
|
|
1350
|
+
for (const run of this.held.values())
|
|
1351
|
+
if (matches(run))
|
|
1352
|
+
return run.id;
|
|
1353
|
+
// Secondary references are record-only: query them in SQLite rather than hydrating every
|
|
1354
|
+
// finished transcript or relying on the slim summary's primary issue number.
|
|
1355
|
+
const where = `archived = 0 AND (
|
|
1356
|
+
json_extract(data, '$.issueNumber') = ? OR json_extract(data, '$.markerRefs.issue') = ?
|
|
1357
|
+
OR lower(rtrim(json_extract(data, '$.referencedIssueUrl'), '/')) = ?
|
|
1358
|
+
OR EXISTS (SELECT 1 FROM json_each(data, '$.referencedIssueCandidates') WHERE lower(rtrim(value, '/')) = ?)
|
|
1359
|
+
OR (json_extract(data, '$.automation.event') LIKE 'issue.%' AND lower(rtrim(json_extract(data, '$.automation.githubUrl'), '/')) = ?)
|
|
1360
|
+
OR instr(lower(json_extract(data, '$.task')), ?) > 0
|
|
1361
|
+
)`;
|
|
1362
|
+
for (const key of this.db?.listKeysWhere(where, [number, number, url, url, url, url]) ?? []) {
|
|
1363
|
+
if (this.held.has(key.id) || this.deleted.has(key.id) || this.unreadable.has(key.id))
|
|
1364
|
+
continue;
|
|
1365
|
+
const run = this.getRun(key.id);
|
|
1366
|
+
if (run && matches(run))
|
|
1367
|
+
return key.id;
|
|
1368
|
+
}
|
|
1369
|
+
return undefined;
|
|
1370
|
+
}
|
|
1371
|
+
/**
|
|
1372
|
+
* The runs automation receipts launched, by receipt id (`automation.receiptId` or
|
|
1373
|
+
* `automationTrigger.receiptId`). Memory answers first; a receipt it does not know costs one
|
|
1374
|
+
* pass over `runs.db` that reads the records' JSON in SQLite — only a crash leftover (a
|
|
1375
|
+
* receipt still `reserved` at startup) ever asks.
|
|
1376
|
+
*/
|
|
1377
|
+
findRunIdsByAutomationReceipt(receiptIds) {
|
|
1378
|
+
const wanted = new Set(receiptIds);
|
|
1379
|
+
const found = new Map();
|
|
1380
|
+
const receiptOf = (run) => run.automation?.receiptId ?? run.automationTrigger?.receiptId;
|
|
1381
|
+
for (const run of this.held.values()) {
|
|
1382
|
+
const receipt = receiptOf(run);
|
|
1383
|
+
if (receipt !== undefined && wanted.has(receipt))
|
|
1384
|
+
found.set(receipt, run.id);
|
|
1385
|
+
}
|
|
1386
|
+
const missing = [...wanted].filter((receipt) => !found.has(receipt));
|
|
1387
|
+
if (missing.length === 0 || !this.db)
|
|
1388
|
+
return found;
|
|
1389
|
+
const where = "json_extract(data, '$.automation.receiptId') IN (SELECT value FROM json_each(?))" +
|
|
1390
|
+
" OR json_extract(data, '$.automationTrigger.receiptId') IN (SELECT value FROM json_each(?))";
|
|
1391
|
+
for (const key of this.db.listKeysWhere(where, [JSON.stringify(missing), JSON.stringify(missing)])) {
|
|
1392
|
+
if (this.held.has(key.id) || this.deleted.has(key.id))
|
|
1393
|
+
continue;
|
|
1394
|
+
const run = this.getRun(key.id);
|
|
1395
|
+
const receipt = run && receiptOf(run);
|
|
1396
|
+
if (receipt !== undefined && wanted.has(receipt) && !found.has(receipt))
|
|
1397
|
+
found.set(receipt, key.id);
|
|
1398
|
+
}
|
|
1399
|
+
return found;
|
|
1400
|
+
}
|
|
1401
|
+
/** The id of every root whose delegation carries a conversation, held or stored: recovery's full
|
|
1402
|
+
* reconcile pass (#661). Only roots' records are read, in SQLite. `naming` keeps the ledgers whose
|
|
1403
|
+
* messages name that run id, filtered there too, so a deleted peer decodes no other root. */
|
|
1404
|
+
listConversationRootIds(naming) {
|
|
1405
|
+
const where = `json_extract(summary, '$.delegation.role') = 'root' AND json_extract(data, '$.delegation.conversation') IS NOT NULL${naming ? " AND instr(json_extract(data, '$.delegation.conversation.messages'), ?) > 0" : ''}`;
|
|
1406
|
+
const ids = new Set((this.db?.listKeysWhere(where, naming ? [naming] : []) ?? []).map((key) => key.id));
|
|
1407
|
+
for (const id of this.deleted)
|
|
1408
|
+
ids.delete(id);
|
|
1409
|
+
for (const [id, run] of this.held) {
|
|
1410
|
+
if (run.delegation?.role === 'root' && run.delegation.conversation && (!naming || run.delegation.conversation.messages.some(message => [message.senderRunId, message.recipientRunId].includes(naming))))
|
|
1411
|
+
ids.add(id);
|
|
1412
|
+
else
|
|
1413
|
+
ids.delete(id);
|
|
1414
|
+
}
|
|
1415
|
+
return [...ids];
|
|
1416
|
+
}
|
|
1417
|
+
/** The id of every run, held or stored, without decoding any (orphan-worktree sweeps). */
|
|
1418
|
+
listRunIds() {
|
|
1419
|
+
const ids = new Set(this.db?.listIds() ?? []);
|
|
1420
|
+
for (const id of this.deleted)
|
|
1421
|
+
ids.delete(id);
|
|
1422
|
+
for (const id of this.held.keys())
|
|
1423
|
+
ids.add(id);
|
|
1424
|
+
return [...ids];
|
|
1425
|
+
}
|
|
1426
|
+
/** Runs with a materialized worktree directory (`worktreePath`, not reclaimed), most recently
|
|
1427
|
+
* finished first, then newest first: worktree retention and the worktrees panel. */
|
|
1428
|
+
listRunsWithWorktree() {
|
|
1429
|
+
const runs = this.queryRuns(this.db?.listWithWorktree() ?? [], (run) => run.worktreePath !== undefined && run.worktreeReclaimedAt === undefined);
|
|
1430
|
+
const recency = ([run]) => run.finishedAt ?? run.createdAt;
|
|
1431
|
+
return runs.sort((a, b) => (recency(a) < recency(b) ? 1 : recency(a) > recency(b) ? -1 : newestFirst(a[1], b[1]))).map(([run]) => run);
|
|
1432
|
+
}
|
|
1433
|
+
/**
|
|
1434
|
+
* Every run that owns a branch or names the PR it created, as the few fields branch cleanup and
|
|
1435
|
+
* git-log attribution read (issue 08 §B3, §B5; a squash commit is attributed by its PR number):
|
|
1436
|
+
* the stored summary plus the `branch` and `base_branch` columns, so a Git-tab request decodes
|
|
1437
|
+
* no record. Held runs answer from memory.
|
|
1438
|
+
*/
|
|
1439
|
+
listBranchOwners() {
|
|
1440
|
+
const owners = new Map();
|
|
1441
|
+
for (const row of this.db?.listBranchOwners("branch IS NOT NULL OR json_extract(summary, '$.pullRequestUrl') IS NOT NULL") ?? []) {
|
|
1442
|
+
if (this.deleted.has(row.id) || this.held.has(row.id))
|
|
1443
|
+
continue;
|
|
1444
|
+
const summary = parseStoredSummary(row.summary);
|
|
1445
|
+
owners.set(row.id, {
|
|
1446
|
+
id: row.id, createdAt: row.createdAt, status: row.status, archived: row.archived,
|
|
1447
|
+
...(row.branch === null ? {} : { branch: row.branch }),
|
|
1448
|
+
...(row.baseBranch === null ? {} : { baseBranch: row.baseBranch }),
|
|
1449
|
+
title: summary?.title ?? row.id,
|
|
1450
|
+
...(summary?.pullRequestUrl === undefined ? {} : { pullRequestUrl: summary.pullRequestUrl }),
|
|
1451
|
+
});
|
|
1452
|
+
}
|
|
1453
|
+
for (const run of this.held.values()) {
|
|
1454
|
+
const owner = branchOwnerOf(run);
|
|
1455
|
+
if (owner)
|
|
1456
|
+
owners.set(run.id, owner);
|
|
1457
|
+
}
|
|
1458
|
+
return [...owners.values()];
|
|
1459
|
+
}
|
|
1460
|
+
/** The rows a column query returned, as records, with memory laid over them: a held run
|
|
1461
|
+
* answers from memory (matching `matches` there, not on its maybe-older row). */
|
|
1462
|
+
queryRuns(rows, matches) {
|
|
1463
|
+
const runs = new Map();
|
|
1464
|
+
for (const run of this.held.values())
|
|
1465
|
+
if (matches(run))
|
|
1466
|
+
runs.set(run.id, [run, this.listOrder(run)]);
|
|
1467
|
+
for (const row of rows) {
|
|
1468
|
+
if (runs.has(row.id) || this.held.has(row.id) || this.deleted.has(row.id))
|
|
1469
|
+
continue;
|
|
1470
|
+
const run = this.fromRow(row);
|
|
1471
|
+
if (run && matches(run))
|
|
1472
|
+
runs.set(row.id, [run, row]);
|
|
1473
|
+
}
|
|
1474
|
+
return [...runs.values()];
|
|
1475
|
+
}
|
|
1476
|
+
/** The run an idempotent start already created (#504), archived or not. A deleted run is gone
|
|
1477
|
+
* with its record, so a retry after deletion honestly creates a fresh one. */
|
|
1478
|
+
findRunByClientRequestId(clientRequestId) {
|
|
1479
|
+
for (const run of this.held.values())
|
|
1480
|
+
if (run.clientRequestId === clientRequestId)
|
|
1481
|
+
return run;
|
|
1482
|
+
const row = this.db?.findByClientRequestId(clientRequestId);
|
|
1483
|
+
if (!row || this.held.has(row.id))
|
|
1484
|
+
return undefined;
|
|
1485
|
+
const run = this.getRun(row.id);
|
|
1486
|
+
return run?.clientRequestId === clientRequestId ? run : undefined;
|
|
1487
|
+
}
|
|
1488
|
+
createRun(input) {
|
|
1489
|
+
if (this.openFailure)
|
|
1490
|
+
throw this.openFailure;
|
|
1491
|
+
const run = this.buildRun(input, randomUUID());
|
|
1492
|
+
// A new family, so nobody else can hold it; claimed before anything (an NDJSON event) is written.
|
|
1493
|
+
if (this.db && !this.claimFamilies([run.id]).has(run.id))
|
|
1494
|
+
throw new RunWriteRefusedError(run.id, 'foreign');
|
|
1495
|
+
this.held.set(run.id, run);
|
|
1496
|
+
this.pruneOldRuns();
|
|
1497
|
+
this.touch(run);
|
|
1498
|
+
return run;
|
|
1499
|
+
}
|
|
1500
|
+
/** Build without publishing: owned creation must commit its receipt and run together. */
|
|
1501
|
+
buildRun(input, id) {
|
|
1502
|
+
const run = {
|
|
1503
|
+
id,
|
|
1504
|
+
// Scrubbed on the way in, exactly as `updateRun` scrubs it on the way
|
|
1505
|
+
// through (#456 review) — a token pasted into the prompt otherwise sat
|
|
1506
|
+
// verbatim in `runs.json` from creation. `task` is deliberately NOT
|
|
1507
|
+
// scrubbed: it is the user's own prompt and is replayed into `{{task}}`
|
|
1508
|
+
// when a queued run is revived after a restart (#367), so redacting it
|
|
1509
|
+
// would corrupt the revived run.
|
|
1510
|
+
title: this.redactText(input.title),
|
|
1511
|
+
workflow: input.workflow,
|
|
1512
|
+
task: input.task,
|
|
1513
|
+
model: input.model,
|
|
1514
|
+
systemPrompt: input.systemPrompt,
|
|
1515
|
+
workflowDef: input.workflowDef === undefined ? undefined : workflowDefSchema.parse(input.workflowDef),
|
|
1516
|
+
effort: input.effort,
|
|
1517
|
+
runner: input.runner,
|
|
1518
|
+
agentProfile: input.agentProfile,
|
|
1519
|
+
autonomous: input.autonomous,
|
|
1520
|
+
archiveWhenDone: input.archiveWhenDone,
|
|
1521
|
+
autoMergePr: input.autoMergePr, allowWorkers: input.allowWorkers, allowMessaging: input.allowMessaging,
|
|
1522
|
+
...(input.notify ? { notify: true } : {}),
|
|
1523
|
+
worktree: input.worktree,
|
|
1524
|
+
groupId: input.groupId,
|
|
1525
|
+
variant: input.variant,
|
|
1526
|
+
...(input.clientRequestId === undefined ? {} : { clientRequestId: input.clientRequestId, clientRequestHash: input.clientRequestHash }),
|
|
1527
|
+
...(input.automation ? { automation: input.automation } : {}),
|
|
1528
|
+
...(input.automationTrigger ? { automationTrigger: input.automationTrigger } : {}),
|
|
1529
|
+
status: 'queued',
|
|
1530
|
+
createdAt: new Date().toISOString(),
|
|
1531
|
+
tokensUsed: 0,
|
|
1532
|
+
archived: false,
|
|
1533
|
+
steps: input.steps.map((s) => ({
|
|
1534
|
+
...s,
|
|
1535
|
+
status: 'pending',
|
|
1536
|
+
iterations: 0,
|
|
1537
|
+
tokensUsed: 0,
|
|
1538
|
+
})),
|
|
1539
|
+
};
|
|
1540
|
+
// A prompt that pastes a PR or issue URL is already about that item — seed
|
|
1541
|
+
// both referenced tiers so queued runs can expose the reference before the
|
|
1542
|
+
// first agent event (#407, #554).
|
|
1543
|
+
trackReferencedPrs(run, input.task, this.repoHandle);
|
|
1544
|
+
trackReferencedIssues(run, input.task, this.repoHandle);
|
|
1545
|
+
return run;
|
|
1546
|
+
}
|
|
1547
|
+
/** Accept the family ledger and its recipient queue in a single durable index replacement. A new
|
|
1548
|
+
* message (`message`, or the delivered input) whose ID is in use writes nothing; `recipientDelegation`
|
|
1549
|
+
* makes a peer recipient with no delegation record a root task in the same write. */
|
|
1550
|
+
commitConversation(rootId, conversation, delivery, message) {
|
|
1551
|
+
const root = this.peek(rootId);
|
|
1552
|
+
if (root?.delegation?.role !== 'root')
|
|
1553
|
+
throw new Error('missing conversation root');
|
|
1554
|
+
assertConversationIdFree(root, message ?? delivery?.input, id => this.peek(id), (runId, id) => this.hasProjection(runId, id));
|
|
1555
|
+
const staged = new Map();
|
|
1556
|
+
stageConversation(staged, root, conversation, delivery, id => this.peek(id), text => this.redactText(text));
|
|
1557
|
+
this.commitIndex(staged);
|
|
1558
|
+
}
|
|
1559
|
+
/** Crash recovery (#505): input a harness accepted but was never seen reading goes
|
|
1560
|
+
* back to the queue. Returns the requeued IDs. */
|
|
1561
|
+
requeueAwaitingReadInputs(id) {
|
|
1562
|
+
const ids = (this.peek(id)?.agentInputs ?? []).filter(input => input.awaitingRead && input.deliveredAt && !input.consumedAt).map(input => input.id);
|
|
1563
|
+
if (ids.length)
|
|
1564
|
+
this.commitOutcome(id, { kind: 'requeued', inputIds: ids });
|
|
1565
|
+
return ids;
|
|
1566
|
+
}
|
|
1567
|
+
/** Delivered, but the harness never confirmed reading it (#505): stop awaiting a read. */
|
|
1568
|
+
commitAgentInputsUnconfirmed(id, ids) {
|
|
1569
|
+
const run = this.peek(id);
|
|
1570
|
+
if (!run?.agentInputs || !ids.length)
|
|
1571
|
+
return;
|
|
1572
|
+
this.commitAgentInputs(id, run.agentInputs.map(input => {
|
|
1573
|
+
if (!ids.includes(input.id) || !input.awaitingRead)
|
|
1574
|
+
return input;
|
|
1575
|
+
const { awaitingRead: _awaiting, ...unconfirmed } = input;
|
|
1576
|
+
return unconfirmed;
|
|
1577
|
+
}));
|
|
1578
|
+
}
|
|
1579
|
+
/** Observable atomic input checkpoint for the queue itself: enqueue, retire, reorder. Delivery
|
|
1580
|
+
* and reads are outcomes (`commitOutcome`). */
|
|
1581
|
+
commitAgentInputs(id, inputs) {
|
|
1582
|
+
const run = this.peek(id);
|
|
1583
|
+
if (!run)
|
|
1584
|
+
throw new Error('missing agent input target');
|
|
1585
|
+
const agentInputs = inputs.map(input => agentInputSchema.parse(input));
|
|
1586
|
+
// Receipts are outcomes (hearsay-tools/cezarion#1805): only `commitOutcome` writes them.
|
|
1587
|
+
const stored = new Map((run.agentInputs ?? []).map(input => [input.id, input]));
|
|
1588
|
+
if (agentInputs.some(input => input.deliveredAt !== stored.get(input.id)?.deliveredAt || input.consumedAt !== stored.get(input.id)?.consumedAt)) {
|
|
1589
|
+
throw new Error('agent input receipts change only through commitOutcome');
|
|
1590
|
+
}
|
|
1591
|
+
const now = new Date().toISOString();
|
|
1592
|
+
for (const input of run.agentInputs ?? []) {
|
|
1593
|
+
const claim = input.inboxClaim;
|
|
1594
|
+
if (!claim || claim.acknowledgedAt || claim.expiresAt <= now)
|
|
1595
|
+
continue;
|
|
1596
|
+
const matching = agentInputs.filter(next => next.id === input.id);
|
|
1597
|
+
if (matching.length !== 1 || !isDeepStrictEqual(matching[0].inboxClaim, claim)) {
|
|
1598
|
+
throw new Error('live inbox receipt changed');
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
this.commitIndex(new Map([[id, { ...run, agentInputs }]]));
|
|
1602
|
+
}
|
|
1603
|
+
/** Reserve only unread conversation inputs in one durable index replacement. */
|
|
1604
|
+
claimInboxInputs(runId, ids, claim) {
|
|
1605
|
+
const run = this.peek(runId);
|
|
1606
|
+
if (!run)
|
|
1607
|
+
throw new Error('missing inbox recipient');
|
|
1608
|
+
const selected = z.array(z.uuid()).min(1).max(32).refine(values => new Set(values).size === values.length).parse(ids);
|
|
1609
|
+
const receipt = inboxClaimSchema.parse({ ...claim, memberIds: selected });
|
|
1610
|
+
if (receipt.acknowledgedAt || receipt.expiresAt <= new Date().toISOString())
|
|
1611
|
+
throw new Error('inbox claim is not live');
|
|
1612
|
+
const inputs = run.agentInputs ?? [];
|
|
1613
|
+
if (inputs.some(input => input.inboxClaim?.receiptId === receipt.receiptId))
|
|
1614
|
+
throw new Error('inbox receipt already exists');
|
|
1615
|
+
for (const id of selected) {
|
|
1616
|
+
const matching = inputs.filter(input => input.id === id);
|
|
1617
|
+
if (matching.length !== 1 || matching[0]?.source !== 'agent' || !matching[0].conversation ||
|
|
1618
|
+
matching[0].deliveredAt || (matching[0].inboxClaim && matching[0].inboxClaim.expiresAt > new Date().toISOString())) {
|
|
1619
|
+
throw new Error('inbox input is not available');
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
const selectedIds = new Set(selected);
|
|
1623
|
+
this.commitIndex(new Map([[runId, { ...run, agentInputs: inputs.map(input => selectedIds.has(input.id)
|
|
1624
|
+
? agentInputSchema.parse({ ...input, inboxClaim: receipt }) : input) }]]));
|
|
1625
|
+
}
|
|
1626
|
+
/** A receipt owns every matching input or none; exact ACK retries preserve the first timestamp. */
|
|
1627
|
+
ackInboxInputs(runId, receiptId, generation, at) {
|
|
1628
|
+
const run = this.peek(runId);
|
|
1629
|
+
if (!run)
|
|
1630
|
+
throw new Error('missing inbox recipient');
|
|
1631
|
+
inboxClaimSchema.shape.receiptId.parse(receiptId);
|
|
1632
|
+
inboxClaimSchema.shape.generation.parse(generation);
|
|
1633
|
+
inboxClaimSchema.shape.expiresAt.parse(at);
|
|
1634
|
+
const matching = (run.agentInputs ?? []).filter(input => input.inboxClaim?.receiptId === receiptId);
|
|
1635
|
+
const receipt = matching[0]?.inboxClaim;
|
|
1636
|
+
const memberIds = receipt?.memberIds;
|
|
1637
|
+
if (!memberIds || matching.length !== memberIds.length ||
|
|
1638
|
+
!memberIds.every(id => matching.some(input => input.id === id)) ||
|
|
1639
|
+
matching.some(input => !isDeepStrictEqual(input.inboxClaim, receipt)) ||
|
|
1640
|
+
matching.some(input => input.inboxClaim?.generation !== generation ||
|
|
1641
|
+
input.source !== 'agent' || !input.conversation))
|
|
1642
|
+
throw new Error('inbox receipt changed');
|
|
1643
|
+
if (matching.every(input => input.inboxClaim?.acknowledgedAt && input.deliveredAt === input.inboxClaim.acknowledgedAt)) {
|
|
1644
|
+
return 'already-acknowledged';
|
|
1645
|
+
}
|
|
1646
|
+
if (matching.some(input => input.deliveredAt || input.inboxClaim?.acknowledgedAt || input.inboxClaim.expiresAt <= at)) {
|
|
1647
|
+
throw new Error('inbox receipt expired or displaced');
|
|
1648
|
+
}
|
|
1649
|
+
this.commitOutcome(runId, { kind: 'inbox-acknowledged', receiptId, at });
|
|
1650
|
+
return 'acknowledged';
|
|
1651
|
+
}
|
|
1652
|
+
releaseInboxInputs(runId, receiptId, generation) {
|
|
1653
|
+
const run = this.peek(runId);
|
|
1654
|
+
if (!run)
|
|
1655
|
+
throw new Error('missing inbox recipient');
|
|
1656
|
+
inboxClaimSchema.shape.receiptId.parse(receiptId);
|
|
1657
|
+
inboxClaimSchema.shape.generation.parse(generation);
|
|
1658
|
+
const matching = (run.agentInputs ?? []).filter(input => input.inboxClaim?.receiptId === receiptId);
|
|
1659
|
+
if (!matching.length || matching.some(input => input.inboxClaim?.generation !== generation ||
|
|
1660
|
+
input.inboxClaim?.acknowledgedAt || input.deliveredAt))
|
|
1661
|
+
throw new Error('inbox receipt changed');
|
|
1662
|
+
this.commitIndex(new Map([[runId, { ...run, agentInputs: run.agentInputs.map(input => {
|
|
1663
|
+
if (input.inboxClaim?.receiptId !== receiptId)
|
|
1664
|
+
return input;
|
|
1665
|
+
const { inboxClaim: _claim, ...unclaimed } = input;
|
|
1666
|
+
return agentInputSchema.parse(unclaimed);
|
|
1667
|
+
}) }]]));
|
|
1668
|
+
return 'released';
|
|
1669
|
+
}
|
|
1670
|
+
clearExpiredInboxClaims(runId, now) {
|
|
1671
|
+
const run = this.peek(runId);
|
|
1672
|
+
if (!run)
|
|
1673
|
+
throw new Error('missing inbox recipient');
|
|
1674
|
+
inboxClaimSchema.shape.expiresAt.parse(now);
|
|
1675
|
+
if (!run.agentInputs?.some(input => input.inboxClaim && !input.inboxClaim.acknowledgedAt && input.inboxClaim.expiresAt <= now))
|
|
1676
|
+
return;
|
|
1677
|
+
this.commitIndex(new Map([[runId, { ...run, agentInputs: run.agentInputs.map(input => {
|
|
1678
|
+
if (!input.inboxClaim || input.inboxClaim.acknowledgedAt || input.inboxClaim.expiresAt > now)
|
|
1679
|
+
return input;
|
|
1680
|
+
const { inboxClaim: _claim, ...unclaimed } = input;
|
|
1681
|
+
return agentInputSchema.parse(unclaimed);
|
|
1682
|
+
}) }]]));
|
|
1683
|
+
}
|
|
1684
|
+
/**
|
|
1685
|
+
* A wait and its wake input share one atomic index replacement (spec 2026-10-17-unified-waits).
|
|
1686
|
+
* Upserts `wait` by id into `waits` and clears `lastWaitsError`; `input` is appended once (an
|
|
1687
|
+
* input whose id is already queued stays as it is). Only an open or resolved wait is committed
|
|
1688
|
+
* here: settling is an outcome (`commitOutcome`). A new open wait
|
|
1689
|
+
* past `WAIT_LIMIT` open ones is refused, so the list stays bounded whatever the caller checked.
|
|
1690
|
+
*/
|
|
1691
|
+
commitWait(id, wait, input, registration) {
|
|
1692
|
+
const run = this.peek(id);
|
|
1693
|
+
if (!run)
|
|
1694
|
+
throw new Error('missing wait target');
|
|
1695
|
+
this.commitIndex(new Map([[id, registeredWaitRecord(run, wait, input, registration)]]));
|
|
1696
|
+
}
|
|
1697
|
+
/** Record a deduplicated registration without mutating its wait. */
|
|
1698
|
+
commitWaitRegistration(id, receipt) {
|
|
1699
|
+
const run = this.peek(id);
|
|
1700
|
+
if (!run)
|
|
1701
|
+
throw new Error('missing wait target');
|
|
1702
|
+
this.commitIndex(new Map([[id, registeredReceiptRecord(run, receipt)]]));
|
|
1703
|
+
}
|
|
1704
|
+
/** A usage-limit appointment withdrawn, committed at once instead of on the debounced save
|
|
1705
|
+
* (hearsay-tools/cezarion#1236): a crash right after the withdrawal must not find it on disk,
|
|
1706
|
+
* or recovery re-arms work the user cancelled. A run held for its resume settles `failed` by
|
|
1707
|
+
* the `withdraw-hold` exit; any other run only loses its resume fields. */
|
|
1708
|
+
commitAutoResumeWithdrawal(id) {
|
|
1709
|
+
const run = this.peek(id);
|
|
1710
|
+
if (!run || (run.autoResumeAt === undefined && run.autoResumeAttempts === undefined))
|
|
1711
|
+
return;
|
|
1712
|
+
if (isUsageLimitHold(run))
|
|
1713
|
+
this.move(id, 'withdraw-hold');
|
|
1714
|
+
else
|
|
1715
|
+
this.commitIndex(new Map([[id, { ...run, autoResumeAt: undefined, autoResumeAttempts: undefined }]]));
|
|
1716
|
+
}
|
|
1717
|
+
/** A human message beside a run's waits (hearsay-tools/cezarion#1235): accepted durably, and
|
|
1718
|
+
* every wait stays. A lifecycle opening becomes
|
|
1719
|
+
* the human's and keeps its wake text, so the message opens the continuation (and answers a
|
|
1720
|
+
* pending question) and the owed results still reach the agent. */
|
|
1721
|
+
commitHumanMessageBesideWaits(id, acceptedHumanMessage) {
|
|
1722
|
+
const run = this.peek(id);
|
|
1723
|
+
if (!run)
|
|
1724
|
+
throw new Error('missing queued message target');
|
|
1725
|
+
this.commitIndex(new Map([[id, { ...run, queuedMessages: [...(run.queuedMessages ?? []), queuedMessageSchema.parse(acceptedHumanMessage)],
|
|
1726
|
+
...(run.continuationMessage ? { continuationMessage: { ...run.continuationMessage, origin: 'human' } } : {}),
|
|
1727
|
+
}]]));
|
|
1728
|
+
}
|
|
1729
|
+
/**
|
|
1730
|
+
* The one write of a run outcome (hearsay-tools/cezarion#1805): an input or wait delivered, a wake
|
|
1731
|
+
* charged, a wait settled, a question answered, staged by `applyOutcomes` with what it depends on
|
|
1732
|
+
* (`deps`) and committed in one transaction, with every pending row. A crash keeps all of it or
|
|
1733
|
+
* none. Nothing changes: nothing is written. Needs no live session: it reads the stored record.
|
|
1734
|
+
*
|
|
1735
|
+
* A refused write throws with nothing changed. `bestEffort` is for a cleanup path, which must
|
|
1736
|
+
* never stop on it: it warns and applies the change in memory for the debounced save to retry.
|
|
1737
|
+
* A conversation request outcome is the other sanctioned write: `commitConversation`.
|
|
1738
|
+
*/
|
|
1739
|
+
commitOutcome(id, outcomes, deps = {}, opts = {}) {
|
|
1740
|
+
const run = this.peek(id);
|
|
1741
|
+
if (!run)
|
|
1742
|
+
return { committed: false, answered: [], settled: [] };
|
|
1743
|
+
const staged = applyOutcomes(run, Array.isArray(outcomes) ? outcomes : [outcomes], deps);
|
|
1744
|
+
if (!staged.changed)
|
|
1745
|
+
return { committed: false, answered: [], settled: [] };
|
|
1746
|
+
try {
|
|
1747
|
+
this.commitIndex(new Map([[id, staged.next]]));
|
|
1748
|
+
}
|
|
1749
|
+
catch (error) {
|
|
1750
|
+
if (!opts.bestEffort)
|
|
1751
|
+
throw error;
|
|
1752
|
+
console.warn(`${logPrefix()} outcome checkpoint failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1753
|
+
const live = this.record(id);
|
|
1754
|
+
if (!live)
|
|
1755
|
+
return { committed: false, answered: staged.answered, settled: staged.settled };
|
|
1756
|
+
for (const key of Object.keys(live))
|
|
1757
|
+
if (!(key in staged.next))
|
|
1758
|
+
delete live[key];
|
|
1759
|
+
Object.assign(live, staged.next);
|
|
1760
|
+
this.touch(live);
|
|
1761
|
+
return { committed: false, answered: staged.answered, settled: staged.settled };
|
|
1762
|
+
}
|
|
1763
|
+
return { committed: true, answered: staged.answered, settled: staged.settled };
|
|
1764
|
+
}
|
|
1765
|
+
/** An acknowledged inactive-root Finish must survive the async diff and restart. */
|
|
1766
|
+
commitRootFinishIntent(id) {
|
|
1767
|
+
const run = this.peek(id);
|
|
1768
|
+
if (run?.status !== 'waiting' || run.delegation?.role !== 'root')
|
|
1769
|
+
throw new Error('root is not waiting');
|
|
1770
|
+
if (run.delegation.finishRequestedAt)
|
|
1771
|
+
return;
|
|
1772
|
+
this.commitDelegation([{ id, delegation: { ...run.delegation, finishRequestedAt: new Date().toISOString() } }]);
|
|
1773
|
+
}
|
|
1774
|
+
/** Persist all authority patches before exposing any of them to the engine or subscribers. */
|
|
1775
|
+
commitDelegation(patches) {
|
|
1776
|
+
if (patches.length === 0)
|
|
1777
|
+
return;
|
|
1778
|
+
const staged = new Map();
|
|
1779
|
+
let destroyProgress = true;
|
|
1780
|
+
for (const patch of patches) {
|
|
1781
|
+
const run = this.peek(patch.id);
|
|
1782
|
+
if (!run || staged.has(patch.id))
|
|
1783
|
+
throw new Error('missing or duplicate delegation patch target');
|
|
1784
|
+
const delegation = delegationStateSchema.parse(patch.delegation);
|
|
1785
|
+
if (!isDestroyProgress(run.delegation, delegation))
|
|
1786
|
+
destroyProgress = false;
|
|
1787
|
+
staged.set(patch.id, { ...run, delegation });
|
|
1788
|
+
}
|
|
1789
|
+
// In-process cause only: consumers can skip metadata replay when delegation
|
|
1790
|
+
// is disabled without dropping real status or termination-proof notifications.
|
|
1791
|
+
this.commitIndex(staged, destroyProgress ? 'delegation-destroy-progress' : 'delegation-checkpoint');
|
|
1792
|
+
}
|
|
1793
|
+
/** A pending destroy's automatic retry state, or its removal (hearsay-tools/cezarion#879). Kept on
|
|
1794
|
+
* the record beside the strict delegation, so an older cezar strips it rather than quarantining the
|
|
1795
|
+
* worker. Destroy progress: nothing the family reconcile reads changes. */
|
|
1796
|
+
commitDestroyRetry(id, retry) {
|
|
1797
|
+
const run = this.peek(id);
|
|
1798
|
+
if (run?.delegation?.role !== 'worker' || !run.delegation.destroy)
|
|
1799
|
+
throw new Error('missing pending destroy');
|
|
1800
|
+
if (!retry && !run.destroyRetry)
|
|
1801
|
+
return;
|
|
1802
|
+
// An explicit `undefined`, not an omitted key: commitIndex assigns onto the live record.
|
|
1803
|
+
this.commitIndex(new Map([[id, { ...run, destroyRetry: retry ? workerDestroyRetrySchema.parse(retry) : undefined }]]), 'delegation-destroy-progress');
|
|
1804
|
+
}
|
|
1805
|
+
/** Accepted execution revision is public lifecycle identity, separate from process generations. */
|
|
1806
|
+
commitWorkerContinuation(id, exit, patch = {}, step, conversation) {
|
|
1807
|
+
const run = this.peek(id);
|
|
1808
|
+
if (run?.delegation?.role !== 'worker')
|
|
1809
|
+
throw new Error('missing worker continuation target');
|
|
1810
|
+
const delegation = delegationStateSchema.parse({ ...run.delegation,
|
|
1811
|
+
executionRevision: (run.delegation.executionRevision ?? 0) + 1,
|
|
1812
|
+
executionStartSeq: this.transcriptFacts(id).lastSeq,
|
|
1813
|
+
});
|
|
1814
|
+
const staged = new Map();
|
|
1815
|
+
this.exitEvents.started(id);
|
|
1816
|
+
const next = { ...run, ...applyExit(run, exit, this.redactPatch(patch)), delegation,
|
|
1817
|
+
...(step ? { steps: [...run.steps, { ...step, status: 'pending', iterations: 0, tokensUsed: 0 }] } : {}),
|
|
1818
|
+
};
|
|
1819
|
+
staged.set(id, next);
|
|
1820
|
+
if (Object.prototype.hasOwnProperty.call(patch, 'archived'))
|
|
1821
|
+
this.syncTranscriptForm(id, next.archived);
|
|
1822
|
+
if (conversation) {
|
|
1823
|
+
const root = this.peek(conversation.rootId);
|
|
1824
|
+
if (root?.delegation?.role !== 'root' || run.delegation.parentRunId !== root.id)
|
|
1825
|
+
throw new Error('missing conversation ownership');
|
|
1826
|
+
stageConversation(staged, root, conversation.state, { recipientRunId: id, input: conversation.input }, runId => this.peek(runId), text => this.redactText(text));
|
|
1827
|
+
}
|
|
1828
|
+
this.commitIndex(staged);
|
|
1829
|
+
}
|
|
1830
|
+
workerResultsDir(parentId, write = false) { return workerResultsDirectory(write ? this.writeDir(parentId) : this.sourceDataDir(parentId), parentId, write); }
|
|
1831
|
+
workerResultSnapshotPath(parentId, workerId, snapshotId) {
|
|
1832
|
+
return join(this.dataDir, 'runs', `${z.uuid().parse(parentId)}-worker-results`, `${z.uuid().parse(workerId)}.${z.uuid().parse(snapshotId)}.json`);
|
|
1833
|
+
}
|
|
1834
|
+
readWorkerResultFile(parentId, workerId) {
|
|
1835
|
+
const parent = this.peek(parentId);
|
|
1836
|
+
if (parent?.delegation?.role !== 'root' || !parent.delegation.receipts.some(receipt => receipt.workerId === workerId))
|
|
1837
|
+
return undefined;
|
|
1838
|
+
const reference = parent.delegation.results?.find(result => result.workerId === workerId);
|
|
1839
|
+
if (!reference)
|
|
1840
|
+
return undefined;
|
|
1841
|
+
try {
|
|
1842
|
+
const path = join(this.workerResultsDir(parentId), `${z.uuid().parse(workerId)}.${z.uuid().parse(reference.snapshotId)}.json`);
|
|
1843
|
+
const fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
1844
|
+
let raw;
|
|
1845
|
+
try {
|
|
1846
|
+
const info = fstatSync(fd);
|
|
1847
|
+
if (!info.isFile() || info.size > 3_145_728)
|
|
1848
|
+
return undefined;
|
|
1849
|
+
raw = readFileSync(fd, 'utf8');
|
|
1850
|
+
}
|
|
1851
|
+
finally {
|
|
1852
|
+
closeSync(fd);
|
|
1853
|
+
}
|
|
1854
|
+
const file = workerResultFileSchema.parse(JSON.parse(raw));
|
|
1855
|
+
if (!bindWorkerResultPath(file.result.diff, this.dataDir, this.hasFallbackRun(parentId), parentId, path))
|
|
1856
|
+
return undefined;
|
|
1857
|
+
if (file.result.parentRunId !== parentId || file.result.workerId !== workerId || file.result.workspace.ownerRunId !== workerId || file.result.revision !== reference.revision || file.result.observedAt !== reference.observedAt ||
|
|
1858
|
+
(file.result.diff.state === 'available' && (file.result.diff.snapshotId !== reference.snapshotId || file.diffSnapshot === undefined)))
|
|
1859
|
+
return undefined;
|
|
1860
|
+
return file;
|
|
1861
|
+
}
|
|
1862
|
+
catch {
|
|
1863
|
+
return undefined;
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
readWorkerResult(parentId, workerId) {
|
|
1867
|
+
return this.readWorkerResultFile(parentId, workerId)?.result;
|
|
1868
|
+
}
|
|
1869
|
+
readWorkerResultDiff(parentId, workerId) {
|
|
1870
|
+
return this.readWorkerResultFile(parentId, workerId)?.diffSnapshot;
|
|
1871
|
+
}
|
|
1872
|
+
/** Publish a pointer only after its redacted immutable snapshot reaches disk. */
|
|
1873
|
+
commitWorkerResult(parentId, value, diffSnapshot, opts = {}) {
|
|
1874
|
+
return commitStoreWorkerResult({
|
|
1875
|
+
redactResult: (result) => this.redact({ type: 'worker-result', seq: 0, ts: result.observedAt, result }).result,
|
|
1876
|
+
peek: (id) => this.peek(id),
|
|
1877
|
+
readWorkerResultFile: (id, workerId) => this.readWorkerResultFile(id, workerId),
|
|
1878
|
+
readWorkerResult: (id, workerId) => this.readWorkerResult(id, workerId),
|
|
1879
|
+
redactText: (text) => this.redactText(text),
|
|
1880
|
+
workerResultSnapshotPath: (id, workerId, snapshotId) => this.workerResultSnapshotPath(id, workerId, snapshotId),
|
|
1881
|
+
workerResultsDir: (id) => this.workerResultsDir(id, true),
|
|
1882
|
+
commitDelegation: (patches) => this.commitDelegation(patches),
|
|
1883
|
+
}, parentId, value, diffSnapshot, opts);
|
|
1884
|
+
}
|
|
1885
|
+
/** Create an owned worker run and its parent receipt; `createStoreOwnedRun` holds the checks. */
|
|
1886
|
+
createOwnedRun(input, parentId, requestId, worker, requestHash, executionIdentity = { kind: 'internal' }) {
|
|
1887
|
+
return createStoreOwnedRun({
|
|
1888
|
+
peek: (id) => this.peek(id),
|
|
1889
|
+
hasRun: (id) => this.hasRun(id),
|
|
1890
|
+
held: this.held, deleted: this.deleted, db: this.db,
|
|
1891
|
+
buildRun: (ownedInput, id) => this.buildRun(ownedInput, id),
|
|
1892
|
+
writeWorkerIdentity: (id, identity) => this.writeWorkerIdentity(id, identity),
|
|
1893
|
+
writeWorkerExecution: (id, proof, fresh) => this.writeWorkerExecution(id, proof, fresh),
|
|
1894
|
+
commitIndex: (owned) => this.commitIndex(owned),
|
|
1895
|
+
}, input, parentId, requestId, worker, requestHash, executionIdentity);
|
|
1896
|
+
}
|
|
1897
|
+
/**
|
|
1898
|
+
* Durably replace or delete staged rows together with pending dirty rows. Persist before
|
|
1899
|
+
* changing held objects or notifying subscribers. On failure retain all pending state; flush()
|
|
1900
|
+
* remains best-effort.
|
|
1901
|
+
*/
|
|
1902
|
+
commitIndex(input, source) {
|
|
1903
|
+
const staged = stageCommit(input, {
|
|
1904
|
+
peek: id => this.peek(id),
|
|
1905
|
+
completionArchiveBlocker: run => this.completionArchiveBlocker(run),
|
|
1906
|
+
stageArchive: (staged, run, archived) => this.stageArchive(staged, run, archived),
|
|
1907
|
+
});
|
|
1908
|
+
if (!this.db)
|
|
1909
|
+
throw this.openFailure ?? new Error('runs database unavailable: nothing can be saved');
|
|
1910
|
+
this.assertWritable(staged);
|
|
1911
|
+
this.persist(staged);
|
|
1912
|
+
// Preserve existing record references, but expose the entire transaction before its first event.
|
|
1913
|
+
// A finished run joins the held set for the commit and leaves at the next sweep.
|
|
1914
|
+
for (const [id, next] of staged) {
|
|
1915
|
+
this.coldReads.forget(id);
|
|
1916
|
+
if (!next) {
|
|
1917
|
+
this.held.delete(id);
|
|
1918
|
+
continue;
|
|
1919
|
+
}
|
|
1920
|
+
this.committing.add(id);
|
|
1921
|
+
const current = this.held.get(id);
|
|
1922
|
+
if (current) {
|
|
1923
|
+
for (const key of Object.keys(current)) {
|
|
1924
|
+
if (!(key in next))
|
|
1925
|
+
delete current[key];
|
|
1926
|
+
}
|
|
1927
|
+
Object.assign(current, next);
|
|
1928
|
+
}
|
|
1929
|
+
else
|
|
1930
|
+
this.held.set(id, next);
|
|
1931
|
+
}
|
|
1932
|
+
try {
|
|
1933
|
+
for (const id of staged.keys()) {
|
|
1934
|
+
const run = this.held.get(id);
|
|
1935
|
+
if (run)
|
|
1936
|
+
this.emit('run', run, source);
|
|
1937
|
+
else
|
|
1938
|
+
this.emit('deleted', id);
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
finally {
|
|
1942
|
+
for (const id of staged.keys())
|
|
1943
|
+
this.committing.delete(id);
|
|
1944
|
+
this.sweepIfSettled(staged.keys());
|
|
1945
|
+
}
|
|
1946
|
+
for (const [id, next] of staged)
|
|
1947
|
+
if (next?.archived)
|
|
1948
|
+
this.compressor.enqueue(id);
|
|
1949
|
+
}
|
|
1950
|
+
completionArchiveBlocker(root) {
|
|
1951
|
+
const family = [root, ...this.listWorkersOf(root.id)];
|
|
1952
|
+
for (const run of family) {
|
|
1953
|
+
const facts = this.factsFor(run.id).peek(run.id);
|
|
1954
|
+
if (!facts || !['done', 'review', 'failed', 'cancelled'].includes(run.status) || run.waits?.some(wait => wait.phase === 'open') ||
|
|
1955
|
+
run.hasPendingPermission || facts.pendingAsk !== undefined)
|
|
1956
|
+
return 'Task or worker work still needs attention. Open the task or archive manually.';
|
|
1957
|
+
if (run.delegation?.role === 'invalid' || (run.delegation?.role === 'worker' && this.readWorkerExecution(run.id)?.phase !== 'complete'))
|
|
1958
|
+
return 'Worker execution has not settled. Open the worker or archive manually.';
|
|
1959
|
+
}
|
|
1960
|
+
return undefined;
|
|
1961
|
+
}
|
|
1962
|
+
/** One archive write for manual actions, merge observations, completion, and sweeps.
|
|
1963
|
+
* Archiving files history away; it never stops a process or removes a worktree. */
|
|
1964
|
+
stageArchive(staged, root, archived) {
|
|
1965
|
+
const ids = root.delegation?.role === 'worker' ? [root.id] :
|
|
1966
|
+
[root.id, ...new Set([...(this.db?.listIdsByParent(root.id) ?? []), ...this.listWorkersOf(root.id).map(run => run.id)])];
|
|
1967
|
+
const now = new Date().toISOString();
|
|
1968
|
+
for (const id of ids) {
|
|
1969
|
+
const member = staged.get(id) ?? this.peek(id);
|
|
1970
|
+
if (!member)
|
|
1971
|
+
continue;
|
|
1972
|
+
if (!archived) {
|
|
1973
|
+
this.compressor.cancel(id);
|
|
1974
|
+
restoreHistory(this.dataDir, id);
|
|
1975
|
+
}
|
|
1976
|
+
const next = { ...member, archiveBlocker: undefined };
|
|
1977
|
+
archiveFields(next, archived, now);
|
|
1978
|
+
staged.set(id, next);
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
/** A bounded persisted queue also covers projects without a live RunManager. */
|
|
1982
|
+
listCompletionCandidates(afterSeq = 0, limit = 32) {
|
|
1983
|
+
return this.db?.listCompletionCandidates(afterSeq, limit) ?? [];
|
|
1984
|
+
}
|
|
1985
|
+
canCompleteAction(id, token) {
|
|
1986
|
+
const run = this.record(id);
|
|
1987
|
+
return Boolean(run && run.status === 'done' && !run.archived && (run.autoMergePr || run.archiveWhenDone) &&
|
|
1988
|
+
run.completionAction?.id === token && run.completionAction.phase !== 'complete' && !this.writeRefusal(id));
|
|
1989
|
+
}
|
|
1990
|
+
claimCompletionAction(id, token) {
|
|
1991
|
+
if (!this.canCompleteAction(id, token))
|
|
1992
|
+
return false;
|
|
1993
|
+
const run = this.record(id);
|
|
1994
|
+
this.assertWritable(new Map([[id, run]]));
|
|
1995
|
+
// A normal write may defer its claim until the transaction. A remote mutation
|
|
1996
|
+
// needs the committed claim now, and must not act on a stale row read before it.
|
|
1997
|
+
const claim = this.db?.getClaim(familyKey(run));
|
|
1998
|
+
return Boolean(claim && claim.session === this.owner?.session &&
|
|
1999
|
+
claim.generation === this.claimed.get(familyKey(run)) &&
|
|
2000
|
+
this.db?.get(id)?.revision === this.base.get(id)?.revision);
|
|
2001
|
+
}
|
|
2002
|
+
completeAction(id, token, phase, message) {
|
|
2003
|
+
const run = this.record(id);
|
|
2004
|
+
if (!run || !this.canCompleteAction(id, token))
|
|
2005
|
+
return false;
|
|
2006
|
+
if (run.completionAction?.phase === phase && run.completionAction.message === message)
|
|
2007
|
+
return true;
|
|
2008
|
+
const next = { ...run, completionAction: { id: token, phase, ...(message ? { message } : {}) } };
|
|
2009
|
+
const staged = new Map([[id, next]]);
|
|
2010
|
+
if (phase === 'complete' && next.archiveWhenDone) {
|
|
2011
|
+
const blocker = this.completionArchiveBlocker(next);
|
|
2012
|
+
if (blocker)
|
|
2013
|
+
next.completionAction = { id: token, phase: 'blocked', message: blocker };
|
|
2014
|
+
else
|
|
2015
|
+
this.stageArchive(staged, next, true);
|
|
2016
|
+
}
|
|
2017
|
+
this.commitIndex(staged);
|
|
2018
|
+
return true;
|
|
2019
|
+
}
|
|
2020
|
+
/**
|
|
2021
|
+
* Refuse (throwing, with nothing changed) a commit that stages a quarantined run, or a run whose
|
|
2022
|
+
* family this store does not claim and cannot take: another process owns it, or a dead process
|
|
2023
|
+
* left it live and it has not been adopted.
|
|
2024
|
+
*/
|
|
2025
|
+
assertWritable(staged) {
|
|
2026
|
+
const families = new Map();
|
|
2027
|
+
const held = new Set();
|
|
2028
|
+
for (const [id, next] of staged) {
|
|
2029
|
+
if (this.quarantined.has(id))
|
|
2030
|
+
throw new RunWriteRefusedError(id, 'quarantined');
|
|
2031
|
+
const family = next ? familyKey(next) : this.familyOf(id);
|
|
2032
|
+
families.set(id, family);
|
|
2033
|
+
if (this.held.has(id))
|
|
2034
|
+
held.add(family);
|
|
2035
|
+
}
|
|
2036
|
+
// A run this store holds may move to a new family; any other row is claimed as a plain write.
|
|
2037
|
+
const owned = new Set([...this.claimFamilies(held, { allowLive: true }),
|
|
2038
|
+
...this.claimFamilies([...families.values()].filter((family) => !held.has(family)))]);
|
|
2039
|
+
for (const [id, family] of families) {
|
|
2040
|
+
if (!owned.has(family))
|
|
2041
|
+
throw this.runOwnership(id) === 'fallback' ? this.fallbackRefusal(id) : new RunWriteRefusedError(id, this.runOwnership(id) === 'orphaned' ? 'orphaned' : 'foreign');
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
/** The family of a run this store knows: held, else its row; a run nowhere is its own. */
|
|
2045
|
+
familyOf(id) {
|
|
2046
|
+
const held = this.held.get(id);
|
|
2047
|
+
return held ? familyKey(held) : this.deletedFamilies.get(id) ?? this.db?.familyOf(id) ?? id;
|
|
2048
|
+
}
|
|
2049
|
+
/** Schedule the sweep that lets any of `ids` leave memory, when one of them now can. */
|
|
2050
|
+
sweepIfSettled(ids) {
|
|
2051
|
+
for (const id of ids) {
|
|
2052
|
+
const run = this.held.get(id);
|
|
2053
|
+
if (run && !this.isAnchor(id, run)) {
|
|
2054
|
+
this.scheduleSave();
|
|
2055
|
+
return;
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
/** Move run `id` by `exit` (ADR 0056): its status and `extras` in one write, checked against the
|
|
2060
|
+
* record that write changes. A run this store cannot write is neither checked nor written. The
|
|
2061
|
+
* exit decides how the write persists (`exitDurability`), never the extras. A guarded exit (one
|
|
2062
|
+
* with a `guard` or a `settle`) commits at once, and writes nothing when its guard fails: undefined, or its
|
|
2063
|
+
* `refused` error. `call` names the step the exit settles apart from the others, and the text
|
|
2064
|
+
* of the lifecycle event it writes after the write (`exitEvent`). */
|
|
2065
|
+
move(id, exit, extras = {}, call = {}) {
|
|
2066
|
+
const spec = RUN_EXITS[exit];
|
|
2067
|
+
const guarded = Boolean(spec.guard || spec.settle);
|
|
2068
|
+
const run = guarded ? this.peek(id) : this.record(id);
|
|
2069
|
+
if (!run || spec.guard?.(run) === false) {
|
|
2070
|
+
if (spec.refused)
|
|
2071
|
+
throw new Error(spec.refused);
|
|
2072
|
+
return undefined;
|
|
2073
|
+
}
|
|
2074
|
+
const event = this.exitEvents.owed(id, exit, run.status, call);
|
|
2075
|
+
// Redacted first: a step the exit settles takes the run's error.
|
|
2076
|
+
const redacted = this.redactPatch(extras);
|
|
2077
|
+
let moved;
|
|
2078
|
+
if (!guarded)
|
|
2079
|
+
moved = this.writeRun(id, run, applyExit(run, exit, redacted, call), exitDurability(exit, run));
|
|
2080
|
+
else {
|
|
2081
|
+
const input = { ...run, ...redacted };
|
|
2082
|
+
this.commitIndex(new Map([[id, { ...(spec.settle?.(input, { ...call, now: new Date().toISOString() }) ?? input), ...applyExit(run, exit, {}, call) }]]));
|
|
2083
|
+
moved = this.peek(id);
|
|
2084
|
+
}
|
|
2085
|
+
if (event)
|
|
2086
|
+
this.exitEvents.write(id, event);
|
|
2087
|
+
return moved;
|
|
2088
|
+
}
|
|
2089
|
+
/** A write that is not a run exit. It commits at once when it changes handoff records, a
|
|
2090
|
+
* completion option or task access, else the next save writes it. A status in the patch, which only test
|
|
2091
|
+
* fixtures still write, takes the exit's record rule (`doneCommits`). */
|
|
2092
|
+
updateRun(id, patch) {
|
|
2093
|
+
const run = this.record(id);
|
|
2094
|
+
if (!run)
|
|
2095
|
+
return undefined;
|
|
2096
|
+
const committed = (patch.status === 'done' && doneCommits(run)) || patch.handoffRecords !== undefined ||
|
|
2097
|
+
[patch.archiveWhenDone, patch.autoMergePr, patch.allowWorkers, patch.allowMessaging].some(value => value !== undefined);
|
|
2098
|
+
return this.writeRun(id, run, patch, committed ? 'committed' : 'debounced');
|
|
2099
|
+
}
|
|
2100
|
+
/** One update of the held record `run`: committed at once, or written to the record for the next
|
|
2101
|
+
* save. A debounced write is best-effort, so a locked database cannot stop cleanup. */
|
|
2102
|
+
writeRun(id, run, patch, durability) {
|
|
2103
|
+
const hooks = {
|
|
2104
|
+
redact: (text) => this.redactText(text),
|
|
2105
|
+
repoHandle: this.repoHandle,
|
|
2106
|
+
restore: () => restoreHistory(this.dataDir, id),
|
|
2107
|
+
archive: (record, archived) => this.applyArchived(record, archived),
|
|
2108
|
+
touch: (record) => this.touch(record),
|
|
2109
|
+
};
|
|
2110
|
+
if (durability === 'committed') {
|
|
2111
|
+
this.commitIndex(new Map([[id, stageDurableUpdate(run, patch, hooks)]]));
|
|
2112
|
+
return this.record(id);
|
|
2113
|
+
}
|
|
2114
|
+
stageDebouncedUpdate(run, patch, hooks);
|
|
2115
|
+
return run;
|
|
2116
|
+
}
|
|
2117
|
+
redactPatch(patch) {
|
|
2118
|
+
return redactPatch(patch, text => this.redactText(text));
|
|
2119
|
+
}
|
|
2120
|
+
/** Append a step to an existing run (used by "Continue" — spec 003). */
|
|
2121
|
+
addStep(runId, step) {
|
|
2122
|
+
const run = this.record(runId);
|
|
2123
|
+
if (!run || run.steps.some((s) => s.id === step.id))
|
|
2124
|
+
return;
|
|
2125
|
+
run.steps.push({ ...step, status: 'pending', iterations: 0, tokensUsed: 0 });
|
|
2126
|
+
this.touch(run);
|
|
2127
|
+
}
|
|
2128
|
+
updateStep(runId, stepId, patch) {
|
|
2129
|
+
const run = this.record(runId);
|
|
2130
|
+
const step = run?.steps.find((s) => s.id === stepId);
|
|
2131
|
+
if (!run || !step)
|
|
2132
|
+
return;
|
|
2133
|
+
Object.assign(step, stageStepUpdate(patch, text => this.redactText(text)));
|
|
2134
|
+
run.tokensUsed = run.steps.reduce((sum, s) => sum + s.tokensUsed, 0);
|
|
2135
|
+
const startedAgentSteps = run.steps.filter((candidate) => candidate.kind === 'agent' && candidate.iterations > 0);
|
|
2136
|
+
const directionalComplete = startedAgentSteps.length > 0 &&
|
|
2137
|
+
startedAgentSteps.every((candidate) => candidate.usageInvocationsStarted !== undefined &&
|
|
2138
|
+
candidate.usageInvocationsObserved !== undefined &&
|
|
2139
|
+
candidate.usageInvocationsObserved > 0 &&
|
|
2140
|
+
candidate.usageInvocationsStarted === candidate.usageInvocationsObserved &&
|
|
2141
|
+
candidate.usageTurnsStarted !== undefined &&
|
|
2142
|
+
candidate.usageTurnsRecorded !== undefined &&
|
|
2143
|
+
candidate.usageTurnsStarted > 0 &&
|
|
2144
|
+
candidate.usageTurnsStarted === candidate.usageTurnsRecorded &&
|
|
2145
|
+
candidate.inputTokens !== undefined &&
|
|
2146
|
+
candidate.outputTokens !== undefined);
|
|
2147
|
+
run.inputTokens = directionalComplete
|
|
2148
|
+
? startedAgentSteps.reduce((sum, candidate) => sum + (candidate.inputTokens ?? 0), 0)
|
|
2149
|
+
: undefined;
|
|
2150
|
+
run.outputTokens = directionalComplete
|
|
2151
|
+
? startedAgentSteps.reduce((sum, candidate) => sum + (candidate.outputTokens ?? 0), 0)
|
|
2152
|
+
: undefined;
|
|
2153
|
+
const cost = run.steps.reduce((sum, s) => sum + (s.costUsd ?? 0), 0);
|
|
2154
|
+
run.costUsd = run.steps.some((step) => step.costUsd !== undefined) ? cost : undefined;
|
|
2155
|
+
this.touch(run);
|
|
2156
|
+
}
|
|
2157
|
+
listArchiveOnMergeCandidates(afterSeq = 0, limit = 32) {
|
|
2158
|
+
return this.db?.listArchiveOnMergeCandidates(afterSeq, limit) ?? [];
|
|
2159
|
+
}
|
|
2160
|
+
/** Use unnormalized persisted evidence: cold reads must not turn interrupted work into safety. */
|
|
2161
|
+
archiveFamily(id, isActive) {
|
|
2162
|
+
return archiveStoreFamily({
|
|
2163
|
+
db: this.db,
|
|
2164
|
+
held: this.held,
|
|
2165
|
+
deleted: this.deleted,
|
|
2166
|
+
quarantined: this.quarantined,
|
|
2167
|
+
pins: this.pins,
|
|
2168
|
+
listWorkersOf: (parentId) => this.listWorkersOf(parentId),
|
|
2169
|
+
setArchiveBlocker: (runId, blocker) => this.setArchiveBlocker(runId, blocker),
|
|
2170
|
+
decodeRunRow,
|
|
2171
|
+
decodeCold: (row) => this.decodeCold(row),
|
|
2172
|
+
writeRefusal: (memberId) => this.writeRefusal(memberId),
|
|
2173
|
+
factsPeek: (memberId) => this.factsFor(memberId).peek(memberId),
|
|
2174
|
+
readWorkerExecution: (memberId) => this.readWorkerExecution(memberId),
|
|
2175
|
+
}, id, isActive);
|
|
2176
|
+
}
|
|
2177
|
+
/** Blockers are feedback, never archive authority or cleanup permission. */
|
|
2178
|
+
setArchiveBlocker(id, blocker) {
|
|
2179
|
+
const run = this.record(id);
|
|
2180
|
+
if (!run || run.archived || isDeepStrictEqual(run.archiveBlocker, blocker))
|
|
2181
|
+
return;
|
|
2182
|
+
this.commitIndex(new Map([[id, { ...run, archiveBlocker: blocker }]]));
|
|
2183
|
+
}
|
|
2184
|
+
archiveMergeBlocker(snapshot, missing, repoBase) {
|
|
2185
|
+
const url = `https://github.com/${missing.owner}/${missing.repo}/pull/${missing.number}`;
|
|
2186
|
+
const family = [snapshot.rootId, ...(this.db?.listIdsByParent(snapshot.rootId) ?? [])].flatMap(id => {
|
|
2187
|
+
const run = this.peek(id);
|
|
2188
|
+
return run ? [run] : [];
|
|
2189
|
+
});
|
|
2190
|
+
const member = family.find(run => archivePullRequests([run], repoBase)?.some(pr => pullRequestKey(pr) === pullRequestKey(missing)));
|
|
2191
|
+
this.setArchiveBlocker(snapshot.rootId, { message: `PR ${missing.owner}/${missing.repo}#${missing.number} is not confirmed merged. Check the PR association or archive manually.`,
|
|
2192
|
+
runId: member?.id ?? snapshot.rootId, pullRequestUrl: url });
|
|
2193
|
+
}
|
|
2194
|
+
hasArchiveOnMergeAssociations(id) {
|
|
2195
|
+
return this.db?.archiveFamilyHasPullRequests(id) ?? false;
|
|
2196
|
+
}
|
|
2197
|
+
async prepareArchiveOnMerge(id) {
|
|
2198
|
+
const ids = [id, ...(this.db?.listIdsByParent(id) ?? [])];
|
|
2199
|
+
if (ids.length > 1025)
|
|
2200
|
+
return;
|
|
2201
|
+
for (const memberId of ids)
|
|
2202
|
+
await this.factsFor(memberId).ready(memberId);
|
|
2203
|
+
}
|
|
2204
|
+
archiveOnMergeSnapshot(id, repoBase, isActive = () => false) {
|
|
2205
|
+
const family = this.archiveFamily(id, isActive);
|
|
2206
|
+
if (!family)
|
|
2207
|
+
return undefined;
|
|
2208
|
+
return this.archiveSnapshot(family, repoBase);
|
|
2209
|
+
}
|
|
2210
|
+
archiveSnapshot(family, repoBase) {
|
|
2211
|
+
const pullRequests = archivePullRequests(family, repoBase);
|
|
2212
|
+
if (!pullRequests) {
|
|
2213
|
+
this.setArchiveBlocker(family[0].id, { message: 'PR associations cannot be resolved. Check task and worker PRs, or archive manually.' });
|
|
2214
|
+
return undefined;
|
|
2215
|
+
}
|
|
2216
|
+
const fingerprint = archiveDigest(pullRequests);
|
|
2217
|
+
if (family[0].autoArchive?.fingerprint === fingerprint)
|
|
2218
|
+
return undefined;
|
|
2219
|
+
return { rootId: family[0].id, fingerprint, pullRequests, token: archiveDigest(family.map(({ archiveBlocker: _blocker, ...run }) => run)) };
|
|
2220
|
+
}
|
|
2221
|
+
/** No await between final revalidation and the fenced, all-or-nothing cascade. */
|
|
2222
|
+
archiveAfterMerge(snapshot, observations, repoBase, isActive = () => false) {
|
|
2223
|
+
const family = this.archiveFamily(snapshot.rootId, isActive);
|
|
2224
|
+
if (!family)
|
|
2225
|
+
return false;
|
|
2226
|
+
const current = this.archiveSnapshot(family, repoBase);
|
|
2227
|
+
if (!current || current.token !== snapshot.token || current.fingerprint !== snapshot.fingerprint)
|
|
2228
|
+
return false;
|
|
2229
|
+
const parsed = observations.map(observation => mergedPullRequestSchema.safeParse(observation));
|
|
2230
|
+
if (parsed.some(result => !result.success))
|
|
2231
|
+
return false;
|
|
2232
|
+
const merged = parsed.flatMap(result => result.success ? [result.data] : []);
|
|
2233
|
+
const byKey = new Map(merged.map(pr => [pullRequestKey(pr), pr]));
|
|
2234
|
+
if (byKey.size !== current.pullRequests.length || current.pullRequests.some(pr => !byKey.has(pullRequestKey(pr))))
|
|
2235
|
+
return false;
|
|
2236
|
+
const restoredAt = Math.max(0, ...family.map(run => run.manualUnarchivedAt ? Date.parse(run.manualUnarchivedAt) : 0));
|
|
2237
|
+
if (restoredAt && merged.every(pr => Date.parse(pr.mergedAt) <= restoredAt))
|
|
2238
|
+
return false;
|
|
2239
|
+
const now = new Date().toISOString();
|
|
2240
|
+
if (merged.some(pr => Date.parse(pr.mergedAt) > Date.parse(now)))
|
|
2241
|
+
return false;
|
|
2242
|
+
const staged = new Map();
|
|
2243
|
+
this.stageArchive(staged, family[0], true);
|
|
2244
|
+
staged.get(snapshot.rootId).autoArchive = { reason: 'pull-requests-merged', archivedAt: now, fingerprint: current.fingerprint, pullRequests: merged };
|
|
2245
|
+
this.commitIndex(staged);
|
|
2246
|
+
for (const id of staged.keys())
|
|
2247
|
+
this.compressor.enqueue(id);
|
|
2248
|
+
return true;
|
|
2249
|
+
}
|
|
2250
|
+
setArchived(id, archived) {
|
|
2251
|
+
const run = this.record(id);
|
|
2252
|
+
if (!run)
|
|
2253
|
+
return undefined;
|
|
2254
|
+
this.applyArchivedCascade(run, archived);
|
|
2255
|
+
return run;
|
|
2256
|
+
}
|
|
2257
|
+
/** Parent + owned workers; returns how many records flipped `archived`. */
|
|
2258
|
+
applyArchivedCascade(run, archived) {
|
|
2259
|
+
const staged = new Map();
|
|
2260
|
+
this.stageArchive(staged, run, archived);
|
|
2261
|
+
let changed = 0;
|
|
2262
|
+
for (const [id, next] of staged) {
|
|
2263
|
+
if (next && this.peek(id)?.archived !== archived)
|
|
2264
|
+
changed++;
|
|
2265
|
+
if (next)
|
|
2266
|
+
next.completionAction = undefined; // an explicit filing/restoration retires pending work
|
|
2267
|
+
}
|
|
2268
|
+
this.commitIndex(staged);
|
|
2269
|
+
return changed;
|
|
2270
|
+
}
|
|
2271
|
+
applyArchived(run, archived) {
|
|
2272
|
+
const changed = run.archived !== archived;
|
|
2273
|
+
// Transcript first: a restore throw aborts with the record unchanged (#818 S3).
|
|
2274
|
+
this.syncTranscriptForm(run.id, archived);
|
|
2275
|
+
archiveFields(run, archived, new Date().toISOString());
|
|
2276
|
+
this.touch(run);
|
|
2277
|
+
return changed;
|
|
2278
|
+
}
|
|
2279
|
+
/** Transcript form follows `archived`: enqueue compression, or restore the plain file now. */
|
|
2280
|
+
syncTranscriptForm(id, archived) {
|
|
2281
|
+
if (archived)
|
|
2282
|
+
this.compressor.enqueue(id);
|
|
2283
|
+
else {
|
|
2284
|
+
this.compressor.cancel(id);
|
|
2285
|
+
restoreHistory(this.dataDir, id);
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
/**
|
|
2289
|
+
* Whether this store may compress `id`'s transcript: the run exists, is archived, is not in a
|
|
2290
|
+
* live status (queued/running/waiting), and this process holds the family's claim in `claimed`
|
|
2291
|
+
* (not merely `pendingClaims`). Two processes never both write a run (#779, #818 B1).
|
|
2292
|
+
*/
|
|
2293
|
+
isHistoryCompressEligible(id) {
|
|
2294
|
+
if (this.deleted.has(id))
|
|
2295
|
+
return false;
|
|
2296
|
+
try {
|
|
2297
|
+
const run = this.held.get(id) ?? this.peek(id);
|
|
2298
|
+
if (!run || run.archived !== true || isLiveStatus(run.status))
|
|
2299
|
+
return false;
|
|
2300
|
+
return this.holdsCompressClaim(id);
|
|
2301
|
+
}
|
|
2302
|
+
catch {
|
|
2303
|
+
return false;
|
|
2304
|
+
}
|
|
2305
|
+
}
|
|
2306
|
+
/** Take the family claim if free and require it to be in `this.claimed`, not only pending. */
|
|
2307
|
+
holdsCompressClaim(id) {
|
|
2308
|
+
const family = this.familyOf(id);
|
|
2309
|
+
return this.claimFamilies([family]).has(family) && this.claimed.has(family);
|
|
2310
|
+
}
|
|
2311
|
+
/** Startup sweep: enqueue every archived non-live run that still has a plain transcript. */
|
|
2312
|
+
compressArchivedHistory() {
|
|
2313
|
+
try {
|
|
2314
|
+
const ids = new Set();
|
|
2315
|
+
for (const key of this.db?.listKeysWhere('archived = 1 AND live = 0') ?? []) {
|
|
2316
|
+
if (!this.deleted.has(key.id))
|
|
2317
|
+
ids.add(key.id);
|
|
2318
|
+
}
|
|
2319
|
+
for (const run of this.held.values()) {
|
|
2320
|
+
if (run.archived && !isLiveStatus(run.status) && !this.deleted.has(run.id))
|
|
2321
|
+
ids.add(run.id);
|
|
2322
|
+
}
|
|
2323
|
+
for (const id of ids) {
|
|
2324
|
+
try {
|
|
2325
|
+
const { plain } = historyPaths(this.dataDir, id);
|
|
2326
|
+
// Claim only ids with work: a plain file to compress, including both-present leftovers.
|
|
2327
|
+
if (!existsSync(plain))
|
|
2328
|
+
continue;
|
|
2329
|
+
if (!this.holdsCompressClaim(id))
|
|
2330
|
+
continue;
|
|
2331
|
+
if (hasPlainHistory(this.dataDir, id))
|
|
2332
|
+
this.compressor.enqueue(id);
|
|
2333
|
+
}
|
|
2334
|
+
catch {
|
|
2335
|
+
// Best effort: one id must not stop the sweep or boot (#818 S2).
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
catch {
|
|
2340
|
+
// listKeysWhere / open-time fs: never throw out of boot.
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
/** Resolves when the transcript compressor is idle (tests, shutdown). */
|
|
2344
|
+
historyIdle() {
|
|
2345
|
+
return this.compressor.idle();
|
|
2346
|
+
}
|
|
2347
|
+
/** Pin one run to the top of this project's task list, or unpin it (#935). Mirrors
|
|
2348
|
+
* `setArchived`: sets the fields, then persists + broadcasts via `touch`, so the updated
|
|
2349
|
+
* record rides the existing `run` SSE with no new event. Idempotent — re-pinning a pinned
|
|
2350
|
+
* run just re-stamps `pinnedAt`.
|
|
2351
|
+
*
|
|
2352
|
+
* Every active status is pinnable. Archived requests clear stale metadata instead, so a
|
|
2353
|
+
* late click racing archive cannot leave a hidden pin that reappears on unarchive. */
|
|
2354
|
+
setPinned(id, pinned) {
|
|
2355
|
+
const run = this.record(id);
|
|
2356
|
+
if (!run)
|
|
2357
|
+
return undefined;
|
|
2358
|
+
if (pinned && !run.archived) {
|
|
2359
|
+
run.pinned = true;
|
|
2360
|
+
run.pinnedAt = new Date().toISOString();
|
|
2361
|
+
}
|
|
2362
|
+
else {
|
|
2363
|
+
clearPin(run);
|
|
2364
|
+
}
|
|
2365
|
+
this.touch(run);
|
|
2366
|
+
return run;
|
|
2367
|
+
}
|
|
2368
|
+
/** Bulk-archive finished runs (#780). `isSweepable` decides which; `archived` counts every
|
|
2369
|
+
* record that flipped (cascaded workers included), `ids` lists the top-level runs picked and
|
|
2370
|
+
* `pinnedIds` the subset that carried a pin before `clearPin` dropped it. */
|
|
2371
|
+
archiveFinished(scope, automation) {
|
|
2372
|
+
let archived = 0;
|
|
2373
|
+
// Snapshot first: cascade may archive still-live workers, mutating the held set, and the pin
|
|
2374
|
+
// has to be read before `applyArchived` clears it. Runs not in memory are picked off their
|
|
2375
|
+
// stored summary, which carries every field `isSweepable` reads.
|
|
2376
|
+
const picked = [...this.held.values()].filter((run) => SWEEPABLE_RUN_RULE.matches({ ...run, automationId: automationIdOf(run) }, scope, automation))
|
|
2377
|
+
.map((run) => [run, this.listOrder(run)]);
|
|
2378
|
+
for (const row of this.db?.listWhere(SWEEPABLE_RUN_RULE.sql) ?? []) {
|
|
2379
|
+
if (this.held.has(row.id) || this.deleted.has(row.id))
|
|
2380
|
+
continue;
|
|
2381
|
+
const summary = parseStoredSummary(row.summary);
|
|
2382
|
+
if (summary && SWEEPABLE_RUN_RULE.matches(summary, scope, automation))
|
|
2383
|
+
picked.push([summary, row]);
|
|
2384
|
+
}
|
|
2385
|
+
// Another process's runs are not this store's to archive (#779, plan step 3): they stay out.
|
|
2386
|
+
// A sweepable run is never a worker (`isSweepable`), so each is its own family.
|
|
2387
|
+
const writable = this.claimFamilies(picked.map(([run]) => run.id));
|
|
2388
|
+
const mine = sortNewestFirst(picked.filter(([run]) => writable.has(run.id)));
|
|
2389
|
+
const ids = mine.map((run) => run.id);
|
|
2390
|
+
const pinnedIds = mine.filter((run) => run.pinned).map((run) => run.id);
|
|
2391
|
+
for (const id of ids) {
|
|
2392
|
+
// Re-check: a prior cascade may already have archived this id.
|
|
2393
|
+
const run = this.record(id);
|
|
2394
|
+
if (!run || run.archived)
|
|
2395
|
+
continue;
|
|
2396
|
+
archived += this.applyArchivedCascade(run, true);
|
|
2397
|
+
}
|
|
2398
|
+
return { archived, ids, pinnedIds };
|
|
2399
|
+
}
|
|
2400
|
+
/** Mark one run as read (#unread-done-items): stamp the read receipt now. Mirrors
|
|
2401
|
+
* `setArchived` — sets the field then persists + broadcasts via `touch`, so the
|
|
2402
|
+
* updated record rides the existing `run` SSE with no new event. Idempotent by
|
|
2403
|
+
* design: opening an already-read thread just re-stamps a later `seenAt`. */
|
|
2404
|
+
setRead(id) {
|
|
2405
|
+
const run = this.record(id);
|
|
2406
|
+
if (!run)
|
|
2407
|
+
return undefined;
|
|
2408
|
+
run.seenAt = new Date().toISOString();
|
|
2409
|
+
this.touch(run);
|
|
2410
|
+
return run;
|
|
2411
|
+
}
|
|
2412
|
+
/** Mark one run as UNread (#775): drop the read receipt so the run rejoins the unread
|
|
2413
|
+
* list. The inverse of `setRead` and, like it, `touch`es so the updated record rides the
|
|
2414
|
+
* existing `run` SSE.
|
|
2415
|
+
*
|
|
2416
|
+
* Deleting the field rather than adding a "manually unread" flag is the whole point:
|
|
2417
|
+
* absent `seenAt` is ALREADY what every reader treats as unread (`isUnread` in the
|
|
2418
|
+
* cockpit's read-state.ts, and `markAllRead`'s clause-for-clause copy of it below), so
|
|
2419
|
+
* clearing needs no new state and writes a shape any older cezar already parses.
|
|
2420
|
+
*
|
|
2421
|
+
* Deliberately unconditional: clearing a receipt is always a legal write, so this
|
|
2422
|
+
* succeeds for an already-unread run (idempotent) and for statuses that can never wear
|
|
2423
|
+
* the marker. WHETHER the action means anything for a given run is UI policy, and lives
|
|
2424
|
+
* in the cockpit's `runActionFlags` — the same split the rest of the store keeps. */
|
|
2425
|
+
setUnread(id) {
|
|
2426
|
+
const run = this.record(id);
|
|
2427
|
+
if (!run)
|
|
2428
|
+
return undefined;
|
|
2429
|
+
delete run.seenAt;
|
|
2430
|
+
this.touch(run);
|
|
2431
|
+
return run;
|
|
2432
|
+
}
|
|
2433
|
+
/**
|
|
2434
|
+
* Stamp unread finished runs in bulk and return the count. Keep the rule aligned with cockpit
|
|
2435
|
+
* isUnread: done/failed, finished since seen, not archived; cancelled and usage-limit waiting
|
|
2436
|
+
* runs are excluded. `automation` narrows it to one origin, as in `archiveFinished`
|
|
2437
|
+
* (hearsay-tools/cezarion#1616); absent stamps both.
|
|
2438
|
+
*/
|
|
2439
|
+
markAllRead(automation) {
|
|
2440
|
+
const now = new Date().toISOString();
|
|
2441
|
+
let count = 0;
|
|
2442
|
+
// Runs not in memory are picked by the same rule over their stored summary, then re-checked
|
|
2443
|
+
// on the record before they are stamped.
|
|
2444
|
+
const candidates = [...this.held.values()];
|
|
2445
|
+
const stored = (this.db?.listWhere(UNREAD_RUN_RULE.sql) ?? []).filter((row) => !this.held.has(row.id) && !this.deleted.has(row.id));
|
|
2446
|
+
// One claim transaction for them all; another process's runs are skipped (`record` refuses).
|
|
2447
|
+
this.claimFamilies(stored.map(rowFamily));
|
|
2448
|
+
for (const row of stored) {
|
|
2449
|
+
const run = this.record(row.id);
|
|
2450
|
+
if (run)
|
|
2451
|
+
candidates.push(run);
|
|
2452
|
+
}
|
|
2453
|
+
for (const run of candidates) {
|
|
2454
|
+
const unread = UNREAD_RUN_RULE.matches(run);
|
|
2455
|
+
if (!unread)
|
|
2456
|
+
continue;
|
|
2457
|
+
if (automation !== undefined && (automationIdOf(run) !== undefined) !== automation)
|
|
2458
|
+
continue;
|
|
2459
|
+
run.seenAt = now;
|
|
2460
|
+
this.touch(run);
|
|
2461
|
+
count++;
|
|
2462
|
+
}
|
|
2463
|
+
return count;
|
|
2464
|
+
}
|
|
2465
|
+
appendEvent(runId, event) {
|
|
2466
|
+
const run = this.record(runId);
|
|
2467
|
+
if (!run) {
|
|
2468
|
+
const ownership = this.runOwnership(runId);
|
|
2469
|
+
if (ownership === 'fallback')
|
|
2470
|
+
throw this.fallbackRefusal(runId);
|
|
2471
|
+
if (ownership === 'foreign' || ownership === 'orphaned' || ownership === 'quarantined')
|
|
2472
|
+
throw new RunWriteRefusedError(runId, ownership);
|
|
2473
|
+
throw new Error(`unknown run: ${runId}`);
|
|
2474
|
+
}
|
|
2475
|
+
const seq = this.nextSeq(runId);
|
|
2476
|
+
// Scrub credentials before the event touches disk or the live wire (#427):
|
|
2477
|
+
// tool-result output is persisted verbatim and served back over the API, so
|
|
2478
|
+
// a secret in an agent's command output would otherwise land in `.ai/cezar/`.
|
|
2479
|
+
const full = this.redact(defined({ ...event, seq, ts: new Date().toISOString() }));
|
|
2480
|
+
// Sync append keeps event order without a write queue; local NDJSON
|
|
2481
|
+
// appends at agent-event rates are effectively free.
|
|
2482
|
+
if (!hasPlainHistory(this.dataDir, runId))
|
|
2483
|
+
restoreHistory(this.dataDir, runId);
|
|
2484
|
+
const line = `${JSON.stringify(full)}\n`;
|
|
2485
|
+
appendFileSync(this.eventsPath(runId), line, 'utf8');
|
|
2486
|
+
this.facts.append(runId, full, Buffer.byteLength(line));
|
|
2487
|
+
this.maybeEnqueueHistoryCompress(run);
|
|
2488
|
+
this.liveItems.observe(runId, full, false);
|
|
2489
|
+
this.emit('event', { runId, event: full });
|
|
2490
|
+
// Reference discovery runs last, after the event is persisted, indexed and
|
|
2491
|
+
// published: the janitor reads the event text and may still update the run.
|
|
2492
|
+
scanEventReferences(run, full, this.repoHandle, (target, entries) => this.accumulatePullRequests(target, entries), (target) => this.touch(target));
|
|
2493
|
+
return full;
|
|
2494
|
+
}
|
|
2495
|
+
/**
|
|
2496
|
+
* Apply agent-declared reference markers (spec 2026-07-18-task-ref-markers).
|
|
2497
|
+
* Marker values are authoritative for the display tier: they overwrite the
|
|
2498
|
+
* regex/namer numbers unless the prompt identifies the originating issue, and a declared PR re-resolves the referenced URL
|
|
2499
|
+
* against the candidate working set — including down to `undefined` when no
|
|
2500
|
+
* candidate matches (a wrong chip is worse than no chip). The created tier
|
|
2501
|
+
* (`pullRequestUrl`) is deliberately untouched.
|
|
2502
|
+
*
|
|
2503
|
+
* One declaration is NOT a statement about the referenced tier: the number of the PR this run
|
|
2504
|
+
* itself created. See `referencedPrDeclaration` — the marker contract asks the agent to
|
|
2505
|
+
* re-declare after it opens a PR, and taking that literally cost the task the PR it was about.
|
|
2506
|
+
*/
|
|
2507
|
+
applyMarkerRefs(runId, refs) {
|
|
2508
|
+
const run = this.record(runId);
|
|
2509
|
+
if (!run || (refs.pr === undefined && refs.issue === undefined))
|
|
2510
|
+
return run;
|
|
2511
|
+
const numbers = [...new Set([...(run.markerRefs?.pr === undefined ? [] : [run.markerRefs.pr]),
|
|
2512
|
+
...(refs.prs ?? (refs.pr === undefined ? [] : [refs.pr]))])];
|
|
2513
|
+
if (numbers.length)
|
|
2514
|
+
this.accumulatePullRequests(run, numbers.flatMap((number) => {
|
|
2515
|
+
const matches = (run.referencedPrCandidates ?? []).filter((url) => refUrlNumber(url) === number &&
|
|
2516
|
+
(this.repoHandle ? isRepoScopedRef(url, run.task, this.repoHandle) : promptNamesRepo(url, run.task)));
|
|
2517
|
+
const url = matches.length === 1 ? matches[0] : matches.length === 0 && this.repoHandle
|
|
2518
|
+
? `https://github.com/${this.repoHandle.owner}/${this.repoHandle.name}/pull/${number}` : undefined;
|
|
2519
|
+
const parsed = runPullRequestSchema.safeParse({ number, ...(url ? { url } : {}), source: 'declared' });
|
|
2520
|
+
return parsed.success ? [parsed.data] : [];
|
|
2521
|
+
}));
|
|
2522
|
+
run.markerRefs = {
|
|
2523
|
+
...run.markerRefs,
|
|
2524
|
+
...(refs.pr !== undefined ? { pr: refs.pr } : {}),
|
|
2525
|
+
...(refs.issue !== undefined ? { issue: refs.issue } : {}),
|
|
2526
|
+
};
|
|
2527
|
+
// `prNumber` is the about-PR as well (it is what paints a numeric-only chip), so a
|
|
2528
|
+
// re-declaration naming the created PR only FILLS it — it never overwrites the number the
|
|
2529
|
+
// task came in with, which is still the PR this task is about.
|
|
2530
|
+
if (refs.pr !== undefined && (run.prNumber === undefined || refs.pr !== refUrlNumber(run.pullRequestUrl))) {
|
|
2531
|
+
run.prNumber = refs.pr;
|
|
2532
|
+
}
|
|
2533
|
+
if (refs.issue !== undefined) {
|
|
2534
|
+
run.issueNumber = originatingIssue(run) ?? refs.issue;
|
|
2535
|
+
delete run.referencedIssueNumberSeeded;
|
|
2536
|
+
}
|
|
2537
|
+
if (run.markerRefs.pr !== undefined) {
|
|
2538
|
+
run.referencedPullRequestUrl = resolveReferencedRef(run.referencedPrCandidates ?? [], run.task, referencedPrDeclaration(run), this.repoHandle);
|
|
2539
|
+
}
|
|
2540
|
+
if (run.markerRefs.issue !== undefined) {
|
|
2541
|
+
run.referencedIssueUrl = resolveIssueReference(run, this.repoHandle);
|
|
2542
|
+
}
|
|
2543
|
+
this.touch(run);
|
|
2544
|
+
return run;
|
|
2545
|
+
}
|
|
2546
|
+
/** Authoritative creation keeps the first scalar for publishing, plus every created identity. */
|
|
2547
|
+
recordCreatedPr(runId, url) {
|
|
2548
|
+
const run = this.record(runId);
|
|
2549
|
+
const parsed = runPullRequestSchema.safeParse({ number: refUrlNumber(url), url, source: 'created' });
|
|
2550
|
+
if (!run || !parsed.success)
|
|
2551
|
+
return run;
|
|
2552
|
+
let changed = this.accumulatePullRequests(run, [parsed.data]);
|
|
2553
|
+
if (!run.pullRequestUrl) {
|
|
2554
|
+
run.pullRequestUrl = url;
|
|
2555
|
+
changed = true;
|
|
2556
|
+
}
|
|
2557
|
+
if (changed)
|
|
2558
|
+
this.touch(run);
|
|
2559
|
+
return run;
|
|
2560
|
+
}
|
|
2561
|
+
accumulatePullRequests(run, entries) {
|
|
2562
|
+
return stagePullRequests(run, entries, this.repoHandle);
|
|
2563
|
+
}
|
|
2564
|
+
/**
|
|
2565
|
+
* Fan an event out to live subscribers WITHOUT writing it to the NDJSON
|
|
2566
|
+
* file — the channel for coalesced `item.delta` flushes (protocol-v2
|
|
2567
|
+
* performance guardrail: raw deltas never hit disk; replay = the persisted
|
|
2568
|
+
* snapshots). Stamped with `seq`/`ts` like persisted lines so the live
|
|
2569
|
+
* wire keeps one ordering axis. Finite polling coalesces active item content
|
|
2570
|
+
* into bounded in-memory snapshots on that clock; disk replay still has gaps
|
|
2571
|
+
* (dedup compares with `>`).
|
|
2572
|
+
*/
|
|
2573
|
+
emitEphemeral(runId, event) {
|
|
2574
|
+
const full = this.redact({ ...event, seq: this.nextSeq(runId), ts: new Date().toISOString() });
|
|
2575
|
+
this.liveItems.observe(runId, full, true);
|
|
2576
|
+
this.emit('event', { runId, event: full });
|
|
2577
|
+
return full;
|
|
2578
|
+
}
|
|
2579
|
+
/** Lazily-collected concrete secret values from the host env (#427). */
|
|
2580
|
+
secretValues = null;
|
|
2581
|
+
/** Controller-issued tokens never persist, including when host-secret redaction is disabled. */
|
|
2582
|
+
sessionSecrets = new Set();
|
|
2583
|
+
registerSessionSecret(value) { this.sessionSecrets.add(value); }
|
|
2584
|
+
containsSessionSecret(value) { return [...this.sessionSecrets].some(secret => value.includes(secret)); }
|
|
2585
|
+
/**
|
|
2586
|
+
* Scrub known credential values / token shapes from an event before it is
|
|
2587
|
+
* persisted or fanned out. On by default; `CEZ_REDACT_SECRETS=0` opts out.
|
|
2588
|
+
*/
|
|
2589
|
+
redact(event) {
|
|
2590
|
+
const safe = this.sessionSecrets.size ? redactDeep(event, [...this.sessionSecrets]) : event;
|
|
2591
|
+
if (productEnv('REDACT_SECRETS') === '0')
|
|
2592
|
+
return safe;
|
|
2593
|
+
return redactDeep(safe, this.hostSecrets());
|
|
2594
|
+
}
|
|
2595
|
+
/** Best-effort scrub of one free-text string bound for `runs.json`. Honors
|
|
2596
|
+
* the `CEZ_REDACT_SECRETS=0` opt-out itself so every caller inherits it. */
|
|
2597
|
+
redactText(text) {
|
|
2598
|
+
return redactText(text, [...this.sessionSecrets], () => this.hostSecrets());
|
|
2599
|
+
}
|
|
2600
|
+
hostSecrets() {
|
|
2601
|
+
if (this.secretValues === null)
|
|
2602
|
+
this.secretValues = collectSecretValues();
|
|
2603
|
+
return this.secretValues;
|
|
2604
|
+
}
|
|
2605
|
+
/** The run's indexed transcript facts (#880): a read-only view, never a transcript read once
|
|
2606
|
+
* the run's index is loaded. An unreadable transcript answers as empty, as readEvents does. */
|
|
2607
|
+
transcriptFacts(runId) {
|
|
2608
|
+
return this.factsFor(runId).get(runId) ?? emptyFacts();
|
|
2609
|
+
}
|
|
2610
|
+
/** The run's transcript facts when they are known: undefined when the transcript exists but its
|
|
2611
|
+
* facts are not current or cannot be read, so a caller can tell "no ask" from "no evidence". */
|
|
2612
|
+
knownTranscriptFacts(runId) {
|
|
2613
|
+
return this.factsFor(runId).peek(runId);
|
|
2614
|
+
}
|
|
2615
|
+
hasProjection(runId, projectionId) {
|
|
2616
|
+
return this.transcriptFacts(runId).projectionIds.includes(projectionId);
|
|
2617
|
+
}
|
|
2618
|
+
factsWarming = Promise.resolve();
|
|
2619
|
+
/** Indexed family enumeration, including members not yet flushed to SQLite. */
|
|
2620
|
+
listFamilyRunIds(rootId) {
|
|
2621
|
+
const ids = new Set([rootId, ...(this.db?.listIdsByParent(rootId) ?? [])]);
|
|
2622
|
+
for (const [id, run] of this.held) {
|
|
2623
|
+
if (familyRootOf(run) === rootId)
|
|
2624
|
+
ids.add(id);
|
|
2625
|
+
else if (id !== rootId)
|
|
2626
|
+
ids.delete(id);
|
|
2627
|
+
}
|
|
2628
|
+
for (const id of this.deleted)
|
|
2629
|
+
ids.delete(id);
|
|
2630
|
+
return [...ids];
|
|
2631
|
+
}
|
|
2632
|
+
recoveryFactIds(familyRootId) {
|
|
2633
|
+
if (familyRootId !== undefined)
|
|
2634
|
+
return this.listFamilyRunIds(familyRootId);
|
|
2635
|
+
const ids = new Set();
|
|
2636
|
+
for (const [id, run] of this.held) {
|
|
2637
|
+
if (!isLiveRecord(run))
|
|
2638
|
+
continue;
|
|
2639
|
+
const root = familyRootOf(run);
|
|
2640
|
+
for (const member of root === undefined ? [id] : this.listFamilyRunIds(root))
|
|
2641
|
+
ids.add(member);
|
|
2642
|
+
}
|
|
2643
|
+
return [...ids];
|
|
2644
|
+
}
|
|
2645
|
+
/** Readiness owns no run snapshot across an await: refresh only the currently held owner. */
|
|
2646
|
+
async prepareTranscriptFacts(runIds) {
|
|
2647
|
+
for (const id of new Set(runIds)) {
|
|
2648
|
+
// Exact sidecars resolve immediately too: recovery must yield to timers/I/O between IDs.
|
|
2649
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
2650
|
+
await this.factsFor(id).ready(id);
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
prepareRecoveryFacts(familyRootId) {
|
|
2654
|
+
return this.prepareTranscriptFacts(this.recoveryFactIds(familyRootId));
|
|
2655
|
+
}
|
|
2656
|
+
/** Register IDs only here; the index yields between cache reads and off-loop rebuilds. */
|
|
2657
|
+
warmTranscriptFacts() {
|
|
2658
|
+
this.factsWarming = Promise.resolve().then(async () => {
|
|
2659
|
+
if (!this.db)
|
|
2660
|
+
return;
|
|
2661
|
+
const ids = new Set(this.recoveryFactIds());
|
|
2662
|
+
// hearsay-tools/cezarion#949: this microtask ends open()'s synchronous slice, and on a
|
|
2663
|
+
// large history the JSON scan for conversation roots plus one family query per root cost
|
|
2664
|
+
// as much again. Past one slice's worth of runs, yield before the scan and between root
|
|
2665
|
+
// batches; a small store (every unit test) still registers in this one microtask.
|
|
2666
|
+
const large = this.listRunIds().length > WARM_IDS_PER_SLICE;
|
|
2667
|
+
const turn = async () => {
|
|
2668
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
2669
|
+
return this.db !== null;
|
|
2670
|
+
};
|
|
2671
|
+
if (large && !await turn())
|
|
2672
|
+
return;
|
|
2673
|
+
const roots = this.listConversationRootIds();
|
|
2674
|
+
for (let i = 0; i < roots.length; i++) {
|
|
2675
|
+
if (large && i > 0 && i % WARM_IDS_PER_SLICE === 0 && !await turn())
|
|
2676
|
+
return;
|
|
2677
|
+
for (const id of this.listFamilyRunIds(roots[i]))
|
|
2678
|
+
ids.add(id);
|
|
2679
|
+
}
|
|
2680
|
+
if (large && !await turn())
|
|
2681
|
+
return;
|
|
2682
|
+
// Re-list after the yields: a run deleted meanwhile must not be registered again.
|
|
2683
|
+
const present = this.listRunIds();
|
|
2684
|
+
if (large) {
|
|
2685
|
+
const live = new Set(present);
|
|
2686
|
+
for (const id of ids)
|
|
2687
|
+
if (!live.has(id))
|
|
2688
|
+
ids.delete(id);
|
|
2689
|
+
}
|
|
2690
|
+
for (const id of present)
|
|
2691
|
+
ids.add(id);
|
|
2692
|
+
await this.facts.warm(ids);
|
|
2693
|
+
}).catch(() => undefined);
|
|
2694
|
+
}
|
|
2695
|
+
/** Resolves when the open-time warm-up is done (tests). */
|
|
2696
|
+
factsWarmIdle() {
|
|
2697
|
+
return this.factsWarming;
|
|
2698
|
+
}
|
|
2699
|
+
/** `readEvents` off the event loop (async read and async brotli), for collection paths. */
|
|
2700
|
+
async readEventsAsync(runId) {
|
|
2701
|
+
try {
|
|
2702
|
+
const raw = await readHistoryTextAsync(this.sourceDataDir(runId), runId);
|
|
2703
|
+
return raw === undefined ? [] : parseEvents(raw);
|
|
2704
|
+
}
|
|
2705
|
+
catch {
|
|
2706
|
+
return [];
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
readEvents(runId) {
|
|
2710
|
+
try {
|
|
2711
|
+
const raw = readHistoryText(this.sourceDataDir(runId), runId);
|
|
2712
|
+
if (raw === undefined)
|
|
2713
|
+
return [];
|
|
2714
|
+
return parseEvents(raw);
|
|
2715
|
+
}
|
|
2716
|
+
catch {
|
|
2717
|
+
return [];
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
/** Private process-generation evidence; never part of run JSON or SSE. */
|
|
2721
|
+
identityPath(id, write = false) {
|
|
2722
|
+
return this.executionPath(id, write).replace(/\.execution\.json$/, '.identity.json');
|
|
2723
|
+
}
|
|
2724
|
+
readWorkerIdentity(id) {
|
|
2725
|
+
try {
|
|
2726
|
+
const fd = openSync(this.identityPath(id), constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
2727
|
+
try {
|
|
2728
|
+
const info = fstatSync(fd);
|
|
2729
|
+
if (!info.isFile() || info.size > 16384 || (info.mode & 0o077))
|
|
2730
|
+
return undefined;
|
|
2731
|
+
const buffer = Buffer.alloc(16385);
|
|
2732
|
+
const count = readSync(fd, buffer, 0, buffer.length, 0);
|
|
2733
|
+
return workerExecutionIdentitySchema.parse(JSON.parse(buffer.subarray(0, count).toString('utf8')));
|
|
2734
|
+
}
|
|
2735
|
+
finally {
|
|
2736
|
+
closeSync(fd);
|
|
2737
|
+
}
|
|
2738
|
+
}
|
|
2739
|
+
catch {
|
|
2740
|
+
return undefined;
|
|
2741
|
+
}
|
|
2742
|
+
}
|
|
2743
|
+
writeWorkerIdentity(id, identity) {
|
|
2744
|
+
// Immutable acceptance evidence. An orphan from failed index publication cannot be adopted.
|
|
2745
|
+
const fd = openSync(this.identityPath(id, true), constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600);
|
|
2746
|
+
try {
|
|
2747
|
+
writeFileSync(fd, JSON.stringify(identity));
|
|
2748
|
+
fsyncSync(fd);
|
|
2749
|
+
}
|
|
2750
|
+
finally {
|
|
2751
|
+
closeSync(fd);
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
executionPath(id, write = false) {
|
|
2755
|
+
z.string().uuid().parse(id);
|
|
2756
|
+
const dir = join(write ? this.writeDir(id) : this.sourceDataDir(id), 'runs');
|
|
2757
|
+
if (write)
|
|
2758
|
+
mkdirSync(dir, { recursive: true });
|
|
2759
|
+
if (lstatSync(dir).isSymbolicLink() || realpathSync(dir) !== resolve(dir))
|
|
2760
|
+
throw new Error('Unsafe execution directory');
|
|
2761
|
+
return join(dir, `${id}.execution.json`);
|
|
2762
|
+
}
|
|
2763
|
+
readWorkerExecution(id) {
|
|
2764
|
+
try {
|
|
2765
|
+
const path = this.executionPath(id);
|
|
2766
|
+
const fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
2767
|
+
try {
|
|
2768
|
+
const info = fstatSync(fd);
|
|
2769
|
+
if (!info.isFile() || info.size > 16384 || (info.mode & 0o077))
|
|
2770
|
+
return undefined;
|
|
2771
|
+
const buffer = Buffer.alloc(16385);
|
|
2772
|
+
const count = readSync(fd, buffer, 0, buffer.length, 0);
|
|
2773
|
+
return workerExecutionSchema.parse(JSON.parse(buffer.subarray(0, count).toString('utf8')));
|
|
2774
|
+
}
|
|
2775
|
+
finally {
|
|
2776
|
+
closeSync(fd);
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2779
|
+
catch {
|
|
2780
|
+
return undefined;
|
|
2781
|
+
}
|
|
2782
|
+
}
|
|
2783
|
+
writeWorkerExecution(id, proof, fresh = false) {
|
|
2784
|
+
const path = this.executionPath(id, true);
|
|
2785
|
+
try {
|
|
2786
|
+
const existing = lstatSync(path);
|
|
2787
|
+
if (fresh || !existing.isFile() || existing.isSymbolicLink())
|
|
2788
|
+
throw new Error('Unsafe execution checkpoint');
|
|
2789
|
+
}
|
|
2790
|
+
catch (error) {
|
|
2791
|
+
if (error.code !== 'ENOENT')
|
|
2792
|
+
throw error;
|
|
2793
|
+
}
|
|
2794
|
+
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
2795
|
+
const fd = openSync(temporary, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600);
|
|
2796
|
+
try {
|
|
2797
|
+
writeFileSync(fd, JSON.stringify(proof));
|
|
2798
|
+
fsyncSync(fd);
|
|
2799
|
+
}
|
|
2800
|
+
finally {
|
|
2801
|
+
closeSync(fd);
|
|
2802
|
+
}
|
|
2803
|
+
try {
|
|
2804
|
+
renameSync(temporary, path);
|
|
2805
|
+
}
|
|
2806
|
+
finally {
|
|
2807
|
+
rmSync(temporary, { force: true });
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
/** Complete execution is not permission to reuse/delete persistent resources (#738).
|
|
2811
|
+
* Synchronous, fresh and generation/resource fenced; callers hold admission off across awaits. */
|
|
2812
|
+
workerResourcesSafe(id, generation, resourceId, admittingQueued = false) {
|
|
2813
|
+
return this.workerResourceHolders(id, generation, resourceId, { admittingQueued }) === 'safe';
|
|
2814
|
+
}
|
|
2815
|
+
/** `workerResourcesSafe` with the refusal's live PIDs (empty when no PID explains it). */
|
|
2816
|
+
workerResourceHolders(id, generation, resourceId, opts = {}) {
|
|
2817
|
+
if (autosaveCleanupBlocker(this.dataDir, id))
|
|
2818
|
+
return [];
|
|
2819
|
+
const run = this.peek(id);
|
|
2820
|
+
const proof = this.readWorkerExecution(id);
|
|
2821
|
+
if (!workerResourceAuthorized(run, proof, id, generation, resourceId, opts.admittingQueued))
|
|
2822
|
+
return [];
|
|
2823
|
+
const record = this.readWorkerProcesses(id, generation);
|
|
2824
|
+
if (record === 'unknown' || !agentTmpDirOwnershipProven(this.dataDir, id))
|
|
2825
|
+
return [];
|
|
2826
|
+
const paths = [run.delegation.workspace.path, ...agentTmpDirLocations(this.dataDir, id)];
|
|
2827
|
+
// Nothing can be deleted/reused at an absent path. This lets explicit history deletion
|
|
2828
|
+
// retire completed cleanup despite ambient denial, while recorded survivors/unknown evidence
|
|
2829
|
+
// still block. Only ENOENT proves absence; existsSync would also hide access errors.
|
|
2830
|
+
if (workerResourcePathsGone(paths))
|
|
2831
|
+
return workerResourceAbsentHolders(record);
|
|
2832
|
+
return workerResourceLiveHolders(inspectGeneration({ ...(record === 'absent' ? {} : { record }), paths, cwds: opts.cwds }));
|
|
2833
|
+
}
|
|
2834
|
+
/** Upgrade a legacy completed checkpoint while terminal task ownership is still known.
|
|
2835
|
+
* No holder scan here: settlement and scheduling must never wait on cleanup proof. */
|
|
2836
|
+
retainWorkerScratchCleanup(id) {
|
|
2837
|
+
const run = this.peek(id), proof = this.readWorkerExecution(id);
|
|
2838
|
+
if (run?.delegation?.role !== 'worker' || ['queued', 'running', 'waiting'].includes(run.status) || proof?.phase !== 'complete')
|
|
2839
|
+
return;
|
|
2840
|
+
const { resourceId, path } = run.delegation.workspace;
|
|
2841
|
+
if (proof.scratchCleanup?.resourceId === resourceId && proof.scratchCleanup.path === path)
|
|
2842
|
+
return;
|
|
2843
|
+
this.writeWorkerExecution(id, { ...proof, scratchCleanup: { resourceId, path } });
|
|
2844
|
+
}
|
|
2845
|
+
/** Cleanup can outlive its index row, but never its generation or terminal task intent.
|
|
2846
|
+
* `cwds` is the scratch reprobe tick's shared `/proc` snapshot (hearsay-tools/cezarion#879). */
|
|
2847
|
+
workerScratchResourcesSafe(id, generation, resourceId, cwds) {
|
|
2848
|
+
if (autosaveCleanupBlocker(this.dataDir, id))
|
|
2849
|
+
return false;
|
|
2850
|
+
const run = this.peek(id), proof = this.readWorkerExecution(id);
|
|
2851
|
+
if (proof?.phase !== 'complete' || proof.generation !== generation || proof.scratchCleanup?.resourceId !== resourceId ||
|
|
2852
|
+
(run && ['queued', 'running', 'waiting'].includes(run.status)))
|
|
2853
|
+
return false;
|
|
2854
|
+
if (run?.delegation?.role === 'worker')
|
|
2855
|
+
return run.delegation.workspace.path === proof.scratchCleanup.path &&
|
|
2856
|
+
this.workerResourceHolders(id, generation, resourceId, { cwds }) === 'safe';
|
|
2857
|
+
// A valid different role contradicts the retained intent. Quarantined/missing metadata
|
|
2858
|
+
// supplies no new authority; only the private terminal checkpoint authorizes scratch.
|
|
2859
|
+
if (run && run.delegation?.role !== 'invalid')
|
|
2860
|
+
return false;
|
|
2861
|
+
const record = this.readWorkerProcesses(id, generation);
|
|
2862
|
+
if (record === 'unknown' || !agentTmpDirOwnershipProven(this.dataDir, id))
|
|
2863
|
+
return false;
|
|
2864
|
+
return inspectGeneration({ ...(record === 'absent' ? {} : { record }),
|
|
2865
|
+
paths: [proof.scratchCleanup.path, ...agentTmpDirLocations(this.dataDir, id)], cwds }).liveness === 'gone';
|
|
2866
|
+
}
|
|
2867
|
+
commitWorkerExecutionStart(id) {
|
|
2868
|
+
const run = this.peek(id);
|
|
2869
|
+
if (run?.delegation?.role !== 'worker' || run.delegation.destroy)
|
|
2870
|
+
throw new Error('Worker cannot start');
|
|
2871
|
+
const prior = this.readWorkerExecution(id);
|
|
2872
|
+
if (prior?.abandoned)
|
|
2873
|
+
throw new Error('Worker execution was abandoned; spawn a new worker instead of resuming it');
|
|
2874
|
+
if (!prior || (prior.phase !== 'queued' && prior.phase !== 'complete')) {
|
|
2875
|
+
throw new Error('Worker execution checkpoint does not prove safe admission');
|
|
2876
|
+
}
|
|
2877
|
+
if (!this.workerResourcesSafe(id, prior.generation, run.delegation.workspace.resourceId, true)) {
|
|
2878
|
+
throw new Error('Worker resources may still be held by a process; reuse is not proven safe');
|
|
2879
|
+
}
|
|
2880
|
+
const generation = randomUUID();
|
|
2881
|
+
// #469: the controller is durable before `starting`, so a crash leaves a provable generation.
|
|
2882
|
+
this.writeWorkerProcesses(id, { generation, controller: { pid: process.pid, ...startToken(process.pid) }, processes: [] });
|
|
2883
|
+
this.writeWorkerExecution(id, { generation, phase: 'starting' });
|
|
2884
|
+
return generation;
|
|
2885
|
+
}
|
|
2886
|
+
/** Private process record (#469): which processes a generation spawned, never part of run JSON or SSE. */
|
|
2887
|
+
processesPath(id, write = false) {
|
|
2888
|
+
return this.executionPath(id, write).replace(/\.execution\.json$/, '.processes.json');
|
|
2889
|
+
}
|
|
2890
|
+
/** Tri-state: only ENOENT is `absent` (legacy, scan-only). A present record that is unreadable,
|
|
2891
|
+
* wrongly permissioned, malformed or of another generation is `unknown`: never finalized or reaped. */
|
|
2892
|
+
readWorkerProcesses(id, generation) {
|
|
2893
|
+
try {
|
|
2894
|
+
const fd = openSync(this.processesPath(id), constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
2895
|
+
try {
|
|
2896
|
+
const info = fstatSync(fd);
|
|
2897
|
+
if (!info.isFile() || info.size > 8192 || (info.mode & 0o077))
|
|
2898
|
+
return 'unknown';
|
|
2899
|
+
const buffer = Buffer.alloc(8193);
|
|
2900
|
+
const count = readSync(fd, buffer, 0, buffer.length, 0);
|
|
2901
|
+
const record = workerProcessRecordSchema.parse(JSON.parse(buffer.subarray(0, count).toString('utf8')));
|
|
2902
|
+
return record.generation === generation ? record : 'unknown';
|
|
2903
|
+
}
|
|
2904
|
+
finally {
|
|
2905
|
+
closeSync(fd);
|
|
2906
|
+
}
|
|
2907
|
+
}
|
|
2908
|
+
catch (error) {
|
|
2909
|
+
return error.code === 'ENOENT' ? 'absent' : 'unknown';
|
|
2910
|
+
}
|
|
2911
|
+
}
|
|
2912
|
+
writeWorkerProcesses(id, record) {
|
|
2913
|
+
const path = this.processesPath(id, true);
|
|
2914
|
+
try {
|
|
2915
|
+
const existing = lstatSync(path);
|
|
2916
|
+
if (!existing.isFile() || existing.isSymbolicLink())
|
|
2917
|
+
throw new Error('Unsafe process record');
|
|
2918
|
+
}
|
|
2919
|
+
catch (error) {
|
|
2920
|
+
if (error.code !== 'ENOENT')
|
|
2921
|
+
throw error;
|
|
2922
|
+
}
|
|
2923
|
+
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
2924
|
+
const fd = openSync(temporary, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600);
|
|
2925
|
+
try {
|
|
2926
|
+
writeFileSync(fd, JSON.stringify(workerProcessRecordSchema.parse(record)));
|
|
2927
|
+
fsyncSync(fd);
|
|
2928
|
+
}
|
|
2929
|
+
finally {
|
|
2930
|
+
closeSync(fd);
|
|
2931
|
+
}
|
|
2932
|
+
try {
|
|
2933
|
+
renameSync(temporary, path);
|
|
2934
|
+
}
|
|
2935
|
+
finally {
|
|
2936
|
+
rmSync(temporary, { force: true });
|
|
2937
|
+
}
|
|
2938
|
+
}
|
|
2939
|
+
/** Bound to the current generation. Past the cap the record never drops an entry: the
|
|
2940
|
+
* working-directory scan is the remaining evidence. */
|
|
2941
|
+
appendWorkerProcess(id, generation, pid, pgid) {
|
|
2942
|
+
const record = this.readWorkerProcesses(id, generation);
|
|
2943
|
+
if (typeof record === 'string' || this.readWorkerExecution(id)?.generation !== generation)
|
|
2944
|
+
return false;
|
|
2945
|
+
const entry = { pid, ...startToken(pid), ...(pgid === undefined ? {} : { pgid }) };
|
|
2946
|
+
if (record.processes.some(known => known.pid === entry.pid && known.startToken === entry.startToken))
|
|
2947
|
+
return true;
|
|
2948
|
+
if (record.processes.length >= WORKER_PROCESS_CAP)
|
|
2949
|
+
return false;
|
|
2950
|
+
this.writeWorkerProcesses(id, { ...record, processes: [...record.processes, entry] });
|
|
2951
|
+
return true;
|
|
2952
|
+
}
|
|
2953
|
+
commitWorkerExecutionComplete(id, generation) {
|
|
2954
|
+
const proof = this.readWorkerExecution(id);
|
|
2955
|
+
if (!proof || proof.generation !== generation)
|
|
2956
|
+
return false;
|
|
2957
|
+
if (proof.phase === 'queued' && this.peek(id)?.status !== 'cancelled')
|
|
2958
|
+
return false;
|
|
2959
|
+
try {
|
|
2960
|
+
const run = this.peek(id);
|
|
2961
|
+
if (run?.delegation?.role !== 'worker')
|
|
2962
|
+
return false;
|
|
2963
|
+
this.commitIndex(new Map([[id, run]]));
|
|
2964
|
+
if (this.readWorkerExecution(id)?.generation !== generation)
|
|
2965
|
+
return false;
|
|
2966
|
+
this.writeWorkerExecution(id, { generation, phase: 'complete',
|
|
2967
|
+
// Nothing abandons an execution any more (hearsay-tools/cezarion#889); keep a checkpoint that already was.
|
|
2968
|
+
...(proof.abandoned ? { abandoned: true } : {}),
|
|
2969
|
+
...(proof.phase === 'queued' || proof.neverMaterialized ? { neverMaterialized: true } : {}),
|
|
2970
|
+
...(!['queued', 'running', 'waiting'].includes(run.status) ? { scratchCleanup: {
|
|
2971
|
+
resourceId: run.delegation.workspace.resourceId, path: run.delegation.workspace.path
|
|
2972
|
+
} } : {}) });
|
|
2973
|
+
// The earlier index event cannot attest exit: subscribers must observe the
|
|
2974
|
+
// durable private proof before a terminal worker can satisfy a lifecycle wait.
|
|
2975
|
+
this.emit('run', this.peek(id));
|
|
2976
|
+
return true;
|
|
2977
|
+
}
|
|
2978
|
+
catch {
|
|
2979
|
+
return false;
|
|
2980
|
+
}
|
|
2981
|
+
}
|
|
2982
|
+
/** Complete replacement evidence can stand in for absent child history, never for a present execution. */
|
|
2983
|
+
readDeletedWorkerResult(parentId, workerId) {
|
|
2984
|
+
if (this.hasRun(workerId))
|
|
2985
|
+
return undefined;
|
|
2986
|
+
const parent = this.peek(parentId);
|
|
2987
|
+
if (parent?.delegation?.role !== 'root' || parent.delegation.historyDeletion)
|
|
2988
|
+
return undefined;
|
|
2989
|
+
const deletion = parent.delegation.receipts.find(receipt => receipt.workerId === workerId)?.deletion;
|
|
2990
|
+
if (deletion?.phase !== 'complete')
|
|
2991
|
+
return undefined;
|
|
2992
|
+
const result = this.readWorkerResult(parentId, workerId);
|
|
2993
|
+
return result?.settled && ['review', 'done', 'failed', 'cancelled'].includes(result.status) &&
|
|
2994
|
+
result.cleanup === 'complete' && result.revision === deletion.revision &&
|
|
2995
|
+
result.workspace.resourceId === deletion.resourceId ? result : undefined;
|
|
2996
|
+
}
|
|
2997
|
+
/** A deleted child's receipt replaces the private proof only after the deletion checkpoint. */
|
|
2998
|
+
workerDeletionEvidence(id) {
|
|
2999
|
+
const child = this.peek(id);
|
|
3000
|
+
if (child?.delegation?.role !== 'worker' || child.delegation.workspace.ownerRunId !== id ||
|
|
3001
|
+
child.delegation.destroy?.phase !== 'complete' || child.delegation.destroy.remaining.length ||
|
|
3002
|
+
!['review', 'done', 'failed', 'cancelled'].includes(child.status))
|
|
3003
|
+
return false;
|
|
3004
|
+
return this.workerDeletionResult(id, child);
|
|
3005
|
+
}
|
|
3006
|
+
workerDeletionResult(id, child) {
|
|
3007
|
+
const parent = this.peek(child.delegation.parentRunId);
|
|
3008
|
+
if (parent?.delegation?.role !== 'root' || parent.delegation.historyDeletion)
|
|
3009
|
+
return false;
|
|
3010
|
+
const receipt = parent.delegation.receipts.find(entry => entry.workerId === id);
|
|
3011
|
+
const result = this.readWorkerResult(parent.id, id);
|
|
3012
|
+
if (!receipt || !result?.settled || result.cleanup !== 'complete' || result.status !== child.status ||
|
|
3013
|
+
result.revision !== (child.delegation.executionRevision ?? 0))
|
|
3014
|
+
return false;
|
|
3015
|
+
const { state: _state, ...workspace } = result.workspace;
|
|
3016
|
+
if (JSON.stringify(workspace) !== JSON.stringify(child.delegation.workspace))
|
|
3017
|
+
return false;
|
|
3018
|
+
return workerDeletionProof(receipt.deletion, result.revision, workspace.resourceId, {
|
|
3019
|
+
readProof: () => this.readWorkerExecution(id), resourcesSafe: generation => this.workerResourcesSafe(id, generation, workspace.resourceId),
|
|
3020
|
+
scratchMayExist: () => agentTmpDirMayExist(this.dataDir, id), executionExists: () => existsSync(this.executionPath(id)),
|
|
3021
|
+
});
|
|
3022
|
+
}
|
|
3023
|
+
canDeleteRun(id, kind = 'record') {
|
|
3024
|
+
if (this.fallbackDeleteError(id, kind))
|
|
3025
|
+
return false;
|
|
3026
|
+
if (autosaveCleanupBlocker(this.dataDir, id))
|
|
3027
|
+
return false;
|
|
3028
|
+
const run = this.peek(id);
|
|
3029
|
+
if (run?.delegation?.role === 'invalid')
|
|
3030
|
+
return false;
|
|
3031
|
+
if (run?.delegation?.role === 'worker')
|
|
3032
|
+
return this.workerDeletionEvidence(id);
|
|
3033
|
+
const privateWorkers = workerEvidenceRunIds(this.dataDir);
|
|
3034
|
+
if (!privateWorkers || privateWorkers.includes(id))
|
|
3035
|
+
return false;
|
|
3036
|
+
if (run?.delegation?.role === 'root') {
|
|
3037
|
+
if (run.delegation.finishRequestedAt && ['queued', 'running', 'waiting'].includes(run.status))
|
|
3038
|
+
return false;
|
|
3039
|
+
// Keep the parent's receipt and result ownership until each child history is explicitly removed.
|
|
3040
|
+
if (this.listWorkersOf(id).length > 0)
|
|
3041
|
+
return false;
|
|
3042
|
+
return run.delegation.receipts.every(receipt => {
|
|
3043
|
+
if (this.hasRun(receipt.workerId) || receipt.deletion?.phase !== 'complete')
|
|
3044
|
+
return false;
|
|
3045
|
+
// A pending parent deletion already validated the results before removing their bytes.
|
|
3046
|
+
if (run.delegation?.role === 'root' && run.delegation.historyDeletion === 'pending')
|
|
3047
|
+
return true;
|
|
3048
|
+
return this.readDeletedWorkerResult(id, receipt.workerId) !== undefined;
|
|
3049
|
+
});
|
|
3050
|
+
}
|
|
3051
|
+
return this.listWorkersOf(id).length === 0;
|
|
3052
|
+
}
|
|
3053
|
+
/** Delegated history deletion is synchronous and checkpointed; any failed byte/index removal remains retryable. */
|
|
3054
|
+
deleteDelegatedRun(id) {
|
|
3055
|
+
const run = this.peek(id);
|
|
3056
|
+
try {
|
|
3057
|
+
if (run.delegation?.role === 'worker') {
|
|
3058
|
+
const parent = this.peek(run.delegation.parentRunId);
|
|
3059
|
+
if (parent.delegation?.role !== 'root')
|
|
3060
|
+
return false;
|
|
3061
|
+
const receipt = parent.delegation.receipts.find(entry => entry.workerId === id);
|
|
3062
|
+
const result = this.readWorkerResult(parent.id, id);
|
|
3063
|
+
const deletion = receipt.deletion ?? { phase: 'pending', revision: result.revision,
|
|
3064
|
+
resourceId: run.delegation.workspace.resourceId, generation: this.readWorkerExecution(id).generation };
|
|
3065
|
+
this.commitDelegation([{ id: parent.id, delegation: { ...parent.delegation,
|
|
3066
|
+
receipts: parent.delegation.receipts.map(entry => entry.workerId === id ? { ...entry, deletion } : entry),
|
|
3067
|
+
} }]);
|
|
3068
|
+
// Do not advertise artifact bytes as retained while interrupted deletion is in progress.
|
|
3069
|
+
this.commitWorkerResult(parent.id, { ...result, observedAt: new Date().toISOString(),
|
|
3070
|
+
diff: result.diff.state === 'available' ? { ...result.diff, snapshotId: randomUUID() } : result.diff,
|
|
3071
|
+
artifacts: result.artifacts.state === 'available' ? { ...result.artifacts,
|
|
3072
|
+
items: result.artifacts.items.map(item => ({ state: 'unavailable', reason: 'unreadable',
|
|
3073
|
+
detail: 'History deletion is in progress', id: item.id, path: item.path })),
|
|
3074
|
+
} : result.artifacts,
|
|
3075
|
+
}, this.readWorkerResultDiff(parent.id, id));
|
|
3076
|
+
this.removeRunHistoryBytes(id);
|
|
3077
|
+
// Delete scratch while its generation evidence still exists; failed removal keeps it.
|
|
3078
|
+
removeAgentTmpDir(this.dataDir, id);
|
|
3079
|
+
if (agentTmpDirMayExist(this.dataDir, id))
|
|
3080
|
+
return false;
|
|
3081
|
+
// Private process/account evidence is now replaced by the exact parent receipt.
|
|
3082
|
+
rmSync(this.identityPath(id), { force: true });
|
|
3083
|
+
rmSync(this.processesPath(id), { force: true });
|
|
3084
|
+
rmSync(this.executionPath(id), { force: true });
|
|
3085
|
+
const retained = this.readWorkerResult(parent.id, id);
|
|
3086
|
+
this.commitWorkerResult(parent.id, { ...retained, observedAt: new Date().toISOString(),
|
|
3087
|
+
diff: retained.diff.state === 'available' ? { ...retained.diff, snapshotId: randomUUID() } : retained.diff,
|
|
3088
|
+
artifacts: retained.artifacts.state === 'available' ? { ...retained.artifacts,
|
|
3089
|
+
items: retained.artifacts.items.map(item => ({ state: 'deleted', reason: 'missing', id: item.id, path: item.path })),
|
|
3090
|
+
} : retained.artifacts,
|
|
3091
|
+
}, this.readWorkerResultDiff(parent.id, id));
|
|
3092
|
+
this.commitIndex(new Map([[parent.id, { ...parent, delegation: { ...parent.delegation,
|
|
3093
|
+
receipts: parent.delegation.receipts.map(entry => entry.workerId === id ? { ...entry, deletion: { ...deletion, phase: 'complete' } } : entry),
|
|
3094
|
+
} }], [id, null]]));
|
|
3095
|
+
}
|
|
3096
|
+
else if (run.delegation?.role === 'root') {
|
|
3097
|
+
this.commitDelegation([{ id, delegation: { ...run.delegation, historyDeletion: 'pending' } }]);
|
|
3098
|
+
this.removeRunHistoryBytes(id);
|
|
3099
|
+
rmSync(join(this.dataDir, 'runs', `${id}-worker-results`), { recursive: true, force: true });
|
|
3100
|
+
this.commitIndex(new Map([[id, null]]));
|
|
3101
|
+
}
|
|
3102
|
+
else
|
|
3103
|
+
return false;
|
|
3104
|
+
removeAgentTmpDir(this.dataDir, id);
|
|
3105
|
+
this.seqs.delete(id);
|
|
3106
|
+
return true;
|
|
3107
|
+
}
|
|
3108
|
+
catch {
|
|
3109
|
+
return false;
|
|
3110
|
+
}
|
|
3111
|
+
}
|
|
3112
|
+
removeRunHistoryBytes(id) {
|
|
3113
|
+
// Fixed owned paths only: never follow context descriptors supplied in persisted metadata.
|
|
3114
|
+
z.uuid().parse(id);
|
|
3115
|
+
const dir = join(this.dataDir, 'runs');
|
|
3116
|
+
if (realpathSync(dir) !== resolve(dir))
|
|
3117
|
+
throw new Error('History storage redirected');
|
|
3118
|
+
this.facts.forget(id);
|
|
3119
|
+
removeHistory(this.dataDir, id);
|
|
3120
|
+
rmSync(this.handoffPath(id), { force: true });
|
|
3121
|
+
rmSync(this.imagesDir(id), { recursive: true, force: true });
|
|
3122
|
+
removeArtifacts(this.dataDir, id);
|
|
3123
|
+
clearAutosaveCleanup(this.dataDir, id);
|
|
3124
|
+
}
|
|
3125
|
+
deleteRun(id) {
|
|
3126
|
+
const refusal = this.fallbackDeleteError(id);
|
|
3127
|
+
if (refusal)
|
|
3128
|
+
throw new Error(refusal);
|
|
3129
|
+
if (!this.canDeleteRun(id))
|
|
3130
|
+
return false;
|
|
3131
|
+
const run = this.peek(id);
|
|
3132
|
+
if (run && this.db && (this.quarantined.has(id) || !this.claimFamilies([familyKey(run)]).size))
|
|
3133
|
+
return false;
|
|
3134
|
+
if (run?.delegation?.role === 'worker' || run?.delegation?.role === 'root')
|
|
3135
|
+
return this.deleteDelegatedRun(id);
|
|
3136
|
+
if (run)
|
|
3137
|
+
clearAutosaveCleanup(this.dataDir, id);
|
|
3138
|
+
const existed = run !== undefined;
|
|
3139
|
+
this.held.delete(id);
|
|
3140
|
+
if (existed) {
|
|
3141
|
+
this.markDeleted(id, familyKey(run));
|
|
3142
|
+
removeAgentTmpDir(this.dataDir, id);
|
|
3143
|
+
this.seqs.delete(id);
|
|
3144
|
+
this.scheduleSave();
|
|
3145
|
+
this.emit('deleted', id);
|
|
3146
|
+
}
|
|
3147
|
+
return existed;
|
|
3148
|
+
}
|
|
3149
|
+
/** Write the pending rows out now (used on shutdown). */
|
|
3150
|
+
flush() {
|
|
3151
|
+
if (this.saveTimer) {
|
|
3152
|
+
clearTimeout(this.saveTimer);
|
|
3153
|
+
this.saveTimer = null;
|
|
3154
|
+
}
|
|
3155
|
+
this.saveNow();
|
|
3156
|
+
}
|
|
3157
|
+
// ---- internals -----------------------------------------------------------
|
|
3158
|
+
seqs = new Map();
|
|
3159
|
+
nextSeq(runId) {
|
|
3160
|
+
const next = (this.seqs.get(runId) ?? this.rehydrateSeq(runId)) + 1;
|
|
3161
|
+
this.seqs.set(runId, next);
|
|
3162
|
+
return next;
|
|
3163
|
+
}
|
|
3164
|
+
/** After a restart the in-memory counter is empty while the run's NDJSON file
|
|
3165
|
+
* keeps the history. Restarting from 1 would collide with the seqs a client
|
|
3166
|
+
* already replayed — its `seq > maxSeq` dedup then silently drops every
|
|
3167
|
+
* resumed event, even across a reload (the frozen-transcript symptom class
|
|
3168
|
+
* of #424). One file read on the first post-restart append per run. */
|
|
3169
|
+
rehydrateSeq(runId) {
|
|
3170
|
+
return this.transcriptFacts(runId).lastSeq;
|
|
3171
|
+
}
|
|
3172
|
+
eventsPath(runId) {
|
|
3173
|
+
return historyPaths(this.sourceDataDir(runId), runId).plain;
|
|
3174
|
+
}
|
|
3175
|
+
/** Same location `handoffPath()` in handoff.ts produces — inlined to keep
|
|
3176
|
+
* the store free of upward imports. */
|
|
3177
|
+
handoffPath(runId) {
|
|
3178
|
+
return join(this.sourceDataDir(runId), 'runs', `${runId}.handoff.md`);
|
|
3179
|
+
}
|
|
3180
|
+
/** Agent screenshots persisted by the run manager (see persistImage). */
|
|
3181
|
+
imagesDir(runId) {
|
|
3182
|
+
return join(this.sourceDataDir(runId), 'runs', `${runId}-images`);
|
|
3183
|
+
}
|
|
3184
|
+
touch(run) {
|
|
3185
|
+
this.dirty.add(run.id);
|
|
3186
|
+
this.scheduleSave();
|
|
3187
|
+
this.emit('run', run);
|
|
3188
|
+
this.maybeEnqueueHistoryCompress(run);
|
|
3189
|
+
}
|
|
3190
|
+
/** Form follows archived: a terminal archived run with a plain transcript is queued again. */
|
|
3191
|
+
maybeEnqueueHistoryCompress(run) {
|
|
3192
|
+
if (run.archived && !isLiveStatus(run.status) && hasPlainHistory(this.dataDir, run.id)) {
|
|
3193
|
+
this.compressor.enqueue(run.id);
|
|
3194
|
+
}
|
|
3195
|
+
}
|
|
3196
|
+
/** The held record is gone; the next save or commit deletes its row, fenced by `family`'s claim
|
|
3197
|
+
* and by the revision this store last saw (kept in `base` until the delete is written). */
|
|
3198
|
+
markDeleted(id, family) {
|
|
3199
|
+
this.liveItems.forget(id);
|
|
3200
|
+
this.coldReads.forget(id);
|
|
3201
|
+
const read = this.coldBase.get(id);
|
|
3202
|
+
if (!this.base.has(id) && read)
|
|
3203
|
+
this.base.set(id, storedRow(read));
|
|
3204
|
+
this.dirty.delete(id);
|
|
3205
|
+
this.deleted.add(id);
|
|
3206
|
+
this.deletedFamilies.set(id, family);
|
|
3207
|
+
this.historyOwed.add(id);
|
|
3208
|
+
}
|
|
3209
|
+
/** A deleted run's history files, once the delete of its row has committed. Best effort, as
|
|
3210
|
+
* deletion always was. */
|
|
3211
|
+
removeOwedHistory(id) {
|
|
3212
|
+
if (!this.historyOwed.delete(id))
|
|
3213
|
+
return;
|
|
3214
|
+
try {
|
|
3215
|
+
this.facts.forget(id);
|
|
3216
|
+
removeHistory(this.dataDir, id);
|
|
3217
|
+
rmSync(this.handoffPath(id), { force: true });
|
|
3218
|
+
rmSync(this.imagesDir(id), { recursive: true, force: true });
|
|
3219
|
+
removeArtifacts(this.dataDir, id);
|
|
3220
|
+
}
|
|
3221
|
+
catch { /* best effort */ }
|
|
3222
|
+
}
|
|
3223
|
+
/**
|
|
3224
|
+
* Count-based history retention: of the runs past the newest `MAX_RUNS_KEPT` unarchived ones,
|
|
3225
|
+
* archive each finished run without delegation that is not pinned and whose family this store
|
|
3226
|
+
* can claim. Ranked over the row keys (the `created_at` index, no record decoded) with memory
|
|
3227
|
+
* laid over them, since a run created or archived since the last save has no row yet; the rows
|
|
3228
|
+
* past the cut are then filtered in `runs.db`, so only real candidates decode. Archived runs
|
|
3229
|
+
* are never deleted by retention — only an explicit delete, worker destruction, or history
|
|
3230
|
+
* deletion removes a run.
|
|
3231
|
+
*/
|
|
3232
|
+
pruneOldRuns() {
|
|
3233
|
+
const ranked = new Map();
|
|
3234
|
+
for (const key of this.db?.listKeysWhere('archived = ?', [0]) ?? []) {
|
|
3235
|
+
if (!this.held.has(key.id) && !this.deleted.has(key.id))
|
|
3236
|
+
ranked.set(key.id, key);
|
|
3237
|
+
}
|
|
3238
|
+
for (const run of this.held.values())
|
|
3239
|
+
if (!run.archived)
|
|
3240
|
+
ranked.set(run.id, { id: run.id, ...this.listOrder(run) });
|
|
3241
|
+
const overflow = [...ranked.values()].sort(newestFirst).slice(MAX_RUNS_KEPT).map((key) => key.id);
|
|
3242
|
+
if (overflow.length === 0)
|
|
3243
|
+
return;
|
|
3244
|
+
const stale = overflow.flatMap((id) => {
|
|
3245
|
+
const run = this.held.get(id);
|
|
3246
|
+
// Retention must not archive a live task between turns, a pinned run, or a delegation
|
|
3247
|
+
// family whose history is promised until explicit deletion.
|
|
3248
|
+
return run && !this.hasFallbackRun(id) && RETENTION_RUN_RULE.matches(run) ? [id] : [];
|
|
3249
|
+
});
|
|
3250
|
+
const skipped = overflow.filter(id => this.hasFallbackRun(id)).length;
|
|
3251
|
+
if (skipped)
|
|
3252
|
+
console.info(`${logPrefix()} Retention skipped ${skipped} read-only fallback run(s) under ${dataReadDirs(this.dataDir)[1]}.`);
|
|
3253
|
+
const cold = overflow.filter((id) => !this.held.has(id) && !this.hasFallbackRun(id));
|
|
3254
|
+
if (cold.length > 0) {
|
|
3255
|
+
// Another process's runs are its own to keep or archive (#779, plan step 3).
|
|
3256
|
+
const rows = this.db.listWhere(RETENTION_RUN_RULE.sql, [JSON.stringify(cold)]);
|
|
3257
|
+
const writable = this.claimFamilies(rows.map(rowFamily));
|
|
3258
|
+
for (const row of rows)
|
|
3259
|
+
if (writable.has(rowFamily(row)))
|
|
3260
|
+
stale.push(row.id);
|
|
3261
|
+
}
|
|
3262
|
+
for (const id of stale) {
|
|
3263
|
+
const run = this.record(id);
|
|
3264
|
+
if (!run || run.archived || !RETENTION_RUN_RULE.matches(run))
|
|
3265
|
+
continue;
|
|
3266
|
+
this.applyArchived(run, true);
|
|
3267
|
+
removeAgentTmpDir(this.dataDir, id);
|
|
3268
|
+
}
|
|
3269
|
+
}
|
|
3270
|
+
/** Debounced so token-usage updates don't rewrite a row per event. Nothing to schedule without
|
|
3271
|
+
* a database: a `RunStore.unavailable` store, or a closed one, writes nothing. */
|
|
3272
|
+
scheduleSave() {
|
|
3273
|
+
if (this.saveTimer || !this.db)
|
|
3274
|
+
return;
|
|
3275
|
+
this.saveTimer = setTimeout(() => {
|
|
3276
|
+
this.saveTimer = null;
|
|
3277
|
+
this.saveNow();
|
|
3278
|
+
}, 300);
|
|
3279
|
+
this.saveTimer.unref?.();
|
|
3280
|
+
}
|
|
3281
|
+
/** The single database write, so a test can fail it the way a full disk would. */
|
|
3282
|
+
writeIndex(changes) {
|
|
3283
|
+
const transaction = (changes) => { assertProjectWritable(this.dataDir); return this.db.transaction(changes); };
|
|
3284
|
+
return this.persistence ? this.persistence(changes, transaction) : transaction(changes);
|
|
3285
|
+
}
|
|
3286
|
+
/**
|
|
3287
|
+
* Write `staged` plus everything still pending — dirty rows from memory, deletions — in one
|
|
3288
|
+
* transaction, then clear the pending marks. Throws with nothing cleared when it fails.
|
|
3289
|
+
* Never copies or sorts the whole store: the cost is the rows that changed.
|
|
3290
|
+
*
|
|
3291
|
+
* Fenced (#779, plan step 3): the transaction commits only if every family it writes still
|
|
3292
|
+
* carries this store's claim and every row is at the revision this store last saw. Otherwise
|
|
3293
|
+
* nothing is written and `handleConflicts` deals with the rows that changed under it.
|
|
3294
|
+
*/
|
|
3295
|
+
persist(staged) {
|
|
3296
|
+
const upserts = [];
|
|
3297
|
+
const deletes = [];
|
|
3298
|
+
const families = new Map();
|
|
3299
|
+
const extras = new Map();
|
|
3300
|
+
const encode = (run) => {
|
|
3301
|
+
const encoded = encodeRawRecord(run, (this.base.get(run.id) ?? this.coldBase.get(run.id))?.extras);
|
|
3302
|
+
extras.set(run.id, encoded.extras);
|
|
3303
|
+
const projected = { ...run };
|
|
3304
|
+
restoreOriginatingIssue(projected);
|
|
3305
|
+
upserts.push({ ...encodeRunRow(run, encoded.data),
|
|
3306
|
+
issueAttributionCurrent: isDeepStrictEqual(projected, run) });
|
|
3307
|
+
};
|
|
3308
|
+
for (const [id, next] of staged) {
|
|
3309
|
+
if (next)
|
|
3310
|
+
encode(next);
|
|
3311
|
+
else
|
|
3312
|
+
deletes.push(id);
|
|
3313
|
+
families.set(id, next ? familyKey(next) : this.familyOf(id));
|
|
3314
|
+
}
|
|
3315
|
+
for (const id of this.dirty) {
|
|
3316
|
+
const run = this.held.get(id);
|
|
3317
|
+
if (!run || staged.has(id))
|
|
3318
|
+
continue;
|
|
3319
|
+
encode(run);
|
|
3320
|
+
families.set(id, familyKey(run));
|
|
3321
|
+
}
|
|
3322
|
+
for (const id of this.deleted) {
|
|
3323
|
+
if (staged.has(id))
|
|
3324
|
+
continue;
|
|
3325
|
+
deletes.push(id);
|
|
3326
|
+
families.set(id, this.familyOf(id));
|
|
3327
|
+
}
|
|
3328
|
+
// A row's insertion order (`seq`) is the order this write inserts it in, and it is where the
|
|
3329
|
+
// run lists among runs created in the same millisecond. Insert new runs in creation order (the
|
|
3330
|
+
// held set's, then staged runs not held yet), whether staged or dirty brought them here.
|
|
3331
|
+
if (upserts.filter((row) => !this.base.has(row.id)).length > 1) {
|
|
3332
|
+
const created = new Map();
|
|
3333
|
+
for (const id of [...this.held.keys(), ...staged.keys()])
|
|
3334
|
+
if (!created.has(id))
|
|
3335
|
+
created.set(id, created.size);
|
|
3336
|
+
upserts.sort((a, b) => created.get(a.id) - created.get(b.id));
|
|
3337
|
+
}
|
|
3338
|
+
// A held run whose write moved it into a new family (a worker quarantined to `invalid`) takes
|
|
3339
|
+
// that family's claim first; one it cannot take fails the fence below as claim-lost.
|
|
3340
|
+
const moved = [...new Set(families.values())].filter((family) => !this.claimed.has(family) && !this.pendingClaims.has(family));
|
|
3341
|
+
if (moved.length > 0)
|
|
3342
|
+
this.claimFamilies(moved, { allowLive: true });
|
|
3343
|
+
let commit;
|
|
3344
|
+
try {
|
|
3345
|
+
commit = this.writeIndex({ upserts, deletes, ...(this.owner ? { fence: this.fence(families) } : {}) });
|
|
3346
|
+
}
|
|
3347
|
+
catch (error) {
|
|
3348
|
+
if (error instanceof RunConflictError)
|
|
3349
|
+
this.handleConflicts(error, staged);
|
|
3350
|
+
throw error;
|
|
3351
|
+
}
|
|
3352
|
+
for (const [family, generation] of commit.claims) {
|
|
3353
|
+
this.claimed.set(family, generation);
|
|
3354
|
+
this.pendingClaims.delete(family);
|
|
3355
|
+
}
|
|
3356
|
+
for (const row of upserts) {
|
|
3357
|
+
this.base.set(row.id, { revision: commit.revisions.get(row.id), seq: commit.seqs.get(row.id), data: row.data, extras: extras.get(row.id) });
|
|
3358
|
+
}
|
|
3359
|
+
for (const id of deletes) {
|
|
3360
|
+
this.liveItems.forget(id);
|
|
3361
|
+
this.base.delete(id);
|
|
3362
|
+
this.deletedFamilies.delete(id);
|
|
3363
|
+
this.removeOwedHistory(id);
|
|
3364
|
+
}
|
|
3365
|
+
this.dirty.clear();
|
|
3366
|
+
this.deleted.clear();
|
|
3367
|
+
if (this.saveTimer) {
|
|
3368
|
+
clearTimeout(this.saveTimer);
|
|
3369
|
+
this.saveTimer = null;
|
|
3370
|
+
}
|
|
3371
|
+
}
|
|
3372
|
+
/** What a write of these rows (id → family) must still find: this store's claims, at the
|
|
3373
|
+
* generations it took them, and each row at the revision it last saw (none for a new row). */
|
|
3374
|
+
fence(families) {
|
|
3375
|
+
const claims = new Map();
|
|
3376
|
+
const rows = new Map();
|
|
3377
|
+
for (const [id, family] of families) {
|
|
3378
|
+
const pending = this.pendingClaims.get(family);
|
|
3379
|
+
// Generations start at 1, so an unclaimed family's 0 fails the fence as claim-lost.
|
|
3380
|
+
claims.set(family, pending !== undefined ? { take: pending } : { generation: this.claimed.get(family) ?? 0 });
|
|
3381
|
+
rows.set(id, { revision: (this.base.get(id) ?? this.coldBase.get(id))?.revision ?? null, family });
|
|
3382
|
+
}
|
|
3383
|
+
return { owner: this.owner, claims, rows };
|
|
3384
|
+
}
|
|
3385
|
+
/**
|
|
3386
|
+
* Handle a fenced-write conflict. Save unexpected conflict evidence before clearing pending
|
|
3387
|
+
* state or quarantining the row. If evidence persistence fails, retain pending writes. Other
|
|
3388
|
+
* rows remain writable; stale unclaimed reads only fail their write.
|
|
3389
|
+
*/
|
|
3390
|
+
handleConflicts(error, staged) {
|
|
3391
|
+
const unexpected = error.conflicts.filter((conflict) => this.held.has(conflict.id) || this.deleted.has(conflict.id) || this.coldBase.get(conflict.id)?.owned === true);
|
|
3392
|
+
if (unexpected.length === 0 || !this.db || !this.owner)
|
|
3393
|
+
return;
|
|
3394
|
+
const evidence = unexpected.map((conflict) => {
|
|
3395
|
+
const seen = this.base.get(conflict.id) ?? this.coldBase.get(conflict.id);
|
|
3396
|
+
const local = staged.has(conflict.id) ? staged.get(conflict.id) : this.deleted.has(conflict.id) ? null : this.held.get(conflict.id) ?? null;
|
|
3397
|
+
return {
|
|
3398
|
+
runId: conflict.id, reason: conflict.reason,
|
|
3399
|
+
baseRevision: seen?.revision ?? null, baseData: seen?.data ?? null,
|
|
3400
|
+
localData: local === null ? null : JSON.stringify(local), localDeleted: local === null,
|
|
3401
|
+
currentRevision: conflict.current?.revision ?? null, currentData: conflict.current?.data ?? null,
|
|
3402
|
+
};
|
|
3403
|
+
});
|
|
3404
|
+
let seqs;
|
|
3405
|
+
try {
|
|
3406
|
+
seqs = this.db.recordConflicts(this.owner.session, evidence);
|
|
3407
|
+
}
|
|
3408
|
+
catch (evidenceError) {
|
|
3409
|
+
const message = evidenceError instanceof Error ? evidenceError.message : String(evidenceError);
|
|
3410
|
+
console.error(`${logPrefix()} ${RUNS_DB_FILE}: could not record the conflicting write on ${unexpected.map((c) => c.id).join(', ')}; it stays pending: ${message}`);
|
|
3411
|
+
return;
|
|
3412
|
+
}
|
|
3413
|
+
unexpected.forEach((conflict, index) => {
|
|
3414
|
+
const family = this.familyOf(conflict.id);
|
|
3415
|
+
this.liveItems.forget(conflict.id);
|
|
3416
|
+
this.quarantined.add(conflict.id);
|
|
3417
|
+
this.coldReads.forget(conflict.id);
|
|
3418
|
+
this.dirty.delete(conflict.id);
|
|
3419
|
+
this.deleted.delete(conflict.id);
|
|
3420
|
+
this.deletedFamilies.delete(conflict.id);
|
|
3421
|
+
// The delete did not happen, so the history stays with the row.
|
|
3422
|
+
this.historyOwed.delete(conflict.id);
|
|
3423
|
+
if (conflict.reason === 'claim-lost') {
|
|
3424
|
+
this.claimed.delete(family);
|
|
3425
|
+
this.pendingClaims.delete(family);
|
|
3426
|
+
}
|
|
3427
|
+
const decoded = conflict.current ? decodeRunRow(conflict.current.data) : undefined;
|
|
3428
|
+
const current = decoded?.run;
|
|
3429
|
+
if (this.isFallbackRun(conflict.id))
|
|
3430
|
+
this.held.delete(conflict.id);
|
|
3431
|
+
const held = this.held.get(conflict.id);
|
|
3432
|
+
if (current && conflict.current) {
|
|
3433
|
+
// The other writer's record as it stands, normalized only as a live owner's run would be.
|
|
3434
|
+
reconcileLoadedRun(current, { keepLive: true });
|
|
3435
|
+
rescopeRun(current, this.repoHandle);
|
|
3436
|
+
if (held)
|
|
3437
|
+
replaceRecord(held, current);
|
|
3438
|
+
this.base.set(conflict.id, { revision: conflict.current.revision, seq: conflict.current.seq, data: conflict.current.data, extras: decoded.extras });
|
|
3439
|
+
this.emit('run', held ?? current);
|
|
3440
|
+
}
|
|
3441
|
+
else {
|
|
3442
|
+
this.held.delete(conflict.id);
|
|
3443
|
+
this.base.delete(conflict.id);
|
|
3444
|
+
this.emit('deleted', conflict.id);
|
|
3445
|
+
}
|
|
3446
|
+
console.error(`${logPrefix()} run ${conflict.id} was changed by another writer (${conflict.reason}); this process will not write it again until ${productName('cezar')} restarts. Evidence: ${join(this.dataDir, RUNS_DB_FILE)}, table run_conflicts, seq ${seqs[index]}.`);
|
|
3447
|
+
});
|
|
3448
|
+
this.scheduleSave();
|
|
3449
|
+
}
|
|
3450
|
+
/** Write what is pending, then (unless closing) let every run whose write settled leave memory. */
|
|
3451
|
+
saveNow(evict = true) {
|
|
3452
|
+
if (!this.db || projectWriteRefused(this.dataDir))
|
|
3453
|
+
return;
|
|
3454
|
+
// A second attempt only after a conflict excluded its rows: the unrelated rest goes out at once.
|
|
3455
|
+
for (let attempt = 0; attempt < 2 && (this.dirty.size > 0 || this.deleted.size > 0); attempt++) {
|
|
3456
|
+
const quarantined = this.quarantined.size;
|
|
3457
|
+
try {
|
|
3458
|
+
this.persist(new Map());
|
|
3459
|
+
}
|
|
3460
|
+
catch (err) {
|
|
3461
|
+
if (err instanceof RunConflictError && this.quarantined.size > quarantined)
|
|
3462
|
+
continue;
|
|
3463
|
+
// A pending save may outlive its directory; never recreate it or hide live-directory
|
|
3464
|
+
// failures. The rows stay pending (and held), so the next save or commit retries them.
|
|
3465
|
+
if (existsSync(this.dataDir)) {
|
|
3466
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3467
|
+
console.error(`${logPrefix()} failed to save ${RUNS_DB_FILE}: ${message}`);
|
|
3468
|
+
}
|
|
3469
|
+
break;
|
|
3470
|
+
}
|
|
3471
|
+
}
|
|
3472
|
+
if (evict)
|
|
3473
|
+
this.evictSettled();
|
|
3474
|
+
}
|
|
3475
|
+
/**
|
|
3476
|
+
* Write what is pending and release the database (store disposal, process shutdown).
|
|
3477
|
+
* Idempotent. A closed store keeps answering reads for what it holds — the final save evicts
|
|
3478
|
+
* nothing — but cannot read `runs.db` any more, so a run that had already left memory reads as
|
|
3479
|
+
* absent. It saves nothing more, and a durable commit on it throws.
|
|
3480
|
+
*/
|
|
3481
|
+
close() {
|
|
3482
|
+
this.liveItems.clear();
|
|
3483
|
+
// Demand-bound feeds stop before storage disappears during project removal/shutdown.
|
|
3484
|
+
this.emit('closed');
|
|
3485
|
+
this.compressor.stop();
|
|
3486
|
+
this.fallbackFacts?.stop();
|
|
3487
|
+
this.facts.stop();
|
|
3488
|
+
this.facts.flush();
|
|
3489
|
+
if (this.saveTimer) {
|
|
3490
|
+
clearTimeout(this.saveTimer);
|
|
3491
|
+
this.saveTimer = null;
|
|
3492
|
+
}
|
|
3493
|
+
this.saveNow(false);
|
|
3494
|
+
if (this.db && this.owner) {
|
|
3495
|
+
// Every claim goes with the store. Should the release fail, the claims stay until this
|
|
3496
|
+
// process exits; from then on they are provably dead and the next opener takes them.
|
|
3497
|
+
try {
|
|
3498
|
+
this.db.releaseClaims(this.owner.session);
|
|
3499
|
+
}
|
|
3500
|
+
catch { /* see above */ }
|
|
3501
|
+
}
|
|
3502
|
+
this.claimed.clear();
|
|
3503
|
+
this.pendingClaims.clear();
|
|
3504
|
+
if (this.owner)
|
|
3505
|
+
closeClaimSession(this.owner.session);
|
|
3506
|
+
this.db?.close();
|
|
3507
|
+
this.db = null;
|
|
3508
|
+
}
|
|
3509
|
+
}
|
|
3510
|
+
function archiveDigest(value) {
|
|
3511
|
+
return createHash('sha256').update(JSON.stringify(value)).digest('hex');
|
|
3512
|
+
}
|
|
3513
|
+
function archiveFields(run, archived, now) {
|
|
3514
|
+
stageArchiveFields(run, archived, now);
|
|
3515
|
+
}
|
|
3516
|
+
//# sourceMappingURL=store.js.map
|