@wjarka/cezarion 0.10.0-pr30.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +838 -0
- package/dist/agent-config/account-identity.d.ts +44 -0
- package/dist/agent-config/account-identity.js +128 -0
- package/dist/agent-config/account-identity.js.map +1 -0
- package/dist/agent-config/catalog.d.ts +72 -0
- package/dist/agent-config/catalog.js +223 -0
- package/dist/agent-config/catalog.js.map +1 -0
- package/dist/agent-config/files.d.ts +46 -0
- package/dist/agent-config/files.js +119 -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 +12 -0
- package/dist/agent-config/model-settings/pi.js +18 -0
- package/dist/agent-config/model-settings/pi.js.map +1 -0
- package/dist/agent-config/model-settings/shared.d.ts +9 -0
- package/dist/agent-config/model-settings/shared.js +93 -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 +32 -0
- package/dist/agent-config/models.js.map +1 -0
- package/dist/agent-config/seed.d.ts +7 -0
- package/dist/agent-config/seed.js +90 -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 +71 -0
- package/dist/agent-config/service.js.map +1 -0
- package/dist/agent-config/validate.d.ts +21 -0
- package/dist/agent-config/validate.js +76 -0
- package/dist/agent-config/validate.js.map +1 -0
- package/dist/automations/coordinator.d.ts +26 -0
- package/dist/automations/coordinator.js +66 -0
- package/dist/automations/coordinator.js.map +1 -0
- package/dist/automations/github-poller.d.ts +74 -0
- package/dist/automations/github-poller.js +234 -0
- package/dist/automations/github-poller.js.map +1 -0
- package/dist/automations/scheduler.d.ts +42 -0
- package/dist/automations/scheduler.js +192 -0
- package/dist/automations/scheduler.js.map +1 -0
- package/dist/automations/store.d.ts +65 -0
- package/dist/automations/store.js +298 -0
- package/dist/automations/store.js.map +1 -0
- package/dist/automations/task-template.d.ts +18 -0
- package/dist/automations/task-template.js +95 -0
- package/dist/automations/task-template.js.map +1 -0
- package/dist/automations/types.d.ts +255 -0
- package/dist/automations/types.js +159 -0
- package/dist/automations/types.js.map +1 -0
- package/dist/config.d.ts +77 -0
- package/dist/config.js +242 -0
- package/dist/config.js.map +1 -0
- package/dist/contract/agent-config.d.ts +153 -0
- package/dist/contract/agent-profiles.d.ts +347 -0
- package/dist/contract/automations.d.ts +935 -0
- package/dist/contract/events.d.ts +104 -0
- package/dist/contract/github.d.ts +556 -0
- package/dist/contract/health.d.ts +102 -0
- package/dist/contract/index.d.ts +16 -0
- package/dist/contract/index.js +1895 -0
- package/dist/contract/projects.d.ts +210 -0
- package/dist/contract/repo.d.ts +234 -0
- package/dist/contract/runs.d.ts +1473 -0
- package/dist/contract/skills.d.ts +232 -0
- package/dist/contract/workflows.d.ts +218 -0
- package/dist/contract/workspace.d.ts +579 -0
- package/dist/core/agent-env.d.ts +31 -0
- package/dist/core/agent-env.js +371 -0
- package/dist/core/agent-env.js.map +1 -0
- package/dist/core/agent-model-policy.d.ts +10 -0
- package/dist/core/agent-model-policy.js +36 -0
- package/dist/core/agent-model-policy.js.map +1 -0
- package/dist/core/agent-profiles.d.ts +62 -0
- package/dist/core/agent-profiles.js +90 -0
- package/dist/core/agent-profiles.js.map +1 -0
- package/dist/core/agent-runner.d.ts +211 -0
- package/dist/core/agent-runner.js +68 -0
- package/dist/core/agent-runner.js.map +1 -0
- package/dist/core/ask.d.ts +105 -0
- package/dist/core/ask.js +187 -0
- package/dist/core/ask.js.map +1 -0
- package/dist/core/backend-detect.d.ts +16 -0
- package/dist/core/backend-detect.js +155 -0
- package/dist/core/backend-detect.js.map +1 -0
- package/dist/core/claude-cli-runner.d.ts +59 -0
- package/dist/core/claude-cli-runner.js +483 -0
- package/dist/core/claude-cli-runner.js.map +1 -0
- package/dist/core/claude-ui-mapper.d.ts +80 -0
- package/dist/core/claude-ui-mapper.js +598 -0
- package/dist/core/claude-ui-mapper.js.map +1 -0
- package/dist/core/codex-app-server-runner.d.ts +41 -0
- package/dist/core/codex-app-server-runner.js +584 -0
- package/dist/core/codex-app-server-runner.js.map +1 -0
- package/dist/core/codex-app-server-transport.d.ts +35 -0
- package/dist/core/codex-app-server-transport.js +150 -0
- package/dist/core/codex-app-server-transport.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-ui-mapper.d.ts +112 -0
- package/dist/core/codex-ui-mapper.js +766 -0
- package/dist/core/codex-ui-mapper.js.map +1 -0
- package/dist/core/model-identity.d.ts +125 -0
- package/dist/core/model-identity.js +132 -0
- package/dist/core/model-identity.js.map +1 -0
- package/dist/core/model-presets.d.ts +21 -0
- package/dist/core/model-presets.js +81 -0
- package/dist/core/model-presets.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/opencode-model-catalog.d.ts +32 -0
- package/dist/core/opencode-model-catalog.js +153 -0
- package/dist/core/opencode-model-catalog.js.map +1 -0
- package/dist/core/opencode-server-runner.d.ts +32 -0
- package/dist/core/opencode-server-runner.js +882 -0
- package/dist/core/opencode-server-runner.js.map +1 -0
- package/dist/core/opencode-ui-mapper.d.ts +137 -0
- package/dist/core/opencode-ui-mapper.js +800 -0
- package/dist/core/opencode-ui-mapper.js.map +1 -0
- package/dist/core/pi-model-catalog.d.ts +31 -0
- package/dist/core/pi-model-catalog.js +152 -0
- package/dist/core/pi-model-catalog.js.map +1 -0
- package/dist/core/pi-runner.d.ts +24 -0
- package/dist/core/pi-runner.js +403 -0
- package/dist/core/pi-runner.js.map +1 -0
- package/dist/core/pi-ui-mapper.d.ts +36 -0
- package/dist/core/pi-ui-mapper.js +304 -0
- package/dist/core/pi-ui-mapper.js.map +1 -0
- package/dist/core/process-usage.d.ts +66 -0
- package/dist/core/process-usage.js +193 -0
- package/dist/core/process-usage.js.map +1 -0
- package/dist/core/provider-auth.d.ts +154 -0
- package/dist/core/provider-auth.js +509 -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 +8 -0
- package/dist/core/runner-factory.js +25 -0
- package/dist/core/runner-factory.js.map +1 -0
- package/dist/core/runner-model-catalog.d.ts +29 -0
- package/dist/core/runner-model-catalog.js +78 -0
- package/dist/core/runner-model-catalog.js.map +1 -0
- package/dist/core/secret-redaction.d.ts +37 -0
- package/dist/core/secret-redaction.js +115 -0
- package/dist/core/secret-redaction.js.map +1 -0
- package/dist/core/shell-env.d.ts +28 -0
- package/dist/core/shell-env.js +56 -0
- package/dist/core/shell-env.js.map +1 -0
- package/dist/core/tool-display.d.ts +31 -0
- package/dist/core/tool-display.js +179 -0
- package/dist/core/tool-display.js.map +1 -0
- package/dist/core/ui-events.d.ts +335 -0
- package/dist/core/ui-events.js +25 -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 +209 -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/git-diff-base.d.ts +85 -0
- package/dist/git-diff-base.js +180 -0
- package/dist/git-diff-base.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.d.ts +161 -0
- package/dist/git-worktree.js +508 -0
- package/dist/git-worktree.js.map +1 -0
- package/dist/handoff.d.ts +62 -0
- package/dist/handoff.js +154 -0
- package/dist/handoff.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +680 -0
- package/dist/index.js.map +1 -0
- package/dist/install-as-command.d.ts +40 -0
- package/dist/install-as-command.js +52 -0
- package/dist/install-as-command.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 +140 -0
- package/dist/paths.js +192 -0
- package/dist/paths.js.map +1 -0
- package/dist/planner.d.ts +27 -0
- package/dist/planner.js +268 -0
- package/dist/planner.js.map +1 -0
- package/dist/release/manifests.d.ts +81 -0
- package/dist/release/manifests.js +74 -0
- package/dist/release/manifests.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/runs/agent-tmpdir.d.ts +41 -0
- package/dist/runs/agent-tmpdir.js +183 -0
- package/dist/runs/agent-tmpdir.js.map +1 -0
- package/dist/runs/auto-name.d.ts +75 -0
- package/dist/runs/auto-name.js +179 -0
- package/dist/runs/auto-name.js.map +1 -0
- package/dist/runs/event-history.d.ts +50 -0
- package/dist/runs/event-history.js +613 -0
- package/dist/runs/event-history.js.map +1 -0
- package/dist/runs/retention.d.ts +60 -0
- package/dist/runs/retention.js +86 -0
- package/dist/runs/retention.js.map +1 -0
- package/dist/runs/review-gate.d.ts +18 -0
- package/dist/runs/review-gate.js +21 -0
- package/dist/runs/review-gate.js.map +1 -0
- package/dist/runs/run-index.d.ts +19 -0
- package/dist/runs/run-index.js +43 -0
- package/dist/runs/run-index.js.map +1 -0
- package/dist/runs/store.d.ts +446 -0
- package/dist/runs/store.js +1118 -0
- package/dist/runs/store.js.map +1 -0
- package/dist/runs/task-markers.d.ts +28 -0
- package/dist/runs/task-markers.js +74 -0
- package/dist/runs/task-markers.js.map +1 -0
- package/dist/runs/task-refs.d.ts +25 -0
- package/dist/runs/task-refs.js +74 -0
- package/dist/runs/task-refs.js.map +1 -0
- package/dist/runs/ui-event-sink.d.ts +81 -0
- package/dist/runs/ui-event-sink.js +210 -0
- package/dist/runs/ui-event-sink.js.map +1 -0
- package/dist/server/app-type.d.ts +21 -0
- package/dist/server/app-type.js +2 -0
- package/dist/server/app-type.js.map +1 -0
- package/dist/server/capabilities.d.ts +70 -0
- package/dist/server/capabilities.js +152 -0
- package/dist/server/capabilities.js.map +1 -0
- package/dist/server/checkout.d.ts +128 -0
- package/dist/server/checkout.js +296 -0
- package/dist/server/checkout.js.map +1 -0
- package/dist/server/forge/github.d.ts +513 -0
- package/dist/server/forge/github.js +2364 -0
- package/dist/server/forge/github.js.map +1 -0
- package/dist/server/forge/index.d.ts +40 -0
- package/dist/server/forge/index.js +72 -0
- package/dist/server/forge/index.js.map +1 -0
- package/dist/server/forge/types.d.ts +242 -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 +105 -0
- package/dist/server/fs-browse.js +182 -0
- package/dist/server/fs-browse.js.map +1 -0
- package/dist/server/git-changes.d.ts +213 -0
- package/dist/server/git-changes.js +555 -0
- package/dist/server/git-changes.js.map +1 -0
- package/dist/server/git.d.ts +28 -0
- package/dist/server/git.js +112 -0
- package/dist/server/git.js.map +1 -0
- package/dist/server/github.d.ts +9 -0
- package/dist/server/github.js +8 -0
- package/dist/server/github.js.map +1 -0
- package/dist/server/launch-key.d.ts +7 -0
- package/dist/server/launch-key.js +33 -0
- package/dist/server/launch-key.js.map +1 -0
- package/dist/server/open-in-app.d.ts +66 -0
- package/dist/server/open-in-app.js +211 -0
- package/dist/server/open-in-app.js.map +1 -0
- package/dist/server/open-in-terminal.d.ts +70 -0
- package/dist/server/open-in-terminal.js +179 -0
- package/dist/server/open-in-terminal.js.map +1 -0
- package/dist/server/pr.d.ts +7 -0
- package/dist/server/pr.js +7 -0
- package/dist/server/pr.js.map +1 -0
- package/dist/server/project-context.d.ts +120 -0
- package/dist/server/project-context.js +182 -0
- package/dist/server/project-context.js.map +1 -0
- package/dist/server/provider-action-gate.d.ts +8 -0
- package/dist/server/provider-action-gate.js +56 -0
- package/dist/server/provider-action-gate.js.map +1 -0
- package/dist/server/provider-auth-runtime.d.ts +21 -0
- package/dist/server/provider-auth-runtime.js +66 -0
- package/dist/server/provider-auth-runtime.js.map +1 -0
- package/dist/server/server.d.ts +13138 -0
- package/dist/server/server.js +5263 -0
- package/dist/server/server.js.map +1 -0
- package/dist/server/static-ui.d.ts +49 -0
- package/dist/server/static-ui.js +92 -0
- package/dist/server/static-ui.js.map +1 -0
- package/dist/server/validators.d.ts +97 -0
- package/dist/server/validators.js +86 -0
- package/dist/server/validators.js.map +1 -0
- package/dist/server/ws.d.ts +85 -0
- package/dist/server/ws.js +218 -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-install/engine.d.ts +50 -0
- package/dist/server-install/engine.js +324 -0
- package/dist/server-install/engine.js.map +1 -0
- package/dist/server-install/platforms/macosx-ngrok.d.ts +6 -0
- package/dist/server-install/platforms/macosx-ngrok.js +345 -0
- package/dist/server-install/platforms/macosx-ngrok.js.map +1 -0
- package/dist/server-install/platforms/ubuntu-vps.d.ts +78 -0
- package/dist/server-install/platforms/ubuntu-vps.js +1016 -0
- package/dist/server-install/platforms/ubuntu-vps.js.map +1 -0
- package/dist/server-install/state.d.ts +52 -0
- package/dist/server-install/state.js +182 -0
- package/dist/server-install/state.js.map +1 -0
- package/dist/server-install/steps.d.ts +109 -0
- package/dist/server-install/steps.js +334 -0
- package/dist/server-install/steps.js.map +1 -0
- package/dist/server-install/strategies.d.ts +4 -0
- package/dist/server-install/strategies.js +20 -0
- package/dist/server-install/strategies.js.map +1 -0
- package/dist/server-install/types.d.ts +287 -0
- package/dist/server-install/types.js +121 -0
- package/dist/server-install/types.js.map +1 -0
- package/dist/server-install/ui.d.ts +42 -0
- package/dist/server-install/ui.js +130 -0
- package/dist/server-install/ui.js.map +1 -0
- package/dist/skills-banner.d.ts +22 -0
- package/dist/skills-banner.js +41 -0
- package/dist/skills-banner.js.map +1 -0
- package/dist/skills-remote.d.ts +84 -0
- package/dist/skills-remote.js +468 -0
- package/dist/skills-remote.js.map +1 -0
- package/dist/skills-update.d.ts +88 -0
- package/dist/skills-update.js +392 -0
- package/dist/skills-update.js.map +1 -0
- package/dist/skills.d.ts +82 -0
- package/dist/skills.js +251 -0
- package/dist/skills.js.map +1 -0
- package/dist/todos.d.ts +48 -0
- package/dist/todos.js +201 -0
- package/dist/todos.js.map +1 -0
- package/dist/ui-state.d.ts +26 -0
- package/dist/ui-state.js +38 -0
- package/dist/ui-state.js.map +1 -0
- package/dist/update-check.d.ts +12 -0
- package/dist/update-check.js +41 -0
- package/dist/update-check.js.map +1 -0
- package/dist/workflows/load.d.ts +15 -0
- package/dist/workflows/load.js +56 -0
- package/dist/workflows/load.js.map +1 -0
- package/dist/workflows/run.d.ts +758 -0
- package/dist/workflows/run.js +3389 -0
- package/dist/workflows/run.js.map +1 -0
- package/dist/workflows/types.d.ts +153 -0
- package/dist/workflows/types.js +212 -0
- package/dist/workflows/types.js.map +1 -0
- package/dist/workspace/agent-accounts.d.ts +153 -0
- package/dist/workspace/agent-accounts.js +304 -0
- package/dist/workspace/agent-accounts.js.map +1 -0
- package/dist/workspace/agent-profiles.d.ts +78 -0
- package/dist/workspace/agent-profiles.js +115 -0
- package/dist/workspace/agent-profiles.js.map +1 -0
- package/dist/workspace/config.d.ts +169 -0
- package/dist/workspace/config.js +379 -0
- package/dist/workspace/config.js.map +1 -0
- package/dist/workspace/migrations.d.ts +45 -0
- package/dist/workspace/migrations.js +147 -0
- package/dist/workspace/migrations.js.map +1 -0
- package/dist/workspace/projects-cli.d.ts +25 -0
- package/dist/workspace/projects-cli.js +172 -0
- package/dist/workspace/projects-cli.js.map +1 -0
- package/dist/workspace/projects.d.ts +109 -0
- package/dist/workspace/projects.js +250 -0
- package/dist/workspace/projects.js.map +1 -0
- package/dist/workspace/semaphore.d.ts +179 -0
- package/dist/workspace/semaphore.js +195 -0
- package/dist/workspace/semaphore.js.map +1 -0
- package/dist/workspace/ui-state.d.ts +34 -0
- package/dist/workspace/ui-state.js +53 -0
- package/dist/workspace/ui-state.js.map +1 -0
- package/package.json +70 -0
- package/scripts/check-pack.mjs +46 -0
- package/scripts/inline-contract.mjs +112 -0
- package/scripts/install-as-command.mjs +120 -0
- package/scripts/mock-claude.mjs +520 -0
- package/scripts/mock-pi-rpc.mjs +83 -0
- package/scripts/sync-readme.mjs +20 -0
- package/scripts/test-process-usage.mjs +47 -0
- package/web/dist/assets/alert-dialog-B4Ofsgv7.js +1 -0
- package/web/dist/assets/arrow-down-Dy8dup7j.js +1 -0
- package/web/dist/assets/arrow-left-BJaucbhn.js +1 -0
- package/web/dist/assets/bundle-mjs-BT31bpU6.js +1 -0
- package/web/dist/assets/centered-state-duPW0phQ.js +43 -0
- package/web/dist/assets/chevron-right-Be4A5TCS.js +1 -0
- package/web/dist/assets/chunk-BO2N2NFS-DE6qKn3r.js +131 -0
- package/web/dist/assets/collapsible-DdcNBdSk.js +1 -0
- package/web/dist/assets/commit-list-CgvN9vec.js +1 -0
- package/web/dist/assets/compare-variants-CQA2nd65.js +1 -0
- package/web/dist/assets/core-BCsw8oQw.js +12 -0
- package/web/dist/assets/css-BsVw1vtW.js +1 -0
- package/web/dist/assets/diff-B6Agp5Hl.js +3 -0
- package/web/dist/assets/diff-stat-BoXuGkgq.js +1 -0
- package/web/dist/assets/diff-view-CD8XI-fj.js +6 -0
- package/web/dist/assets/diff-woXpYk--.js +1 -0
- package/web/dist/assets/dropdown-menu-B3SYw-k5.js +1 -0
- package/web/dist/assets/editable-title-B-hjp5wl.js +1 -0
- package/web/dist/assets/ellipsis-vertical-BYwzw0xl.js +1 -0
- package/web/dist/assets/engine-javascript-FQvzRocB.js +141 -0
- package/web/dist/assets/file-BRj5eISx.js +1 -0
- package/web/dist/assets/folder-z4oT5981.js +1 -0
- package/web/dist/assets/git-toolbar-owSEkYld.js +1 -0
- package/web/dist/assets/github-Bz2VgRX3.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/highlighter-CdHqIwFr.js +3 -0
- package/web/dist/assets/html-BY76lXLH.js +1 -0
- package/web/dist/assets/image-preview-h3WFluuy.js +1 -0
- package/web/dist/assets/index-BbcQNT92.css +2 -0
- package/web/dist/assets/index-EtQQQ6Kj.js +7 -0
- package/web/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/web/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -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-BQXq3kEf.js +1 -0
- package/web/dist/assets/lib-BxQXEXDF.js +1 -0
- package/web/dist/assets/markdown-BYOwaDjH.js +1 -0
- package/web/dist/assets/markdown-DqBKYPiB.js +2 -0
- package/web/dist/assets/mermaid-GHXKKRXX-C4jxqUUb.js +1 -0
- package/web/dist/assets/new-task-form-BdJKRBJX.js +1 -0
- package/web/dist/assets/pill-BQfmfpYp.js +1 -0
- package/web/dist/assets/project-router-DxgFZY1l.js +1 -0
- package/web/dist/assets/prompt-templates-B6m16gq-.js +15 -0
- package/web/dist/assets/python-gzcpVVnB.js +1 -0
- package/web/dist/assets/react-runtime-CCIEwYL0.js +9 -0
- package/web/dist/assets/refresh-cw-mhSpxf2a.js +1 -0
- package/web/dist/assets/repo-git-CUNRxR3_.js +1 -0
- package/web/dist/assets/rolldown-runtime-QTnfLwEv.js +1 -0
- package/web/dist/assets/run-diff-DsmmDVfx.js +3 -0
- package/web/dist/assets/run-header-BkhrseLJ.js +1 -0
- package/web/dist/assets/rust-Cfkwpbl8.js +1 -0
- package/web/dist/assets/search-x-BqDMB2g8.js +1 -0
- package/web/dist/assets/shellscript-CLZ0U2zV.js +1 -0
- package/web/dist/assets/skill-empty-hint-BWEvTggc.js +1 -0
- package/web/dist/assets/skills-9oR_oHse.js +1 -0
- package/web/dist/assets/skills-DwIxnnWO.js +1 -0
- package/web/dist/assets/sparkles-DIoTQpyx.js +1 -0
- package/web/dist/assets/sql-BsFa4tDR.js +1 -0
- package/web/dist/assets/square-terminal-Cc7CuQxc.js +1 -0
- package/web/dist/assets/tab-link-D1fQgpmO.js +1 -0
- package/web/dist/assets/task-changes-BKeLlDpK.js +1 -0
- package/web/dist/assets/task-commits-gcEFZIa5.js +1 -0
- package/web/dist/assets/task-files-BGAqoXTW.js +2 -0
- package/web/dist/assets/task-thread-DT8aUYTS.js +9 -0
- package/web/dist/assets/textarea-BOVbfy5C.js +1 -0
- package/web/dist/assets/thread-loading-CypW73KD.js +1 -0
- package/web/dist/assets/toml-CcmNWLt0.js +1 -0
- package/web/dist/assets/trash-2-CJq6LHWn.js +1 -0
- package/web/dist/assets/triangle-alert-CjDFvU27.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/upload-CPDZlJeK.js +1 -0
- package/web/dist/assets/use-desktop-5JsLmi0a.js +1 -0
- package/web/dist/assets/use-submit-shortcut-CHVFevty.js +1 -0
- package/web/dist/assets/utils-DiO6zyJd.js +64 -0
- package/web/dist/assets/workflows-rQDFzWRt.js +11 -0
- package/web/dist/assets/yaml-rwi0_p6S.js +1 -0
- package/web/dist/assets/zoomable-image-COWqb11v.js +1 -0
- package/web/dist/index.html +80 -0
- package/web/dist/open-mercato.svg +11 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { realpath, stat } from 'node:fs/promises';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { basename, join, resolve, sep } from 'node:path';
|
|
4
|
+
import { PROJECT_TAGS_MAX, PROJECT_TAG_MAX_LENGTH } from '../contract/index.js';
|
|
5
|
+
import { forgeKindOfRemote, forgeWebRoot } from '../server/forge/index.js';
|
|
6
|
+
import { getRepoInfo } from '../server/git.js';
|
|
7
|
+
import { mergeWriteWorkspaceConfig, loadWorkspaceConfig, } from './config.js';
|
|
8
|
+
/**
|
|
9
|
+
* Project registry operations over `~/.cezar/config.json` (spec
|
|
10
|
+
* 2026-07-20-multi-project-workspace, "Project identity" + "Boot flow"):
|
|
11
|
+
*
|
|
12
|
+
* - `registerProject(root)` — realpath-normalize, dedupe by realpath, allocate
|
|
13
|
+
* a human-readable slug from `basename(root)`. Registration is additive and
|
|
14
|
+
* goes through the read-modify-write merge, so the worst race outcome
|
|
15
|
+
* between concurrent `cezar serve` processes is a lost `lastOpenedAt` bump —
|
|
16
|
+
* never a lost project.
|
|
17
|
+
* - `listProjects()` — registry entries + a cheap per-root status/branch probe
|
|
18
|
+
* behind a short TTL cache, so a sidebar render never shells `git` N times.
|
|
19
|
+
* - `removeProject(id)` — unregister only. It never touches any file inside
|
|
20
|
+
* the repo (a project's own state stays in `<repo>/.ai/cezar/`).
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Slugs the allocator must never hand out: `default` is the reserved alias
|
|
24
|
+
* for the boot project, the rest are the cockpit shell's own top-level path
|
|
25
|
+
* segments. A repo named `default/` becomes `default-2` and can never shadow
|
|
26
|
+
* the alias or a route.
|
|
27
|
+
*/
|
|
28
|
+
export const RESERVED_PROJECT_IDS = new Set([
|
|
29
|
+
'default',
|
|
30
|
+
'new',
|
|
31
|
+
'settings',
|
|
32
|
+
'api',
|
|
33
|
+
'p',
|
|
34
|
+
'assets',
|
|
35
|
+
]);
|
|
36
|
+
/** Slug length cap — mirrors `PROJECT_ID_RE` (1 head char + up to 63 more). */
|
|
37
|
+
const SLUG_MAX = 64;
|
|
38
|
+
/**
|
|
39
|
+
* `basename(root)` → slug base: lowercase, runs of `[^a-z0-9-]` become one
|
|
40
|
+
* `-`, edge dashes trimmed so the result matches `^[a-z0-9][a-z0-9-]{0,63}$`.
|
|
41
|
+
* A degenerate basename (e.g. `日本語/`) falls back to `project` rather than
|
|
42
|
+
* ever escaping the slug shape.
|
|
43
|
+
*/
|
|
44
|
+
function slugBase(root) {
|
|
45
|
+
const base = basename(root)
|
|
46
|
+
.toLowerCase()
|
|
47
|
+
.replace(/[^a-z0-9-]+/g, '-')
|
|
48
|
+
.replace(/^-+|-+$/g, '')
|
|
49
|
+
.slice(0, SLUG_MAX);
|
|
50
|
+
return base || 'project';
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Allocate a unique slug for `root`: the slug base, deduplicated against
|
|
54
|
+
* `taken` ids AND the reserved ids with a numeric suffix (`api`, `api-2`,
|
|
55
|
+
* `api-3`, …). Suffixed candidates stay within the 64-char cap by truncating
|
|
56
|
+
* the base, never the suffix.
|
|
57
|
+
*/
|
|
58
|
+
export function allocateProjectSlug(root, taken) {
|
|
59
|
+
const used = new Set(RESERVED_PROJECT_IDS);
|
|
60
|
+
for (const id of taken)
|
|
61
|
+
used.add(id);
|
|
62
|
+
const base = slugBase(root);
|
|
63
|
+
if (!used.has(base))
|
|
64
|
+
return base;
|
|
65
|
+
for (let n = 2;; n++) {
|
|
66
|
+
const suffix = `-${n}`;
|
|
67
|
+
const candidate = `${base.slice(0, SLUG_MAX - suffix.length)}${suffix}`;
|
|
68
|
+
if (!used.has(candidate))
|
|
69
|
+
return candidate;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Realpath-normalize a root: resolves symlinks and drops trailing slashes, so
|
|
74
|
+
* every spelling of the same directory dedupes to one registry entry. A path
|
|
75
|
+
* that cannot be realpath'd (doesn't exist yet, unreadable) degrades to plain
|
|
76
|
+
* `resolve()` — registration callers guard existence; this module never throws
|
|
77
|
+
* over it.
|
|
78
|
+
*/
|
|
79
|
+
async function normalizeRoot(root) {
|
|
80
|
+
try {
|
|
81
|
+
return await realpath(root);
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return resolve(root);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/** True when `path` sits inside a cezar task worktree (`…/.ai/cezar/worktrees/…`). */
|
|
88
|
+
function isInsideTaskWorktree(path) {
|
|
89
|
+
const marker = `${sep}.ai${sep}cezar${sep}worktrees${sep}`;
|
|
90
|
+
return `${path}${sep}`.includes(marker);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Registration guard (spec 2026-07-20-multi-project-workspace, "Boot flow"):
|
|
94
|
+
* auto-registration is suppressed — the process still serves the folder
|
|
95
|
+
* normally, it just doesn't pollute the registry — when the resolved
|
|
96
|
+
* `repoRoot` is:
|
|
97
|
+
*
|
|
98
|
+
* - inside any `…/.ai/cezar/worktrees/…` path (task worktrees and nested
|
|
99
|
+
* `cez` invocations — the same nesting reality the `CEZ_TODOS_FILE=''`
|
|
100
|
+
* guard in `workflows/run.ts` acknowledges), checked on both the raw and
|
|
101
|
+
* realpath'd spelling so neither a symlinked prefix nor a literal one
|
|
102
|
+
* slips through; or
|
|
103
|
+
* - the user's home directory itself (realpath-compared, so a symlinked
|
|
104
|
+
* `$HOME` still matches).
|
|
105
|
+
*/
|
|
106
|
+
export async function shouldRegisterProject(repoRoot) {
|
|
107
|
+
const real = await normalizeRoot(repoRoot);
|
|
108
|
+
if (isInsideTaskWorktree(real) || isInsideTaskWorktree(resolve(repoRoot)))
|
|
109
|
+
return false;
|
|
110
|
+
const home = await normalizeRoot(homedir());
|
|
111
|
+
return real !== home;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Register `root` in the workspace registry (idempotent). Known root (by
|
|
115
|
+
* realpath) → bump its `lastOpenedAt` and return the existing entry, id and
|
|
116
|
+
* all. Unknown → allocate a slug and append a new entry via merge-write.
|
|
117
|
+
*/
|
|
118
|
+
export async function registerProject(root, source = 'local') {
|
|
119
|
+
const real = await normalizeRoot(root);
|
|
120
|
+
const now = new Date().toISOString();
|
|
121
|
+
let entry;
|
|
122
|
+
await mergeWriteWorkspaceConfig((config) => {
|
|
123
|
+
const existing = config.projects.find((p) => p.root === real);
|
|
124
|
+
if (existing) {
|
|
125
|
+
existing.lastOpenedAt = now;
|
|
126
|
+
entry = existing;
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
entry = {
|
|
130
|
+
id: allocateProjectSlug(real, config.projects.map((p) => p.id)),
|
|
131
|
+
root: real,
|
|
132
|
+
name: basename(real),
|
|
133
|
+
addedAt: now,
|
|
134
|
+
lastOpenedAt: now,
|
|
135
|
+
source,
|
|
136
|
+
};
|
|
137
|
+
config.projects.push(entry);
|
|
138
|
+
});
|
|
139
|
+
// The mutator always runs, so `entry` is always set — this satisfies TS.
|
|
140
|
+
if (!entry)
|
|
141
|
+
throw new Error('registerProject: merge-write did not run the mutator');
|
|
142
|
+
return entry;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* The ONE spelling rule for project tags — applied on every write, never on read.
|
|
146
|
+
*
|
|
147
|
+
* Trimmed, empties dropped, over-long ones truncated, deduped CASE-INSENSITIVELY (the first
|
|
148
|
+
* spelling wins, so `Storefront` typed before `storefront` keeps its capital), capped at
|
|
149
|
+
* `PROJECT_TAGS_MAX`, and sorted so two projects tagged with the same set store and render the
|
|
150
|
+
* same list. Case-insensitive dedupe is what makes tags usable as a grouping key: `API` and `api`
|
|
151
|
+
* grouping into two columns of the same thing is the whole failure this prevents.
|
|
152
|
+
*
|
|
153
|
+
* Returns `undefined` — never `[]` — for an empty result, because the registry stores nothing for
|
|
154
|
+
* an untagged project and `delete entry.tags` is what the writers then do.
|
|
155
|
+
*/
|
|
156
|
+
export function normalizeProjectTags(tags) {
|
|
157
|
+
if (!tags)
|
|
158
|
+
return undefined;
|
|
159
|
+
const bySpelling = new Map();
|
|
160
|
+
for (const raw of tags) {
|
|
161
|
+
const tag = raw.trim().slice(0, PROJECT_TAG_MAX_LENGTH);
|
|
162
|
+
if (!tag)
|
|
163
|
+
continue;
|
|
164
|
+
const key = tag.toLowerCase();
|
|
165
|
+
if (!bySpelling.has(key))
|
|
166
|
+
bySpelling.set(key, tag);
|
|
167
|
+
if (bySpelling.size >= PROJECT_TAGS_MAX)
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
const normalized = [...bySpelling.values()].sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' }));
|
|
171
|
+
return normalized.length > 0 ? normalized : undefined;
|
|
172
|
+
}
|
|
173
|
+
/** Probe TTL — long enough to coalesce a burst of sidebar renders, short
|
|
174
|
+
* enough that a deleted repo shows as `missing` on the next real look. */
|
|
175
|
+
const PROBE_TTL_MS = 5_000;
|
|
176
|
+
const probeCache = new Map();
|
|
177
|
+
/** Test hook: drop cached probes so status changes are visible immediately. */
|
|
178
|
+
export function clearProjectProbeCache() {
|
|
179
|
+
probeCache.clear();
|
|
180
|
+
}
|
|
181
|
+
async function computeProbe(root) {
|
|
182
|
+
try {
|
|
183
|
+
if (!(await stat(root)).isDirectory())
|
|
184
|
+
return { status: 'missing' };
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
return { status: 'missing' };
|
|
188
|
+
}
|
|
189
|
+
try {
|
|
190
|
+
// `.git` may be a dir (normal clone) or a file (worktree/submodule) —
|
|
191
|
+
// a bare stat covers both without shelling out.
|
|
192
|
+
await stat(join(root, '.git'));
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
return { status: 'not-git' };
|
|
196
|
+
}
|
|
197
|
+
// Branch and forge are best-effort garnish: getRepoInfo never throws (null
|
|
198
|
+
// on e.g. an unborn HEAD), and a repo without either is still status ok.
|
|
199
|
+
const info = await getRepoInfo(root);
|
|
200
|
+
const forge = forgeKindOfRemote(info?.remote);
|
|
201
|
+
// Free: `getRepoInfo` already ran for the branch, and the remote is already parsed for `forge`.
|
|
202
|
+
const repoUrl = forgeWebRoot(info?.remote);
|
|
203
|
+
return {
|
|
204
|
+
status: 'ok',
|
|
205
|
+
...(info?.branch ? { branch: info.branch } : {}),
|
|
206
|
+
...(forge ? { forge } : {}),
|
|
207
|
+
...(repoUrl ? { repoUrl } : {}),
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
async function probeRoot(root) {
|
|
211
|
+
const cached = probeCache.get(root);
|
|
212
|
+
if (cached && Date.now() - cached.at < PROBE_TTL_MS)
|
|
213
|
+
return cached.probe;
|
|
214
|
+
const probe = await computeProbe(root);
|
|
215
|
+
probeCache.set(root, { at: Date.now(), probe });
|
|
216
|
+
return probe;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* One root's `status` (+`branch`) through the same TTL cache `listProjects`
|
|
220
|
+
* uses. Exported for `POST /api/projects` (step 4.2): the register route
|
|
221
|
+
* answers with the freshly registered entry and must hand the cockpit the
|
|
222
|
+
* SAME shape the list route does — one project, one shape, whichever route
|
|
223
|
+
* produced it.
|
|
224
|
+
*/
|
|
225
|
+
export async function probeProjectStatus(root) {
|
|
226
|
+
return probeRoot(root);
|
|
227
|
+
}
|
|
228
|
+
export async function listProjects(selector) {
|
|
229
|
+
const config = await loadWorkspaceConfig();
|
|
230
|
+
const projects = selector
|
|
231
|
+
? config.projects.filter((project) => project.id === selector.projectId)
|
|
232
|
+
: config.projects;
|
|
233
|
+
return Promise.all(projects.map(async (project) => ({ ...project, ...(await probeRoot(project.root)) })));
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Remove `id` from the registry. Returns false when no such entry exists.
|
|
237
|
+
* Pure unregistration: nothing inside the repo (worktrees, `.ai/cezar/`,
|
|
238
|
+
* run history) is touched — re-registering the same root later gets a fresh
|
|
239
|
+
* slug but finds all its state intact.
|
|
240
|
+
*/
|
|
241
|
+
export async function removeProject(id) {
|
|
242
|
+
let removed = false;
|
|
243
|
+
await mergeWriteWorkspaceConfig((config) => {
|
|
244
|
+
const next = config.projects.filter((p) => p.id !== id);
|
|
245
|
+
removed = next.length !== config.projects.length;
|
|
246
|
+
config.projects = next;
|
|
247
|
+
});
|
|
248
|
+
return removed;
|
|
249
|
+
}
|
|
250
|
+
//# sourceMappingURL=projects.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projects.js","sourceRoot":"","sources":["../../src/workspace/projects.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AACxF,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAkB,MAAM,0BAA0B,CAAC;AAC3F,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EACL,yBAAyB,EACzB,mBAAmB,GAEpB,MAAM,aAAa,CAAC;AAErB;;;;;;;;;;;;;GAaG;AAEH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAwB,IAAI,GAAG,CAAC;IAC/D,SAAS;IACT,KAAK;IACL,UAAU;IACV,KAAK;IACL,GAAG;IACH,QAAQ;CACT,CAAC,CAAC;AAEH,+EAA+E;AAC/E,MAAM,QAAQ,GAAG,EAAE,CAAC;AAEpB;;;;;GAKG;AACH,SAAS,QAAQ,CAAC,IAAY;IAC5B,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC;SACxB,WAAW,EAAE;SACb,OAAO,CAAC,cAAc,EAAE,GAAG,CAAC;SAC5B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC;SACvB,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;IACtB,OAAO,IAAI,IAAI,SAAS,CAAC;AAC3B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY,EAAE,KAAuB;IACvE,MAAM,IAAI,GAAG,IAAI,GAAG,CAAS,oBAAoB,CAAC,CAAC;IACnD,KAAK,MAAM,EAAE,IAAI,KAAK;QAAE,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACrC,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC5B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACjC,KAAK,IAAI,CAAC,GAAG,CAAC,GAAI,CAAC,EAAE,EAAE,CAAC;QACtB,MAAM,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;QACvB,MAAM,SAAS,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CAAC;QACxE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC;YAAE,OAAO,SAAS,CAAC;IAC7C,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,KAAK,UAAU,aAAa,CAAC,IAAY;IACvC,IAAI,CAAC;QACH,OAAO,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,CAAC;AACH,CAAC;AAED,sFAAsF;AACtF,SAAS,oBAAoB,CAAC,IAAY;IACxC,MAAM,MAAM,GAAG,GAAG,GAAG,MAAM,GAAG,QAAQ,GAAG,YAAY,GAAG,EAAE,CAAC;IAC3D,OAAO,GAAG,IAAI,GAAG,GAAG,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AAC1C,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,QAAgB;IAC1D,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,QAAQ,CAAC,CAAC;IAC3C,IAAI,oBAAoB,CAAC,IAAI,CAAC,IAAI,oBAAoB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IACxF,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,OAAO,EAAE,CAAC,CAAC;IAC5C,OAAO,IAAI,KAAK,IAAI,CAAC;AACvB,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,IAAY,EACZ,MAAM,GAAyB,OAAO;IAEtC,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,IAAI,CAAC,CAAC;IACvC,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACrC,IAAI,KAAmC,CAAC;IACxC,MAAM,yBAAyB,CAAC,CAAC,MAAM,EAAE,EAAE;QACzC,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;QAC9D,IAAI,QAAQ,EAAE,CAAC;YACb,QAAQ,CAAC,YAAY,GAAG,GAAG,CAAC;YAC5B,KAAK,GAAG,QAAQ,CAAC;YACjB,OAAO;QACT,CAAC;QACD,KAAK,GAAG;YACN,EAAE,EAAE,mBAAmB,CAAC,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YAC/D,IAAI,EAAE,IAAI;YACV,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC;YACpB,OAAO,EAAE,GAAG;YACZ,YAAY,EAAE,GAAG;YACjB,MAAM;SACP,CAAC;QACF,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC,CAAC,CAAC;IACH,yEAAyE;IACzE,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC;IACpF,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAA0C;IAC7E,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAC;IAC5B,MAAM,UAAU,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC7C,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,GAAG,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,sBAAsB,CAAC,CAAC;QACxD,IAAI,CAAC,GAAG;YAAE,SAAS;QACnB,MAAM,GAAG,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;QAC9B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QACnD,IAAI,UAAU,CAAC,IAAI,IAAI,gBAAgB;YAAE,MAAM;IACjD,CAAC;IACD,MAAM,UAAU,GAAG,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACxD,CAAC,CAAC,aAAa,CAAC,CAAC,EAAE,SAAS,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC,CACvD,CAAC;IACF,OAAO,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC;AACxD,CAAC;AA4BD;2EAC2E;AAC3E,MAAM,YAAY,GAAG,KAAK,CAAC;AAC3B,MAAM,UAAU,GAAG,IAAI,GAAG,EAA4C,CAAC;AAEvE,+EAA+E;AAC/E,MAAM,UAAU,sBAAsB;IACpC,UAAU,CAAC,KAAK,EAAE,CAAC;AACrB,CAAC;AAED,KAAK,UAAU,YAAY,CAAC,IAAY;IACtC,IAAI,CAAC;QACH,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,EAAE;YAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IACtE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IAC/B,CAAC;IACD,IAAI,CAAC;QACH,sEAAsE;QACtE,gDAAgD;QAChD,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IACjC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IAC/B,CAAC;IACD,2EAA2E;IAC3E,yEAAyE;IACzE,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC,CAAC;IACrC,MAAM,KAAK,GAAG,iBAAiB,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC9C,gGAAgG;IAChG,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC3C,OAAO;QACL,MAAM,EAAE,IAAI;QACZ,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAChD,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3B,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAChC,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,SAAS,CAAC,IAAY;IACnC,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACpC,IAAI,MAAM,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,EAAE,GAAG,YAAY;QAAE,OAAO,MAAM,CAAC,KAAK,CAAC;IACzE,MAAM,KAAK,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,CAAC;IACvC,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;IAChD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,IAAY;IAEZ,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC;AACzB,CAAC;AAaD,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,QAA8B;IAC/D,MAAM,MAAM,GAAG,MAAM,mBAAmB,EAAE,CAAC;IAC3C,MAAM,QAAQ,GAAG,QAAQ;QACvB,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,KAAK,QAAQ,CAAC,SAAS,CAAC;QACxE,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC;IACpB,OAAO,OAAO,CAAC,GAAG,CAChB,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,OAAO,EAAE,GAAG,CAAC,MAAM,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CACtF,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,EAAU;IAC5C,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,MAAM,yBAAyB,CAAC,CAAC,MAAM,EAAE,EAAE;QACzC,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QACxD,OAAO,GAAG,IAAI,CAAC,MAAM,KAAK,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;QACjD,MAAM,CAAC,QAAQ,GAAG,IAAI,CAAC;IACzB,CAAC,CAAC,CAAC;IACH,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace-wide resource governance (spec 2026-07-20-multi-project-workspace,
|
|
3
|
+
* "Resource governance", step 2.5): `maxParallel` and `memoryLimitMb` protect
|
|
4
|
+
* the *host*, not a repo, so they live in `~/.cezar/config.json` `resources`
|
|
5
|
+
* and are enforced by ONE shared object across every `RunManager` — the boot
|
|
6
|
+
* path constructs a single `WorkspaceSemaphore` and threads it through
|
|
7
|
+
* `ProjectContexts` and the boot manager.
|
|
8
|
+
*
|
|
9
|
+
* Two jobs, deliberately fused because they cache the same file:
|
|
10
|
+
*
|
|
11
|
+
* 1. **Parallel cap** — `busy()` sums every registered manager's held slots;
|
|
12
|
+
* a manager's `pump()` starts queued runs only while `busy() <
|
|
13
|
+
* maxParallel()`. Slot accounting stays inside each manager (its
|
|
14
|
+
* `active + starting − waiting` count), which is what carries the #347
|
|
15
|
+
* exemption verbatim: a `waiting` run holds no slot, and a message into a
|
|
16
|
+
* waiting run resumes it immediately even when that momentarily exceeds
|
|
17
|
+
* the cap — a resume must never wait on other projects' runs.
|
|
18
|
+
* 2. **Cached resource config** — `maxParallel()`/`memoryLimitMb()` answer
|
|
19
|
+
* from an in-memory snapshot, NOT the file: the memory guard ticks ~every
|
|
20
|
+
* 2 s per manager, and N projects re-reading `~/.cezar/config.json` every
|
|
21
|
+
* tick is exactly what the spec forbids. `refresh()` is the single cache
|
|
22
|
+
* hook: boot calls it once, and `PUT /api/workspace/config` (step 2.7)
|
|
23
|
+
* calls it after a write — it re-reads the file and pumps every manager so
|
|
24
|
+
* a raised cap starts queued runs without a restart.
|
|
25
|
+
*
|
|
26
|
+
* Per-repo legacy `maxParallel`/`memoryLimitMb` keys are ignored by
|
|
27
|
+
* enforcement post-migration (`loadConfig` still parses them for old files;
|
|
28
|
+
* nothing consults them here).
|
|
29
|
+
*/
|
|
30
|
+
/** The cached `resources` slice run enforcement consults. */
|
|
31
|
+
export interface WorkspaceResourceLimits {
|
|
32
|
+
/** Workspace-wide cap on concurrently *running* agent runs. */
|
|
33
|
+
maxParallel: number;
|
|
34
|
+
/** Durable monitoring sessions that do not consume active-task capacity. */
|
|
35
|
+
maxMonitoringSessions?: number;
|
|
36
|
+
/** Automatic monitoring re-check cadence in minutes. Default ON at
|
|
37
|
+
* `DEFAULT_MONITORING_WAKE_MINUTES`; explicit `null` means stay parked; absent means
|
|
38
|
+
* "this loader predates the key" and reads as the default. */
|
|
39
|
+
monitoringWakeIntervalMinutes?: number | null;
|
|
40
|
+
/** Resume a run stopped by a provider usage limit when that limit resets. Default ON. */
|
|
41
|
+
autoResumeOnUsageLimit?: boolean;
|
|
42
|
+
/** Per-task process-tree memory ceiling in MiB; null = no limit. */
|
|
43
|
+
memoryLimitMb: number | null;
|
|
44
|
+
/**
|
|
45
|
+
* Per-project concurrency ceilings, keyed by realpath-normalized project
|
|
46
|
+
* root (the registry stores normalized `root`). A root absent from the map
|
|
47
|
+
* inherits the workspace `maxParallel`. Optional so older `load` stubs that
|
|
48
|
+
* only return the resource slice keep working — an absent map means "no
|
|
49
|
+
* project has an override", i.e. every project inherits.
|
|
50
|
+
*/
|
|
51
|
+
projectLimits?: ReadonlyMap<string, number>;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The two kinds of usage-limit hold an account can be under, kept apart because they bind
|
|
55
|
+
* different work (spec 2026-08-03-auto-resume-after-usage-limit):
|
|
56
|
+
*
|
|
57
|
+
* - `deadline` — a run is parked on a reset instant that has not arrived. The window is known to
|
|
58
|
+
* be shut, so this blocks EVERYTHING on that account, resumes included.
|
|
59
|
+
* - `inFlight` — a resume is running right now, re-testing the window. Nothing is proven yet, so
|
|
60
|
+
* this blocks fresh work but NOT other resumes: a resume blocked by a resume is the deadlock
|
|
61
|
+
* that stopped a live workspace dead.
|
|
62
|
+
*/
|
|
63
|
+
export interface AccountHolds {
|
|
64
|
+
deadline: ReadonlySet<string>;
|
|
65
|
+
inFlight: ReadonlySet<string>;
|
|
66
|
+
}
|
|
67
|
+
/** One manager's seam into the shared counter. */
|
|
68
|
+
export interface SemaphoreParticipant {
|
|
69
|
+
/** Slots this manager currently holds. The #347 exemption lives in the
|
|
70
|
+
* participant's own accounting: `waiting` runs are already subtracted. */
|
|
71
|
+
busySlots(): number;
|
|
72
|
+
/** Kick the manager's queue — capacity may have appeared. Awaited by
|
|
73
|
+
* `release()` so the manager taking a freed slot has registered it before
|
|
74
|
+
* the next participant evaluates capacity. */
|
|
75
|
+
pump(): void | Promise<void>;
|
|
76
|
+
/** Epoch ms of this manager's oldest queued run, or null when its queue is
|
|
77
|
+
* empty — `release()`'s ordering key, so a freed slot goes to the
|
|
78
|
+
* workspace's longest-waiting run instead of whichever manager happens to
|
|
79
|
+
* have registered first. */
|
|
80
|
+
oldestQueuedAt(): number | null;
|
|
81
|
+
/**
|
|
82
|
+
* Agent accounts this participant is holding, by KIND (spec
|
|
83
|
+
* 2026-08-03-auto-resume-after-usage-limit, `RunManager.accountHolds`).
|
|
84
|
+
*
|
|
85
|
+
* Workspace-scoped for the same reason the parallel cap is: a limit closes an ACCOUNT, and one
|
|
86
|
+
* account can be driving tasks in several projects at once. Optional so a stub participant —
|
|
87
|
+
* and any caller that predates the hold — keeps working; absent simply holds nothing.
|
|
88
|
+
*/
|
|
89
|
+
accountHolds?(): AccountHolds;
|
|
90
|
+
}
|
|
91
|
+
export interface WorkspaceSemaphoreOptions {
|
|
92
|
+
/** Snapshot source for `refresh()` — tests inject a stub; production keeps
|
|
93
|
+
* the `~/.cezar/config.json` reader. */
|
|
94
|
+
load?: () => Promise<WorkspaceResourceLimits>;
|
|
95
|
+
/** Starting cache, before any `refresh()` — defaults to the workspace
|
|
96
|
+
* schema's own defaults (`maxParallel: 2`, no memory limit), so a manager
|
|
97
|
+
* constructed without boot wiring behaves like a fresh workspace. */
|
|
98
|
+
initial?: Partial<WorkspaceResourceLimits>;
|
|
99
|
+
}
|
|
100
|
+
export declare class WorkspaceSemaphore {
|
|
101
|
+
private readonly participants;
|
|
102
|
+
private readonly load;
|
|
103
|
+
private limits;
|
|
104
|
+
/** A `release()` sweep is in flight — see `pendingRelease`. */
|
|
105
|
+
private broadcasting;
|
|
106
|
+
/** A slot freed DURING a sweep. The in-flight sweep may already have pumped
|
|
107
|
+
* the manager that should get it, so re-run rather than drop the wakeup. */
|
|
108
|
+
private pendingRelease;
|
|
109
|
+
constructor(options?: WorkspaceSemaphoreOptions);
|
|
110
|
+
/** Join the shared counter. Returns the unregister handle — the manager's
|
|
111
|
+
* `dispose()` must call it so a torn-down project stops counting. */
|
|
112
|
+
register(participant: SemaphoreParticipant): () => void;
|
|
113
|
+
/** Slots held across EVERY registered manager (waiting runs excluded by
|
|
114
|
+
* each participant — the #347 rule). */
|
|
115
|
+
busy(): number;
|
|
116
|
+
/** Cached workspace-wide parallel cap. */
|
|
117
|
+
maxParallel(): number;
|
|
118
|
+
maxMonitoringSessions(): number;
|
|
119
|
+
/** Cadence for automatic monitoring re-checks, or null when the operator chose "park
|
|
120
|
+
* until resumed". Deliberately NOT `?? DEFAULT`: `null` is a real user choice and
|
|
121
|
+
* `null ?? 5` would silently override it (#810). Only an ABSENT key — an older `load`
|
|
122
|
+
* stub, a partial `initial` — falls back to the shipped default. */
|
|
123
|
+
monitoringWakeIntervalMinutes(): number | null;
|
|
124
|
+
/** Whether a usage-limit stop schedules its own resume. Absent (an older `load` stub, a config
|
|
125
|
+
* written before the key existed) reads as ON — the shipped default. */
|
|
126
|
+
autoResumeOnUsageLimit(): boolean;
|
|
127
|
+
/** Cached per-task memory ceiling (MiB), or null for no limit. */
|
|
128
|
+
memoryLimitMb(): number | null;
|
|
129
|
+
/**
|
|
130
|
+
* Every agent account held across the WHOLE workspace, by kind — the union of what each manager
|
|
131
|
+
* reports (spec 2026-08-03-auto-resume-after-usage-limit). A `pump()` consults this before
|
|
132
|
+
* starting a queued run, so a limit hit in one project also stops the same account being walked
|
|
133
|
+
* into the wall from another.
|
|
134
|
+
*
|
|
135
|
+
* Asked live rather than cached: the underlying answer is derived from run records that change
|
|
136
|
+
* on every schedule, resume and cancel, and a stale snapshot here would either stall a queue
|
|
137
|
+
* whose window has reopened or leak a stampede through one that has not.
|
|
138
|
+
*/
|
|
139
|
+
accountHolds(): AccountHolds;
|
|
140
|
+
/**
|
|
141
|
+
* A slot came free somewhere in the workspace: pump EVERY manager,
|
|
142
|
+
* longest-waiting-queue first.
|
|
143
|
+
*
|
|
144
|
+
* This is the counterpart to `busy()` being workspace-wide. A `RunManager`
|
|
145
|
+
* only ever pumps itself, so before this existed a freed slot reached
|
|
146
|
+
* exactly one project's queue: a run queued in project B stayed `queued`
|
|
147
|
+
* while project A's runs came and went, until B happened to start or finish
|
|
148
|
+
* a run of its own (or someone saved the workspace config). Every
|
|
149
|
+
* slot-freeing transition — a run settling, a session parking at `waiting`
|
|
150
|
+
* — routes here instead.
|
|
151
|
+
*
|
|
152
|
+
* Pumps are awaited in turn so the manager that takes the slot has it
|
|
153
|
+
* counted (`starting`) before the next manager evaluates capacity — two
|
|
154
|
+
* managers pumping concurrently could both read the same free slot and
|
|
155
|
+
* overshoot `maxParallel`. Ordering is best-effort fairness, not a global
|
|
156
|
+
* FIFO gate: a manager whose head-of-queue can't start (non-git root,
|
|
157
|
+
* spec 006 degradation) must never block the rest of the workspace.
|
|
158
|
+
*/
|
|
159
|
+
release(): Promise<void>;
|
|
160
|
+
/**
|
|
161
|
+
* The effective per-project concurrency cap for a manager's repo root: the
|
|
162
|
+
* project's own `maxParallel` if set in the registry, else the workspace cap
|
|
163
|
+
* (`maxParallel()`). Answered from the cached snapshot — the class's
|
|
164
|
+
* no-per-tick-file-read invariant is preserved; the only syscall is a
|
|
165
|
+
* `realpathSync` to key the lookup the same way the registry normalizes
|
|
166
|
+
* `root` (once per `pump()`, alongside the existing `getRepoInfo` stat). A
|
|
167
|
+
* root with no registry entry (an ad-hoc run outside the registry) has no
|
|
168
|
+
* override and inherits the workspace cap.
|
|
169
|
+
*/
|
|
170
|
+
projectMaxParallel(repoRoot: string): number;
|
|
171
|
+
/**
|
|
172
|
+
* The workspace resource-cache hook: re-read the config and pump every
|
|
173
|
+
* registered manager, so a config change takes effect without a restart.
|
|
174
|
+
* Called at boot and by `PUT /api/workspace/config` (step 2.7). A failed
|
|
175
|
+
* read keeps the last good cache — enforcement never degrades to unlimited
|
|
176
|
+
* because the file was momentarily unreadable.
|
|
177
|
+
*/
|
|
178
|
+
refresh(): Promise<void>;
|
|
179
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { realpathSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { DEFAULT_MONITORING_WAKE_MINUTES, loadWorkspaceConfig } from './config.js';
|
|
4
|
+
/** Realpath-normalize a root the same way the registry does
|
|
5
|
+
* (`workspace/projects.ts` `normalizeRoot`), but synchronously — this answers
|
|
6
|
+
* a manager's hot-path lookup and must not `await`. A path that cannot be
|
|
7
|
+
* realpath'd degrades to `resolve()`, matching the registry's own fallback. */
|
|
8
|
+
function normalizeRootSync(root) {
|
|
9
|
+
try {
|
|
10
|
+
return realpathSync(root);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return resolve(root);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
const DEFAULT_LIMITS = {
|
|
17
|
+
maxParallel: 2,
|
|
18
|
+
maxMonitoringSessions: 2,
|
|
19
|
+
monitoringWakeIntervalMinutes: DEFAULT_MONITORING_WAKE_MINUTES,
|
|
20
|
+
autoResumeOnUsageLimit: true,
|
|
21
|
+
memoryLimitMb: null,
|
|
22
|
+
};
|
|
23
|
+
/** Production loader: the `resources` slice of `~/.cezar/config.json`
|
|
24
|
+
* (schema-defaulted, so a missing/corrupt file yields the zero-config
|
|
25
|
+
* 2 parallel / 2 monitoring / 5-minute wake / no memory cap),
|
|
26
|
+
* plus the per-project `maxParallel` overrides built into a root→limit map.
|
|
27
|
+
* The registry `root` is already realpath-normalized (`registerProject`), so
|
|
28
|
+
* the keys match `normalizeRootSync`'s output at lookup time. */
|
|
29
|
+
async function loadResourceLimits() {
|
|
30
|
+
const { resources, projects } = await loadWorkspaceConfig();
|
|
31
|
+
const projectLimits = new Map();
|
|
32
|
+
for (const project of projects) {
|
|
33
|
+
if (typeof project.maxParallel === 'number')
|
|
34
|
+
projectLimits.set(project.root, project.maxParallel);
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
maxParallel: resources.maxParallel,
|
|
38
|
+
maxMonitoringSessions: resources.maxMonitoringSessions,
|
|
39
|
+
monitoringWakeIntervalMinutes: resources.monitoringWakeIntervalMinutes,
|
|
40
|
+
autoResumeOnUsageLimit: resources.autoResumeOnUsageLimit,
|
|
41
|
+
memoryLimitMb: resources.memoryLimitMb,
|
|
42
|
+
projectLimits,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export class WorkspaceSemaphore {
|
|
46
|
+
participants = new Set();
|
|
47
|
+
load;
|
|
48
|
+
limits;
|
|
49
|
+
/** A `release()` sweep is in flight — see `pendingRelease`. */
|
|
50
|
+
broadcasting = false;
|
|
51
|
+
/** A slot freed DURING a sweep. The in-flight sweep may already have pumped
|
|
52
|
+
* the manager that should get it, so re-run rather than drop the wakeup. */
|
|
53
|
+
pendingRelease = false;
|
|
54
|
+
constructor(options = {}) {
|
|
55
|
+
this.load = options.load ?? loadResourceLimits;
|
|
56
|
+
this.limits = { ...DEFAULT_LIMITS, ...options.initial };
|
|
57
|
+
}
|
|
58
|
+
/** Join the shared counter. Returns the unregister handle — the manager's
|
|
59
|
+
* `dispose()` must call it so a torn-down project stops counting. */
|
|
60
|
+
register(participant) {
|
|
61
|
+
this.participants.add(participant);
|
|
62
|
+
return () => this.participants.delete(participant);
|
|
63
|
+
}
|
|
64
|
+
/** Slots held across EVERY registered manager (waiting runs excluded by
|
|
65
|
+
* each participant — the #347 rule). */
|
|
66
|
+
busy() {
|
|
67
|
+
let total = 0;
|
|
68
|
+
for (const participant of this.participants)
|
|
69
|
+
total += participant.busySlots();
|
|
70
|
+
return total;
|
|
71
|
+
}
|
|
72
|
+
/** Cached workspace-wide parallel cap. */
|
|
73
|
+
maxParallel() {
|
|
74
|
+
return this.limits.maxParallel;
|
|
75
|
+
}
|
|
76
|
+
maxMonitoringSessions() {
|
|
77
|
+
return this.limits.maxMonitoringSessions ?? 2;
|
|
78
|
+
}
|
|
79
|
+
/** Cadence for automatic monitoring re-checks, or null when the operator chose "park
|
|
80
|
+
* until resumed". Deliberately NOT `?? DEFAULT`: `null` is a real user choice and
|
|
81
|
+
* `null ?? 5` would silently override it (#810). Only an ABSENT key — an older `load`
|
|
82
|
+
* stub, a partial `initial` — falls back to the shipped default. */
|
|
83
|
+
monitoringWakeIntervalMinutes() {
|
|
84
|
+
const configured = this.limits.monitoringWakeIntervalMinutes;
|
|
85
|
+
return configured === undefined ? DEFAULT_MONITORING_WAKE_MINUTES : configured;
|
|
86
|
+
}
|
|
87
|
+
/** Whether a usage-limit stop schedules its own resume. Absent (an older `load` stub, a config
|
|
88
|
+
* written before the key existed) reads as ON — the shipped default. */
|
|
89
|
+
autoResumeOnUsageLimit() {
|
|
90
|
+
return this.limits.autoResumeOnUsageLimit ?? true;
|
|
91
|
+
}
|
|
92
|
+
/** Cached per-task memory ceiling (MiB), or null for no limit. */
|
|
93
|
+
memoryLimitMb() {
|
|
94
|
+
return this.limits.memoryLimitMb;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Every agent account held across the WHOLE workspace, by kind — the union of what each manager
|
|
98
|
+
* reports (spec 2026-08-03-auto-resume-after-usage-limit). A `pump()` consults this before
|
|
99
|
+
* starting a queued run, so a limit hit in one project also stops the same account being walked
|
|
100
|
+
* into the wall from another.
|
|
101
|
+
*
|
|
102
|
+
* Asked live rather than cached: the underlying answer is derived from run records that change
|
|
103
|
+
* on every schedule, resume and cancel, and a stale snapshot here would either stall a queue
|
|
104
|
+
* whose window has reopened or leak a stampede through one that has not.
|
|
105
|
+
*/
|
|
106
|
+
accountHolds() {
|
|
107
|
+
const deadline = new Set();
|
|
108
|
+
const inFlight = new Set();
|
|
109
|
+
for (const participant of this.participants) {
|
|
110
|
+
const holds = participant.accountHolds?.();
|
|
111
|
+
if (!holds)
|
|
112
|
+
continue;
|
|
113
|
+
for (const key of holds.deadline)
|
|
114
|
+
deadline.add(key);
|
|
115
|
+
for (const key of holds.inFlight)
|
|
116
|
+
inFlight.add(key);
|
|
117
|
+
}
|
|
118
|
+
return { deadline, inFlight };
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* A slot came free somewhere in the workspace: pump EVERY manager,
|
|
122
|
+
* longest-waiting-queue first.
|
|
123
|
+
*
|
|
124
|
+
* This is the counterpart to `busy()` being workspace-wide. A `RunManager`
|
|
125
|
+
* only ever pumps itself, so before this existed a freed slot reached
|
|
126
|
+
* exactly one project's queue: a run queued in project B stayed `queued`
|
|
127
|
+
* while project A's runs came and went, until B happened to start or finish
|
|
128
|
+
* a run of its own (or someone saved the workspace config). Every
|
|
129
|
+
* slot-freeing transition — a run settling, a session parking at `waiting`
|
|
130
|
+
* — routes here instead.
|
|
131
|
+
*
|
|
132
|
+
* Pumps are awaited in turn so the manager that takes the slot has it
|
|
133
|
+
* counted (`starting`) before the next manager evaluates capacity — two
|
|
134
|
+
* managers pumping concurrently could both read the same free slot and
|
|
135
|
+
* overshoot `maxParallel`. Ordering is best-effort fairness, not a global
|
|
136
|
+
* FIFO gate: a manager whose head-of-queue can't start (non-git root,
|
|
137
|
+
* spec 006 degradation) must never block the rest of the workspace.
|
|
138
|
+
*/
|
|
139
|
+
async release() {
|
|
140
|
+
if (this.broadcasting) {
|
|
141
|
+
this.pendingRelease = true;
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
this.broadcasting = true;
|
|
145
|
+
try {
|
|
146
|
+
do {
|
|
147
|
+
this.pendingRelease = false;
|
|
148
|
+
const ordered = [...this.participants]
|
|
149
|
+
.map((participant) => ({
|
|
150
|
+
participant,
|
|
151
|
+
// Empty queues sort last — they have nothing to claim the slot with.
|
|
152
|
+
since: participant.oldestQueuedAt() ?? Number.MAX_SAFE_INTEGER,
|
|
153
|
+
}))
|
|
154
|
+
.sort((a, b) => a.since - b.since);
|
|
155
|
+
for (const { participant } of ordered)
|
|
156
|
+
await participant.pump();
|
|
157
|
+
} while (this.pendingRelease);
|
|
158
|
+
}
|
|
159
|
+
finally {
|
|
160
|
+
this.broadcasting = false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* The effective per-project concurrency cap for a manager's repo root: the
|
|
165
|
+
* project's own `maxParallel` if set in the registry, else the workspace cap
|
|
166
|
+
* (`maxParallel()`). Answered from the cached snapshot — the class's
|
|
167
|
+
* no-per-tick-file-read invariant is preserved; the only syscall is a
|
|
168
|
+
* `realpathSync` to key the lookup the same way the registry normalizes
|
|
169
|
+
* `root` (once per `pump()`, alongside the existing `getRepoInfo` stat). A
|
|
170
|
+
* root with no registry entry (an ad-hoc run outside the registry) has no
|
|
171
|
+
* override and inherits the workspace cap.
|
|
172
|
+
*/
|
|
173
|
+
projectMaxParallel(repoRoot) {
|
|
174
|
+
const override = this.limits.projectLimits?.get(normalizeRootSync(repoRoot));
|
|
175
|
+
return override ?? this.maxParallel();
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* The workspace resource-cache hook: re-read the config and pump every
|
|
179
|
+
* registered manager, so a config change takes effect without a restart.
|
|
180
|
+
* Called at boot and by `PUT /api/workspace/config` (step 2.7). A failed
|
|
181
|
+
* read keeps the last good cache — enforcement never degrades to unlimited
|
|
182
|
+
* because the file was momentarily unreadable.
|
|
183
|
+
*/
|
|
184
|
+
async refresh() {
|
|
185
|
+
try {
|
|
186
|
+
this.limits = await this.load();
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
// keep the last good snapshot
|
|
190
|
+
}
|
|
191
|
+
// A raised cap is capacity appearing everywhere at once — same sweep.
|
|
192
|
+
await this.release();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=semaphore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semaphore.js","sourceRoot":"","sources":["../../src/workspace/semaphore.ts"],"names":[],"mappings":"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"}
|