@wjarka/cezarion 0.15.1 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +167 -46
- package/dist/agent-config/catalog.d.ts +6 -2
- package/dist/agent-config/catalog.js +78 -3
- package/dist/agent-config/catalog.js.map +1 -1
- package/dist/agent-config/model-settings/shared.js +6 -3
- package/dist/agent-config/model-settings/shared.js.map +1 -1
- package/dist/agent-config/models.js +4 -0
- package/dist/agent-config/models.js.map +1 -1
- package/dist/agent-config/validate.js +6 -0
- package/dist/agent-config/validate.js.map +1 -1
- package/dist/artifacts/cli.js +10 -2
- package/dist/artifacts/cli.js.map +1 -1
- package/dist/automations/coordinator.d.ts +7 -0
- package/dist/automations/coordinator.js +22 -2
- package/dist/automations/coordinator.js.map +1 -1
- package/dist/automations/github-poller.d.ts +4 -4
- package/dist/automations/github-poller.js.map +1 -1
- package/dist/automations/schedule-runner.d.ts +130 -0
- package/dist/automations/schedule-runner.js +326 -0
- package/dist/automations/schedule-runner.js.map +1 -0
- package/dist/automations/scheduler.d.ts +38 -7
- package/dist/automations/scheduler.js +90 -22
- package/dist/automations/scheduler.js.map +1 -1
- package/dist/automations/store.d.ts +80 -3
- package/dist/automations/store.js +208 -16
- package/dist/automations/store.js.map +1 -1
- package/dist/automations/task-template.d.ts +42 -6
- package/dist/automations/task-template.js +152 -24
- package/dist/automations/task-template.js.map +1 -1
- package/dist/automations/types.d.ts +142 -6
- package/dist/automations/types.js +64 -6
- package/dist/automations/types.js.map +1 -1
- package/dist/autosave-holders.js +8 -12
- package/dist/autosave-holders.js.map +1 -1
- package/dist/ci-wait/client.d.ts +8 -1
- package/dist/ci-wait/client.js +43 -1
- package/dist/ci-wait/client.js.map +1 -1
- package/dist/ci-wait/controller.d.ts +83 -4
- package/dist/ci-wait/controller.js +76 -11
- package/dist/ci-wait/controller.js.map +1 -1
- package/dist/ci-wait/errors.d.ts +7 -1
- package/dist/ci-wait/errors.js +36 -0
- package/dist/ci-wait/errors.js.map +1 -1
- package/dist/ci-wait/github.d.ts +2 -0
- package/dist/ci-wait/github.js +3 -1
- package/dist/ci-wait/github.js.map +1 -1
- package/dist/ci-wait/mcp.d.ts +2 -98
- package/dist/ci-wait/mcp.js +11 -25
- package/dist/ci-wait/mcp.js.map +1 -1
- package/dist/ci-wait/supervisor.d.ts +2 -1
- package/dist/ci-wait/supervisor.js +2 -1
- package/dist/ci-wait/supervisor.js.map +1 -1
- package/dist/ci-wait/tools.d.ts +307 -0
- package/dist/ci-wait/tools.js +63 -0
- package/dist/ci-wait/tools.js.map +1 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.js +7 -0
- package/dist/config.js.map +1 -1
- package/dist/contract/agent-config.d.ts +5 -0
- package/dist/contract/agent-profiles.d.ts +15 -0
- package/dist/contract/automation-schedule.d.ts +60 -0
- package/dist/contract/automations.d.ts +208 -36
- package/dist/contract/delegation.d.ts +96 -0
- package/dist/contract/discovery.d.ts +7 -0
- package/dist/contract/github.d.ts +1 -0
- package/dist/contract/health.d.ts +6 -0
- package/dist/contract/index.d.ts +3 -0
- package/dist/contract/index.js +1445 -745
- package/dist/contract/preview.d.ts +508 -0
- package/dist/contract/runs.d.ts +716 -15
- package/dist/contract/skills.d.ts +39 -0
- package/dist/contract/workflows.d.ts +6 -0
- package/dist/contract/workspace.d.ts +65 -0
- package/dist/contract/zoned-time.d.ts +35 -0
- package/dist/core/agent-env.d.ts +11 -0
- package/dist/core/agent-env.js +27 -5
- package/dist/core/agent-env.js.map +1 -1
- package/dist/core/agent-profiles.js +7 -0
- package/dist/core/agent-profiles.js.map +1 -1
- package/dist/core/agent-runner.d.ts +49 -1
- package/dist/core/agent-runner.js +2 -1
- package/dist/core/agent-runner.js.map +1 -1
- package/dist/core/backend-detect.d.ts +3 -2
- package/dist/core/backend-detect.js +27 -1
- package/dist/core/backend-detect.js.map +1 -1
- package/dist/core/claude-cli-runner.d.ts +1 -0
- package/dist/core/claude-cli-runner.js +69 -19
- package/dist/core/claude-cli-runner.js.map +1 -1
- package/dist/core/codex-app-server-runner.d.ts +1 -0
- package/dist/core/codex-app-server-runner.js +200 -25
- package/dist/core/codex-app-server-runner.js.map +1 -1
- package/dist/core/codex-app-server-transport.d.ts +7 -3
- package/dist/core/codex-app-server-transport.js +11 -9
- package/dist/core/codex-app-server-transport.js.map +1 -1
- package/dist/core/cursor-acp-runner.d.ts +1 -0
- package/dist/core/cursor-acp-runner.js +24 -9
- package/dist/core/cursor-acp-runner.js.map +1 -1
- package/dist/core/host-model-catalog.js +2 -0
- package/dist/core/host-model-catalog.js.map +1 -1
- package/dist/core/managed-session.js +2 -0
- package/dist/core/managed-session.js.map +1 -1
- package/dist/core/model-identity.js +2 -0
- package/dist/core/model-identity.js.map +1 -1
- package/dist/core/model-presets.js +3 -0
- package/dist/core/model-presets.js.map +1 -1
- package/dist/core/omp-model-catalog.d.ts +43 -0
- package/dist/core/omp-model-catalog.js +205 -0
- package/dist/core/omp-model-catalog.js.map +1 -0
- package/dist/core/omp-runner.d.ts +73 -0
- package/dist/core/omp-runner.js +990 -0
- package/dist/core/omp-runner.js.map +1 -0
- package/dist/core/omp-ui-mapper.d.ts +106 -0
- package/dist/core/omp-ui-mapper.js +882 -0
- package/dist/core/omp-ui-mapper.js.map +1 -0
- package/dist/core/opencode-server-runner.d.ts +18 -4
- package/dist/core/opencode-server-runner.js +337 -96
- package/dist/core/opencode-server-runner.js.map +1 -1
- package/dist/core/opencode-session-error.d.ts +11 -0
- package/dist/core/opencode-session-error.js +26 -0
- package/dist/core/opencode-session-error.js.map +1 -0
- package/dist/core/opencode-ui-mapper.js +4 -36
- package/dist/core/opencode-ui-mapper.js.map +1 -1
- package/dist/core/pi-runner.d.ts +1 -0
- package/dist/core/pi-runner.js +75 -12
- package/dist/core/pi-runner.js.map +1 -1
- package/dist/core/provider-auth.d.ts +9 -2
- package/dist/core/provider-auth.js +43 -6
- package/dist/core/provider-auth.js.map +1 -1
- package/dist/core/runner-factory.js +3 -0
- package/dist/core/runner-factory.js.map +1 -1
- package/dist/core/runner-model-catalog.js +1 -0
- package/dist/core/runner-model-catalog.js.map +1 -1
- 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/session-process.d.ts +22 -0
- package/dist/core/session-process.js +202 -0
- package/dist/core/session-process.js.map +1 -0
- package/dist/core/ui-events.d.ts +1 -1
- package/dist/core/usage-limit.js +3 -60
- package/dist/core/usage-limit.js.map +1 -1
- package/dist/data-gitignore.d.ts +6 -0
- package/dist/data-gitignore.js +57 -0
- package/dist/data-gitignore.js.map +1 -0
- package/dist/delegation/capacity.d.ts +19 -0
- package/dist/delegation/capacity.js +34 -0
- package/dist/delegation/capacity.js.map +1 -0
- package/dist/delegation/cli.js +13 -4
- package/dist/delegation/cli.js.map +1 -1
- package/dist/delegation/conversations.d.ts +4 -2
- package/dist/delegation/conversations.js +8 -12
- package/dist/delegation/conversations.js.map +1 -1
- package/dist/delegation/credentials.d.ts +2 -0
- package/dist/delegation/credentials.js +10 -0
- package/dist/delegation/credentials.js.map +1 -1
- 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/execution-identity.d.ts +6 -0
- package/dist/delegation/execution-identity.js +1 -1
- package/dist/delegation/execution-identity.js.map +1 -1
- package/dist/delegation/policy.d.ts +3 -2
- package/dist/delegation/policy.js +7 -5
- package/dist/delegation/policy.js.map +1 -1
- package/dist/delegation/process-liveness.d.ts +57 -20
- package/dist/delegation/process-liveness.js +114 -92
- package/dist/delegation/process-liveness.js.map +1 -1
- package/dist/delegation/provision.js +6 -6
- package/dist/delegation/provision.js.map +1 -1
- package/dist/delegation/results.js +4 -2
- package/dist/delegation/results.js.map +1 -1
- package/dist/delegation/retry-backoff.d.ts +23 -0
- package/dist/delegation/retry-backoff.js +18 -0
- package/dist/delegation/retry-backoff.js.map +1 -0
- package/dist/delegation/routes.d.ts +64 -58
- package/dist/delegation/scratch-cleanup.d.ts +29 -0
- package/dist/delegation/scratch-cleanup.js +143 -0
- package/dist/delegation/scratch-cleanup.js.map +1 -0
- package/dist/delegation/service.d.ts +20 -1
- package/dist/delegation/service.js +233 -70
- package/dist/delegation/service.js.map +1 -1
- package/dist/delegation/transport.d.ts +12 -6
- package/dist/delegation/workspace.d.ts +23 -4
- package/dist/delegation/workspace.js +147 -31
- package/dist/delegation/workspace.js.map +1 -1
- package/dist/git-worktree-lock-helper.js +8 -3
- package/dist/git-worktree-lock-helper.js.map +1 -1
- package/dist/git-worktree-lock.d.ts +6 -1
- package/dist/git-worktree-lock.js +10 -4
- package/dist/git-worktree-lock.js.map +1 -1
- 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 +8 -2
- package/dist/git-worktree.js +19 -2
- package/dist/git-worktree.js.map +1 -1
- package/dist/handoff.d.ts +2 -2
- package/dist/handoff.js +1 -1
- package/dist/index.js +109 -76
- package/dist/index.js.map +1 -1
- package/dist/paths.d.ts +1 -1
- package/dist/paths.js +5 -1
- package/dist/paths.js.map +1 -1
- package/dist/preview/cdp.d.ts +13 -0
- package/dist/preview/cdp.js +69 -0
- package/dist/preview/cdp.js.map +1 -0
- package/dist/preview/chromium.d.ts +75 -0
- package/dist/preview/chromium.js +394 -0
- package/dist/preview/chromium.js.map +1 -0
- package/dist/preview/dev-server.d.ts +68 -0
- package/dist/preview/dev-server.js +341 -0
- package/dist/preview/dev-server.js.map +1 -0
- package/dist/preview/host.d.ts +154 -0
- package/dist/preview/host.js +681 -0
- package/dist/preview/host.js.map +1 -0
- package/dist/preview/registration.d.ts +63 -0
- package/dist/preview/registration.js +109 -0
- package/dist/preview/registration.js.map +1 -0
- package/dist/preview/session.d.ts +73 -0
- package/dist/preview/session.js +347 -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/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 +12 -1
- package/dist/runs/agent-tmpdir.js +123 -34
- package/dist/runs/agent-tmpdir.js.map +1 -1
- package/dist/runs/delegation-state.d.ts +5 -1
- package/dist/runs/delegation-state.js +9 -0
- package/dist/runs/delegation-state.js.map +1 -1
- package/dist/runs/event-history.d.ts +4 -4
- package/dist/runs/event-history.js +97 -117
- package/dist/runs/event-history.js.map +1 -1
- 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 +463 -0
- package/dist/runs/history-file.js.map +1 -0
- package/dist/runs/human-ask-summary.js +18 -13
- package/dist/runs/human-ask-summary.js.map +1 -1
- package/dist/runs/legacy-index.d.ts +62 -0
- package/dist/runs/legacy-index.js +168 -0
- package/dist/runs/legacy-index.js.map +1 -0
- package/dist/runs/raw-record.d.ts +52 -0
- package/dist/runs/raw-record.js +145 -0
- package/dist/runs/raw-record.js.map +1 -0
- package/dist/runs/retention.d.ts +13 -6
- package/dist/runs/retention.js +42 -13
- package/dist/runs/retention.js.map +1 -1
- package/dist/runs/run-claims.d.ts +23 -0
- package/dist/runs/run-claims.js +24 -0
- package/dist/runs/run-claims.js.map +1 -0
- package/dist/runs/run-database.d.ts +385 -0
- package/dist/runs/run-database.js +930 -0
- package/dist/runs/run-database.js.map +1 -0
- package/dist/runs/run-index.d.ts +35 -9
- package/dist/runs/run-index.js +200 -25
- package/dist/runs/run-index.js.map +1 -1
- package/dist/runs/run-row.d.ts +44 -0
- package/dist/runs/run-row.js +84 -0
- package/dist/runs/run-row.js.map +1 -0
- package/dist/runs/run-search.d.ts +29 -0
- package/dist/runs/run-search.js +63 -0
- package/dist/runs/run-search.js.map +1 -0
- package/dist/runs/store-open-error.d.ts +24 -0
- package/dist/runs/store-open-error.js +61 -0
- package/dist/runs/store-open-error.js.map +1 -0
- package/dist/runs/store.d.ts +667 -32
- package/dist/runs/store.js +2153 -324
- package/dist/runs/store.js.map +1 -1
- package/dist/runs/transcript-facts.d.ts +77 -0
- package/dist/runs/transcript-facts.js +302 -0
- package/dist/runs/transcript-facts.js.map +1 -0
- package/dist/runs/webhook.js +5 -4
- package/dist/runs/webhook.js.map +1 -1
- 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/server/capabilities.d.ts +1 -0
- package/dist/server/capabilities.js +2 -0
- package/dist/server/capabilities.js.map +1 -1
- package/dist/server/cockpit-ownership.d.ts +8 -0
- package/dist/server/cockpit-ownership.js +19 -0
- package/dist/server/cockpit-ownership.js.map +1 -1
- package/dist/server/forge/github.d.ts +1 -4
- package/dist/server/forge/github.js +47 -23
- package/dist/server/forge/github.js.map +1 -1
- package/dist/server/git.js +1 -1
- package/dist/server/git.js.map +1 -1
- package/dist/server/open-in-app.js +1 -0
- package/dist/server/open-in-app.js.map +1 -1
- package/dist/server/preview-socket.d.ts +36 -0
- package/dist/server/preview-socket.js +126 -0
- package/dist/server/preview-socket.js.map +1 -0
- package/dist/server/project-context.d.ts +31 -4
- package/dist/server/project-context.js +73 -9
- package/dist/server/project-context.js.map +1 -1
- package/dist/server/provider-action-gate.js +1 -0
- package/dist/server/provider-action-gate.js.map +1 -1
- package/dist/server/repo-branches.d.ts +4 -3
- package/dist/server/repo-branches.js +5 -4
- package/dist/server/repo-branches.js.map +1 -1
- package/dist/server/server.d.ts +4500 -2727
- package/dist/server/server.js +651 -202
- package/dist/server/server.js.map +1 -1
- 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 +6 -1
- package/dist/server/validators.js +4 -4
- package/dist/server/validators.js.map +1 -1
- package/dist/server/ws.d.ts +16 -16
- package/dist/server/ws.js +56 -54
- package/dist/server/ws.js.map +1 -1
- package/dist/skills-remote.d.ts +14 -0
- package/dist/skills-remote.js +91 -15
- package/dist/skills-remote.js.map +1 -1
- package/dist/task-cli/cli.js +40 -16
- package/dist/task-cli/cli.js.map +1 -1
- package/dist/task-cli/discovery.d.ts +4 -3
- package/dist/task-cli/discovery.js +4 -3
- package/dist/task-cli/discovery.js.map +1 -1
- package/dist/task-cli/http.d.ts +2 -0
- package/dist/task-cli/http.js +10 -4
- package/dist/task-cli/http.js.map +1 -1
- package/dist/task-cli/projections.d.ts +5 -2
- package/dist/task-cli/projections.js +13 -0
- package/dist/task-cli/projections.js.map +1 -1
- package/dist/task-cli/watch.d.ts +15 -2
- package/dist/task-cli/watch.js +31 -9
- package/dist/task-cli/watch.js.map +1 -1
- package/dist/ui-state.d.ts +4 -3
- package/dist/ui-state.js +8 -3
- package/dist/ui-state.js.map +1 -1
- package/dist/workflows/grouped-command.d.ts +31 -0
- package/dist/workflows/grouped-command.js +143 -0
- package/dist/workflows/grouped-command.js.map +1 -0
- package/dist/workflows/run.d.ts +223 -42
- package/dist/workflows/run.js +1316 -398
- package/dist/workflows/run.js.map +1 -1
- package/dist/workflows/types.d.ts +12 -0
- package/dist/workflows/types.js +20 -0
- package/dist/workflows/types.js.map +1 -1
- package/dist/workspace/agent-accounts.d.ts +5 -1
- package/dist/workspace/agent-accounts.js +1 -0
- package/dist/workspace/agent-accounts.js.map +1 -1
- package/dist/workspace/agent-profiles.js +1 -1
- package/dist/workspace/agent-profiles.js.map +1 -1
- package/dist/workspace/config.d.ts +3 -1
- package/dist/workspace/config.js +1 -0
- package/dist/workspace/config.js.map +1 -1
- package/dist/worktree-setup.d.ts +85 -0
- package/dist/worktree-setup.js +164 -0
- package/dist/worktree-setup.js.map +1 -0
- package/package.json +7 -5
- package/scripts/benchmark-run-store-fixture.ts +428 -0
- package/scripts/benchmark-run-store.ts +743 -0
- package/scripts/benchmark-stuck-destroys.ts +143 -0
- package/scripts/mock-autonomous.mjs +11 -0
- package/scripts/mock-ci-tool.mjs +20 -5
- package/scripts/mock-claude.mjs +110 -1
- package/scripts/mock-codex-app-server.mjs +117 -2
- package/scripts/mock-codex-coalesce-steer.mjs +59 -0
- package/scripts/mock-codex-reverse-steer.mjs +130 -0
- package/scripts/mock-cursor-acp.mjs +75 -1
- package/scripts/mock-omp-rpc.mjs +605 -0
- package/scripts/mock-opencode-serve.mjs +312 -22
- package/scripts/mock-pi-rpc.mjs +109 -3
- package/scripts/mock-runner-crash.mjs +42 -0
- package/scripts/mock-silent-tail.mjs +78 -0
- package/scripts/mock-turn-messages.mjs +5 -0
- package/scripts/omp-ci-wait.mjs +29 -0
- package/scripts/omp-restrict-delegation.yml +4 -0
- package/scripts/pi-ci-wait.mjs +12 -9
- package/scripts/seed-archive-profile.ts +3 -3
- package/web/dist/assets/agent-accounts-CFOBMVHc.js +1 -0
- package/web/dist/assets/alert-dialog-BrpNUwMt.js +1 -0
- package/web/dist/assets/app-window-CSHwDX9N.js +1 -0
- package/web/dist/assets/button-C_rd7CPp.js +43 -0
- package/web/dist/assets/compare-variants-D6WXhvXA.js +1 -0
- package/web/dist/assets/diff-DrEARXTV.js +3 -0
- package/web/dist/assets/diff-controls-B1-j7nFg.js +1 -0
- package/web/dist/assets/{diff-view-DPn6bQ5W.js → diff-view-DvGLjdTk.js} +3 -3
- package/web/dist/assets/dropdown-menu-BegOZFrj.js +1 -0
- package/web/dist/assets/format-DpNe3S0A.js +1 -0
- package/web/dist/assets/git-toolbar-Cw4ljmhB.js +1 -0
- package/web/dist/assets/github-CfV5sqL0.js +1 -0
- package/web/dist/assets/{image-preview-DJ8cgb1Z.js → image-preview-FOEfqpZP.js} +1 -1
- package/web/dist/assets/index-DgWWlOLy.js +13 -0
- package/web/dist/assets/index-fBFnuvxu.css +1 -0
- package/web/dist/assets/{nav-row-styles-3j64kH5s.js → nav-row-styles-vp2JbhuJ.js} +1 -1
- package/web/dist/assets/play-CmVAj_fu.js +1 -0
- package/web/dist/assets/popover-BohP6WTx.js +1 -0
- package/web/dist/assets/preview-pane-connected-qTA5KNoJ.js +3 -0
- package/web/dist/assets/preview-state-vuMviq6q.js +1 -0
- package/web/dist/assets/prompt-templates-Cubdut_C.js +15 -0
- package/web/dist/assets/repo-git-Cf4t5PD0.js +1 -0
- package/web/dist/assets/{run-diff-5-RRdcVz.js → run-diff-BpkUW37Z.js} +3 -3
- package/web/dist/assets/run-header-68WHf9wV.js +1 -0
- package/web/dist/assets/skills-DiVIgKeU.js +1 -0
- package/web/dist/assets/square-terminal-B6RYYieR.js +1 -0
- package/web/dist/assets/status-dot-CqTJHSP9.js +1 -0
- package/web/dist/assets/{tab-link-K0hjzEXR.js → tab-link-B07Hu4QX.js} +1 -1
- package/web/dist/assets/task-changes--lQF6EYf.js +1 -0
- package/web/dist/assets/task-commits-Cx0qy4DN.js +1 -0
- package/web/dist/assets/task-files-D50s-gKW.js +2 -0
- package/web/dist/assets/task-github-item-DaqmSwYT.js +1 -0
- package/web/dist/assets/task-thread-BPQ64lui.js +7 -0
- package/web/dist/assets/{task-thread-Bw_nQuTx.css → task-thread-zNE2Q9E-.css} +1 -1
- package/web/dist/assets/textarea-D20O4Pw0.js +1 -0
- package/web/dist/assets/thread-state-DWcFMrvb.js +4 -0
- package/web/dist/assets/use-hand-to-agent-state-BLIGmwbd.css +1 -0
- package/web/dist/assets/use-hand-to-agent-state-Cy9eyyfL.js +1 -0
- package/web/dist/assets/{utils-fzNfbPzA.js → utils-JsL3ZsJf.js} +1 -1
- package/web/dist/assets/workflows-jwG7uxqi.js +11 -0
- package/web/dist/assets/{zoomable-image-CDmZ_Rtl.js → zoomable-image-BJJ64KGc.js} +1 -1
- package/web/dist/cezarion-lockup-dark.svg +1 -13
- package/web/dist/cezarion-lockup-light.svg +1 -13
- package/web/dist/cezarion-mark-dark.svg +1 -12
- package/web/dist/cezarion-mark-light.svg +1 -12
- package/web/dist/index.html +16 -21
- package/web/dist/assets/agent-accounts-CU2ZDv24.js +0 -1
- package/web/dist/assets/alert-dialog-C-xYE_dg.js +0 -43
- package/web/dist/assets/chevron-right-BENZ-1ZN.js +0 -1
- package/web/dist/assets/collapsible-DD9J91Iv.js +0 -1
- package/web/dist/assets/compare-variants-5FmqpFSN.js +0 -1
- package/web/dist/assets/diff-controls-Bj0YrioE.js +0 -1
- package/web/dist/assets/diff-rE8rm75J2.js +0 -3
- package/web/dist/assets/diff-stat-BKDCSVfJ.js +0 -1
- package/web/dist/assets/dropdown-menu-Ch5BYQh_.js +0 -1
- package/web/dist/assets/format-dBiNDNVe.js +0 -1
- package/web/dist/assets/git-toolbar-ClVvf5iF.js +0 -1
- package/web/dist/assets/github-BkNOyour.css +0 -1
- package/web/dist/assets/github-DeZMeMwJ.js +0 -1
- package/web/dist/assets/github-item-detail-W5F6GVjF.js +0 -1
- package/web/dist/assets/github-sidebar-model-DcMu-IK2.js +0 -1
- package/web/dist/assets/highlighter-CdHqIwFr.js +0 -3
- package/web/dist/assets/index-CatemBEL.js +0 -8
- package/web/dist/assets/index-MvfD8bZJ.css +0 -1
- package/web/dist/assets/markdown-zdKeLIYA.js +0 -2
- package/web/dist/assets/mobile-run-bar-BCfmFi8V.js +0 -1
- package/web/dist/assets/prompt-templates-CBPp39IU.js +0 -15
- package/web/dist/assets/reference-chip-D6owncJT.js +0 -1
- package/web/dist/assets/repo-git-CtdkZFey.js +0 -1
- package/web/dist/assets/run-header-BcjoDi0W.js +0 -1
- package/web/dist/assets/sheet-ChnYVrp1.js +0 -1
- package/web/dist/assets/skills-C8YKp2vr.js +0 -1
- package/web/dist/assets/task-changes-BW8YdWG8.js +0 -1
- package/web/dist/assets/task-commits-q2kB2y6L.js +0 -1
- package/web/dist/assets/task-files-pPL6_3Cr.js +0 -2
- package/web/dist/assets/task-github-item-BXF5R52U.js +0 -1
- package/web/dist/assets/task-thread-DtkFL3EC.js +0 -10
- package/web/dist/assets/textarea-DxlkE-Yd.js +0 -1
- package/web/dist/assets/token-metrics-DIAjrPlM.js +0 -1
- package/web/dist/assets/use-desktop-CrDny2uG.js +0 -1
- package/web/dist/assets/use-submit-shortcut-KWJsC906.js +0 -1
- package/web/dist/assets/workflows-TmcPEj2K.js +0 -11
- /package/web/dist/assets/{lib-BQXq3kEf2.js → lib-BQXq3kEf.js} +0 -0
|
@@ -0,0 +1,990 @@
|
|
|
1
|
+
import { parseEffort } from '../contract/index.js';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { dirname, resolve as resolvePath } from 'node:path';
|
|
4
|
+
import { parseAskMarker } from './ask.js';
|
|
5
|
+
import { isSignalTerminationExit } from './agent-runner.js';
|
|
6
|
+
import { signalSession, spawnSessionLeader } from './session-process.js';
|
|
7
|
+
import { buildChildEnv } from './agent-env.js';
|
|
8
|
+
import { cezarToolNames } from '../ci-wait/tools.js';
|
|
9
|
+
import { readNdjson } from './ndjson.js';
|
|
10
|
+
import { createOmpUiState, mapOmpRpcMessage, ompFlushProviderError, ompMessageUsage, ompProviderErrorMessage, ompTurnStarted, } from './omp-ui-mapper.js';
|
|
11
|
+
import { summarizeRunnerStderr } from './runner-stderr.js';
|
|
12
|
+
import { V1TextCoalescer } from './v1-text-coalescer.js';
|
|
13
|
+
import { InputSubmissions } from './input-submissions.js';
|
|
14
|
+
import { boundOutputDrainAfterExit, AUTO_END_DELAY_MS, DEFAULT_RUN_TIMEOUT_MS, KILL_GRACE_MS } from './runner-runtime.js';
|
|
15
|
+
/**
|
|
16
|
+
* OMP's built-in tool names at the pinned version (`src/tools/builtin-names.ts`, oh-my-pi
|
|
17
|
+
* v18.4.11; the real binary lists the same 30 in its `--tools` rejection). `--tools` is an
|
|
18
|
+
* allowlist validated against the registry, and an unknown name exits 2 before any frame, so
|
|
19
|
+
* only these, cezar's mapped names and `mcp__*` ever reach it.
|
|
20
|
+
*/
|
|
21
|
+
export const OMP_BUILTIN_TOOL_NAMES = [
|
|
22
|
+
'read', 'bash', 'edit', 'ast_grep', 'ast_edit', 'ask', 'debug', 'ida', 'eval', 'github',
|
|
23
|
+
'glob', 'grep', 'find', 'lsp', 'checkpoint', 'rewind', 'context_notes', 'new_context',
|
|
24
|
+
'security_scan', 'task', 'wait', 'todo', 'web_search', 'write', 'memory_edit', 'retain',
|
|
25
|
+
'recall', 'reflect', 'learn', 'manage_skill',
|
|
26
|
+
];
|
|
27
|
+
/**
|
|
28
|
+
* The tools OMP v18.4.11 enables with no `--tools` and no user config (recorded from the real
|
|
29
|
+
* binary's `get_state.dumpTools`). Only read under governed delegation, which must name a list
|
|
30
|
+
* to leave `task`, `wait` and `eval` out of it.
|
|
31
|
+
*/
|
|
32
|
+
const OMP_DEFAULT_TOOL_NAMES = [
|
|
33
|
+
'read', 'bash', 'edit', 'eval', 'glob', 'grep', 'task', 'wait', 'todo', 'web_search', 'write',
|
|
34
|
+
];
|
|
35
|
+
/** cezar tool names with an OMP equivalent; OMP built-ins and `mcp__*` pass through as-is. */
|
|
36
|
+
const OMP_TOOL_MAP = {
|
|
37
|
+
Read: 'read',
|
|
38
|
+
Edit: 'edit',
|
|
39
|
+
Write: 'write',
|
|
40
|
+
Bash: 'bash',
|
|
41
|
+
Grep: 'grep',
|
|
42
|
+
Glob: 'glob',
|
|
43
|
+
Subagent: 'task',
|
|
44
|
+
Task: 'task',
|
|
45
|
+
TodoWrite: 'todo',
|
|
46
|
+
WebSearch: 'web_search',
|
|
47
|
+
// No `WebFetch`: OMP v18.4.11 has no `fetch` built-in, and its `read` also reads local files,
|
|
48
|
+
// so mapping a web-only grant onto it would widen the grant. It is dropped (fail closed).
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* D1: tools that let the agent spawn agents outside cezar's governance. `wait` only joins `task`
|
|
52
|
+
* work; `eval` has `agent()`/`workpool()` helpers and no v18.4.11 setting turns them off (Ruling 6).
|
|
53
|
+
*/
|
|
54
|
+
const OMP_DELEGATION_TOOLS = new Set(['task', 'wait', 'eval']);
|
|
55
|
+
/**
|
|
56
|
+
* What the omp CLI receives from each `AgentRunSpec` field (#284, spec § Spawn and spec
|
|
57
|
+
* support). Held against the mock's recorded argv and RPC by the harness parity matrix.
|
|
58
|
+
*/
|
|
59
|
+
export const OMP_SPEC_SUPPORT = {
|
|
60
|
+
cezarTools: { honored: true, via: 'explicit CI --extension; cezar_wait_for_ci admitted when --tools is set' },
|
|
61
|
+
systemPrompt: { honored: true, via: '--append-system-prompt' },
|
|
62
|
+
userPrompt: { honored: true, via: 'RPC prompt.message' },
|
|
63
|
+
images: { honored: true, via: "RPC prompt.images ({type:'image', data, mimeType})" },
|
|
64
|
+
cwd: { honored: true, via: 'spawn cwd' },
|
|
65
|
+
allowedTools: { honored: true, via: '--tools, mapped onto OMP names; unmapped names dropped (fail closed)' },
|
|
66
|
+
restrictNativeDelegation: {
|
|
67
|
+
honored: true,
|
|
68
|
+
via: '--config overlay denying task, and task/wait left out of --tools (D1); eval is left out too, since OMP v18.4.11 has no setting that disables its agent()/workpool() helpers; with allowedTools undefined D1 passes an explicit --tools list (OMP\'s default set minus those), failing closed rather than widening to the defaults',
|
|
69
|
+
},
|
|
70
|
+
bashAllowlist: { honored: true, via: "no prefix equivalent: bash dropped from --tools when an allowlist is set (Pi's rule); with allowedTools unset, OMP's default set is named first so bash can be left out" },
|
|
71
|
+
additionalDirectories: { honored: true, via: '--add-dir per directory; an omp build that rejects --add-dir is respawned once without it, with a v1 note (Ruling 22)' },
|
|
72
|
+
env: { honored: true, via: 'merged over the child env through buildChildEnv' },
|
|
73
|
+
model: { honored: true, via: '--model provider/model' },
|
|
74
|
+
effort: { honored: true, via: '--thinking, canonical level' },
|
|
75
|
+
timeoutMs: { honored: true, via: 'wall-clock kill switch' },
|
|
76
|
+
sessionId: { honored: true, via: '--resume <id> when resume is set; a fresh session mints its own id, reported from get_state' },
|
|
77
|
+
resume: { honored: true, via: '--resume in place of a fresh session' },
|
|
78
|
+
resumeFallbackSystemPrompt: { honored: false, reason: 'Continue already resends spec.systemPrompt on every process; a separate fallback prompt is unused' },
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Maps cezar's tool names onto OMP's `--tools` allowlist (spec § Tools). Fails closed: a
|
|
82
|
+
* non-empty request that maps to nothing disables every built-in rather than widening to OMP's
|
|
83
|
+
* defaults, and every narrowing (`bashAllowlist`, D1) only ever removes names.
|
|
84
|
+
*/
|
|
85
|
+
export function ompTools(allowedTools, opts) {
|
|
86
|
+
// OMP's default set includes task, wait, eval and unrestricted bash: D1 and a bash allowlist
|
|
87
|
+
// each have to name a list to leave something out.
|
|
88
|
+
const narrows = opts.restrictNativeDelegation || (opts.bashAllowlist?.length ?? 0) > 0;
|
|
89
|
+
const requested = allowedTools ?? (narrows ? [...OMP_DEFAULT_TOOL_NAMES] : undefined);
|
|
90
|
+
if (requested === undefined)
|
|
91
|
+
return { flag: null, tools: [], dropped: [] };
|
|
92
|
+
const tools = new Set();
|
|
93
|
+
const dropped = new Set();
|
|
94
|
+
for (const name of requested) {
|
|
95
|
+
// `hasOwn`: a plain-object lookup would map `constructor` onto Object's own function.
|
|
96
|
+
const mapped = Object.hasOwn(OMP_TOOL_MAP, name)
|
|
97
|
+
? OMP_TOOL_MAP[name]
|
|
98
|
+
: name.startsWith(MCP_PREFIX) ? ompMcpToolName(name)
|
|
99
|
+
: OMP_BUILTIN_TOOL_NAMES.includes(name) ? name : undefined;
|
|
100
|
+
if (mapped === undefined)
|
|
101
|
+
dropped.add(name);
|
|
102
|
+
else
|
|
103
|
+
tools.add(mapped);
|
|
104
|
+
}
|
|
105
|
+
// OMP can allow or deny the whole bash tool but has no command-prefix equivalent.
|
|
106
|
+
if (opts.bashAllowlist && opts.bashAllowlist.length > 0)
|
|
107
|
+
tools.delete('bash');
|
|
108
|
+
if (opts.restrictNativeDelegation)
|
|
109
|
+
for (const name of OMP_DELEGATION_TOOLS)
|
|
110
|
+
tools.delete(name);
|
|
111
|
+
if (tools.size === 0)
|
|
112
|
+
return { flag: 'no-tools', tools: [], dropped: [...dropped] };
|
|
113
|
+
// Narrow before the CI tool joins: a refusal never leaves a CI-only list where --no-tools belongs.
|
|
114
|
+
for (const name of opts.exclude ?? [])
|
|
115
|
+
tools.delete(name);
|
|
116
|
+
if (tools.size === 0)
|
|
117
|
+
return { flag: 'no-tools', tools: [], dropped: [...dropped] };
|
|
118
|
+
// Exactly the names omp-ci-wait.mjs registers (Ruling 11): OMP exits 2 on an unknown --tools name.
|
|
119
|
+
if (opts.cezarTools)
|
|
120
|
+
for (const name of cezarToolNames(opts.env ?? {}))
|
|
121
|
+
tools.add(name);
|
|
122
|
+
return { flag: 'tools', tools: [...tools], dropped: [...dropped] };
|
|
123
|
+
}
|
|
124
|
+
const MCP_PREFIX = 'mcp__';
|
|
125
|
+
/**
|
|
126
|
+
* OMP's name for an MCP tool granted in Claude's `mcp__<server>__<tool>` spelling (Ruling 20).
|
|
127
|
+
* v18.4.11 registers `mcp__<server>_<tool>` (`qjn`): each part lowercased, runs of anything but
|
|
128
|
+
* `[a-z0-9_]` and repeated underscores folded to one `_`, edges trimmed, and a tool name that
|
|
129
|
+
* repeats its server's prefix stripped of it. `--tools` validation matches exact names, so the
|
|
130
|
+
* Claude spelling would exit 2. A name without a second `__` is already OMP's spelling (OMP
|
|
131
|
+
* folds every `__` in it) and passes verbatim. OMP shortens names past 64 characters with a
|
|
132
|
+
* Bun hash cezar cannot reproduce; such a name stays unknown and the startup refusal drops it.
|
|
133
|
+
*/
|
|
134
|
+
function ompMcpToolName(name) {
|
|
135
|
+
const rest = name.slice(MCP_PREFIX.length);
|
|
136
|
+
const split = rest.indexOf('__');
|
|
137
|
+
if (split <= 0 || split + 2 >= rest.length)
|
|
138
|
+
return name;
|
|
139
|
+
const server = ompMcpNamePart(rest.slice(0, split), 'server');
|
|
140
|
+
const tool = ompMcpNamePart(rest.slice(split + 2), 'tool');
|
|
141
|
+
return `${MCP_PREFIX}${server}_${tool.startsWith(`${server}_`) ? tool.slice(server.length + 1) : tool}`;
|
|
142
|
+
}
|
|
143
|
+
/** v18.4.11 `x7s` with digits kept, the variant OMP registers under. */
|
|
144
|
+
function ompMcpNamePart(value, fallback) {
|
|
145
|
+
const part = value.toLowerCase().replace(/[^a-z0-9_]+/g, '_').replace(/_+/g, '_').replace(/^_+|_+$/g, '');
|
|
146
|
+
return part.length > 0 ? part : fallback;
|
|
147
|
+
}
|
|
148
|
+
/** `omp --mode rpc` argv, in the order of the spec's spawn block. */
|
|
149
|
+
export function buildOmpArgs(spec, excludeTools,
|
|
150
|
+
/** Ruling 22: an omp build that rejects `--add-dir` runs without the additional directories. */
|
|
151
|
+
opts = {}) {
|
|
152
|
+
const args = ['--mode', 'rpc'];
|
|
153
|
+
if (spec.cezarTools)
|
|
154
|
+
args.push('--extension', ompScriptPath('omp-ci-wait.mjs'));
|
|
155
|
+
// OMP has no --session-id: a fresh session mints its id, which get_state reports.
|
|
156
|
+
if (spec.resume && spec.sessionId)
|
|
157
|
+
args.push('--resume', spec.sessionId);
|
|
158
|
+
if (spec.systemPrompt)
|
|
159
|
+
args.push('--append-system-prompt', spec.systemPrompt);
|
|
160
|
+
if (spec.model)
|
|
161
|
+
args.push('--model', spec.model);
|
|
162
|
+
const effort = parseEffort(spec.effort);
|
|
163
|
+
if (effort)
|
|
164
|
+
args.push('--thinking', effort);
|
|
165
|
+
if (!opts.omitAddDir)
|
|
166
|
+
for (const dir of spec.additionalDirectories ?? [])
|
|
167
|
+
args.push('--add-dir', dir);
|
|
168
|
+
if (spec.restrictNativeDelegation)
|
|
169
|
+
args.push('--config', ompScriptPath('omp-restrict-delegation.yml'));
|
|
170
|
+
const selection = ompTools(spec.allowedTools, {
|
|
171
|
+
bashAllowlist: spec.bashAllowlist,
|
|
172
|
+
restrictNativeDelegation: spec.restrictNativeDelegation,
|
|
173
|
+
cezarTools: spec.cezarTools !== undefined,
|
|
174
|
+
env: spec.env,
|
|
175
|
+
exclude: excludeTools,
|
|
176
|
+
});
|
|
177
|
+
if (selection.flag === 'tools')
|
|
178
|
+
args.push('--tools', selection.tools.join(','));
|
|
179
|
+
else if (selection.flag === 'no-tools')
|
|
180
|
+
args.push('--no-tools');
|
|
181
|
+
return args;
|
|
182
|
+
}
|
|
183
|
+
function ompScriptPath(name) {
|
|
184
|
+
return fileURLToPath(new URL(`../../scripts/${name}`, import.meta.url));
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Persistent subprocess adapter for OMP's RPC mode (#595), one `omp --mode rpc` child per
|
|
188
|
+
* session. Structure follows `pi-runner.ts` step for step (Pi stays untouched); what OMP changes
|
|
189
|
+
* is the turn boundary — `session_settled`, or the turn-opening prompt OMP completed without the
|
|
190
|
+
* agent or rejected before admission (`ompTurnBoundary`, gated by prompt id in the mapper), never
|
|
191
|
+
* `agent_end` — plus the startup commands and the error paths. v1 `turn-end` follows the mapper
|
|
192
|
+
* closing its turn, so the two streams cannot disagree on a boundary.
|
|
193
|
+
*
|
|
194
|
+
* Contract: oh-my-pi v18.4.11 `docs/rpc.md`, `packages/coding-agent/src/modes/rpc/rpc-mode.ts`.
|
|
195
|
+
* Design record and the implementation-time rulings: `.ai/specs/2026-10-02-omp-runner.md`.
|
|
196
|
+
*/
|
|
197
|
+
export class OmpRunner {
|
|
198
|
+
backend = 'omp';
|
|
199
|
+
specSupport = OMP_SPEC_SUPPORT;
|
|
200
|
+
systemPromptOnResume = 'resent';
|
|
201
|
+
inputDelivery = {
|
|
202
|
+
mode: 'steer', consumption: 'observable',
|
|
203
|
+
via: 'prompt with streamingBehavior steer; set_steering_mode all at session start; user message_start with the submitted text',
|
|
204
|
+
};
|
|
205
|
+
bin;
|
|
206
|
+
timeoutMs;
|
|
207
|
+
killGraceMs;
|
|
208
|
+
lastSession = null;
|
|
209
|
+
constructor(opts = {}) {
|
|
210
|
+
this.bin = opts.bin ?? process.env.CEZ_OMP_BIN ?? (process.env.CEZ_DRY_RUN === '1' ? mockOmpPath() : 'omp');
|
|
211
|
+
this.timeoutMs = opts.timeoutMs ?? DEFAULT_RUN_TIMEOUT_MS;
|
|
212
|
+
this.killGraceMs = opts.killGraceMs ?? KILL_GRACE_MS;
|
|
213
|
+
}
|
|
214
|
+
run(spec, onEvent) {
|
|
215
|
+
return this.startSession(spec, onEvent, { autoEndAfterFirstTurn: true }).result;
|
|
216
|
+
}
|
|
217
|
+
async interrupt() {
|
|
218
|
+
this.lastSession?.interrupt();
|
|
219
|
+
}
|
|
220
|
+
startSession(spec, onEvent, opts = {}) {
|
|
221
|
+
const killGraceMs = this.killGraceMs;
|
|
222
|
+
let spawnError = null;
|
|
223
|
+
const stderr = [];
|
|
224
|
+
const spawnOmp = (args) => {
|
|
225
|
+
const spawned = spawnSessionLeader(this.bin, args, {
|
|
226
|
+
cwd: spec.cwd,
|
|
227
|
+
env: buildChildEnv({ backend: this.backend, extraEnv: spec.env }),
|
|
228
|
+
});
|
|
229
|
+
boundOutputDrainAfterExit(spawned);
|
|
230
|
+
spawned.on('error', (error) => {
|
|
231
|
+
spawnError = wrapSpawnError(error, this.bin);
|
|
232
|
+
});
|
|
233
|
+
spawned.stderr.setEncoding('utf8');
|
|
234
|
+
spawned.stderr.on('data', (chunk) => stderr.push(chunk));
|
|
235
|
+
return spawned;
|
|
236
|
+
};
|
|
237
|
+
// Reassigned at most once per refusal class: Ruling 13/20 narrow `--tools`, Ruling 22 drops
|
|
238
|
+
// `--add-dir` for an omp build that does not accept it.
|
|
239
|
+
const firstArgs = buildOmpArgs(spec);
|
|
240
|
+
let spawnArgs = firstArgs;
|
|
241
|
+
let child = spawnOmp(firstArgs);
|
|
242
|
+
let toolsRetried = false;
|
|
243
|
+
let flagsRetried = false;
|
|
244
|
+
let refusedTools = [];
|
|
245
|
+
let omitAddDir = false;
|
|
246
|
+
let sawFrame = false;
|
|
247
|
+
let closedByCaller = false;
|
|
248
|
+
// A refused spawn must leave nothing in v2, so its events wait for the first stdout frame.
|
|
249
|
+
// Held only when something can be dropped and retried: a `--tools` list or `--add-dir`.
|
|
250
|
+
let uiHold = firstArgs.includes('--tools') || firstArgs.includes('--add-dir') ? [] : null;
|
|
251
|
+
const emitUiEvent = (event) => {
|
|
252
|
+
if (uiHold)
|
|
253
|
+
uiHold.push(event);
|
|
254
|
+
else
|
|
255
|
+
opts.onUiEvent?.(event);
|
|
256
|
+
};
|
|
257
|
+
const releaseUiHold = () => {
|
|
258
|
+
const held = uiHold;
|
|
259
|
+
uiHold = null;
|
|
260
|
+
for (const event of held ?? [])
|
|
261
|
+
opts.onUiEvent?.(event);
|
|
262
|
+
};
|
|
263
|
+
let open = true;
|
|
264
|
+
let timedOut = false;
|
|
265
|
+
let terminatedByCezar = false;
|
|
266
|
+
let autoEndTimer;
|
|
267
|
+
let killTimer;
|
|
268
|
+
let interruptKillTimer;
|
|
269
|
+
let ompUi = createOmpUiState();
|
|
270
|
+
const textChunks = [];
|
|
271
|
+
/** One v1 `text` per complete block, never per token (pi-runner.ts, #902 / #2). */
|
|
272
|
+
const textCoalescer = new V1TextCoalescer((text) => {
|
|
273
|
+
textChunks.push(text);
|
|
274
|
+
onEvent?.({ type: 'text', text });
|
|
275
|
+
});
|
|
276
|
+
/** `contentIndex` restarts at 0 on every assistant message (as Pi's); see pi-runner.ts. */
|
|
277
|
+
let textBlockSeq = 0;
|
|
278
|
+
const openTextBlockKeys = new Map();
|
|
279
|
+
const textBlockKey = (contentIndex) => {
|
|
280
|
+
if (typeof contentIndex !== 'number')
|
|
281
|
+
return undefined;
|
|
282
|
+
let key = openTextBlockKeys.get(contentIndex);
|
|
283
|
+
if (!key) {
|
|
284
|
+
key = `omp-text-${++textBlockSeq}`;
|
|
285
|
+
openTextBlockKeys.set(contentIndex, key);
|
|
286
|
+
}
|
|
287
|
+
return key;
|
|
288
|
+
};
|
|
289
|
+
const flushText = () => {
|
|
290
|
+
textCoalescer.flush();
|
|
291
|
+
openTextBlockKeys.clear();
|
|
292
|
+
};
|
|
293
|
+
const toolCalls = [];
|
|
294
|
+
let sessionId = spec.sessionId;
|
|
295
|
+
let startupStateChecked = false;
|
|
296
|
+
let tokensUsed = 0;
|
|
297
|
+
let latchedProviderError;
|
|
298
|
+
const emitLatchedProviderError = () => {
|
|
299
|
+
if (!latchedProviderError)
|
|
300
|
+
return;
|
|
301
|
+
onEvent?.({ type: 'error', message: latchedProviderError });
|
|
302
|
+
latchedProviderError = undefined;
|
|
303
|
+
};
|
|
304
|
+
const emitUi = (value) => {
|
|
305
|
+
const mapped = mapOmpRpcMessage(value, ompUi);
|
|
306
|
+
ompUi = mapped.state;
|
|
307
|
+
for (const event of mapped.events)
|
|
308
|
+
emitUiEvent(event);
|
|
309
|
+
};
|
|
310
|
+
/** Releases the mapper's provider-error latch when the stream ends without a settle. */
|
|
311
|
+
const emitLatchedUiProviderError = () => {
|
|
312
|
+
const mapped = ompFlushProviderError(ompUi);
|
|
313
|
+
ompUi = mapped.state;
|
|
314
|
+
for (const event of mapped.events)
|
|
315
|
+
emitUiEvent(event);
|
|
316
|
+
};
|
|
317
|
+
const writeNow = (command) => {
|
|
318
|
+
if (!child.stdin.writable)
|
|
319
|
+
return false;
|
|
320
|
+
try {
|
|
321
|
+
child.stdin.write(`${JSON.stringify(command)}\n`);
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
catch {
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
// Until the current child speaks (OMP's `ready` frame) nothing is written to it: a spawn the
|
|
329
|
+
// Ruling 13 refusal rejects exits without reading stdin, and what it was handed would be lost with
|
|
330
|
+
// it. Accepted commands wait here, in order, and the respawned child gets the same ones.
|
|
331
|
+
const outbox = [];
|
|
332
|
+
const flushOutbox = () => {
|
|
333
|
+
for (const command of outbox.splice(0))
|
|
334
|
+
writeNow(command);
|
|
335
|
+
};
|
|
336
|
+
const write = (command) => {
|
|
337
|
+
if (!open)
|
|
338
|
+
return false;
|
|
339
|
+
if (!sawFrame) {
|
|
340
|
+
outbox.push(command);
|
|
341
|
+
return true;
|
|
342
|
+
}
|
|
343
|
+
return writeNow(command);
|
|
344
|
+
};
|
|
345
|
+
let agentInputReady = false;
|
|
346
|
+
let promptSerial = 0;
|
|
347
|
+
let humanSerial = 0;
|
|
348
|
+
// Every prompt carries an id: a local completion or failure ends the turn only when it
|
|
349
|
+
// answers the prompt that opened it, never a steer (the mapper gates on this id).
|
|
350
|
+
const humanAcks = new Set();
|
|
351
|
+
let agentAck;
|
|
352
|
+
const rejectAgentAck = () => {
|
|
353
|
+
const pending = agentAck;
|
|
354
|
+
agentAck = undefined;
|
|
355
|
+
pending?.reject(new Error('omp closed before prompt acknowledgement'));
|
|
356
|
+
};
|
|
357
|
+
const scheduleAutoEnd = () => {
|
|
358
|
+
if (!opts.autoEndAfterFirstTurn || !open || autoEndTimer || agentAck || humanAcks.size)
|
|
359
|
+
return;
|
|
360
|
+
autoEndTimer = setTimeout(() => {
|
|
361
|
+
autoEndTimer = undefined;
|
|
362
|
+
if (opts.shouldAutoEnd?.() !== false)
|
|
363
|
+
end();
|
|
364
|
+
}, AUTO_END_DELAY_MS);
|
|
365
|
+
autoEndTimer.unref?.();
|
|
366
|
+
};
|
|
367
|
+
// Same readiness rules as pi-runner.ts (#505): a refusal caused only by an outstanding
|
|
368
|
+
// acknowledgement gets one readiness hint when it lands, even mid-turn.
|
|
369
|
+
let refusedForAck = false;
|
|
370
|
+
const readyAfterAck = () => {
|
|
371
|
+
if (open && agentInputReady && !ompUi.turnId && !agentAck && !humanAcks.size) {
|
|
372
|
+
refusedForAck = false;
|
|
373
|
+
opts.onAgentInputReady?.();
|
|
374
|
+
scheduleAutoEnd();
|
|
375
|
+
}
|
|
376
|
+
else if (refusedForAck && open && !agentAck && !humanAcks.size && !pendingMarkerAsk) {
|
|
377
|
+
refusedForAck = false;
|
|
378
|
+
opts.onAgentInputReady?.();
|
|
379
|
+
}
|
|
380
|
+
else if (open && !ompUi.turnId && !agentAck && !humanAcks.size && !pendingMarkerAsk) {
|
|
381
|
+
// #486: a human ack drained with no turn open — re-settle the idle boundary.
|
|
382
|
+
refusedForAck = false;
|
|
383
|
+
opts.onAgentInputReady?.();
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
let pendingMarkerAsk = false;
|
|
387
|
+
let turnTextStart = 0;
|
|
388
|
+
// Accepted agent prompts not yet seen as a user message_start (#505).
|
|
389
|
+
const submissions = new InputSubmissions();
|
|
390
|
+
// Submissions already queued when a turn began: that turn reads them. OMP reports an
|
|
391
|
+
// admitted steer at the next terminal agent_end (rpc-prompt-results.ts), never drops it.
|
|
392
|
+
let carried = new Set();
|
|
393
|
+
const acked = new Set();
|
|
394
|
+
let turnFailed = false;
|
|
395
|
+
const sendMessage = (content, requestId, inputIds = []) => {
|
|
396
|
+
if (!open)
|
|
397
|
+
return false;
|
|
398
|
+
// A steer joins the running turn; only an idle prompt opens one (#505).
|
|
399
|
+
const opensTurn = !ompUi.turnId;
|
|
400
|
+
agentInputReady = false;
|
|
401
|
+
if (opensTurn) {
|
|
402
|
+
pendingMarkerAsk = false;
|
|
403
|
+
turnTextStart = textChunks.length;
|
|
404
|
+
}
|
|
405
|
+
const { message, images } = toOmpPrompt(content);
|
|
406
|
+
const id = requestId ?? `cezar-prompt-${++humanSerial}`;
|
|
407
|
+
if (autoEndTimer) {
|
|
408
|
+
clearTimeout(autoEndTimer);
|
|
409
|
+
autoEndTimer = undefined;
|
|
410
|
+
}
|
|
411
|
+
if (!write({
|
|
412
|
+
type: 'prompt',
|
|
413
|
+
id,
|
|
414
|
+
message,
|
|
415
|
+
...(images.length > 0 ? { images } : {}),
|
|
416
|
+
...(ompUi.turnId ? { streamingBehavior: 'steer' } : {}),
|
|
417
|
+
})) {
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
if (!requestId)
|
|
421
|
+
humanAcks.add(id);
|
|
422
|
+
else
|
|
423
|
+
submissions.accept(requestId, inputIds, message);
|
|
424
|
+
if (!ompUi.turnId) {
|
|
425
|
+
const mapped = ompTurnStarted(ompUi, id);
|
|
426
|
+
ompUi = mapped.state;
|
|
427
|
+
for (const event of mapped.events)
|
|
428
|
+
emitUiEvent(event);
|
|
429
|
+
}
|
|
430
|
+
return true;
|
|
431
|
+
};
|
|
432
|
+
const end = () => {
|
|
433
|
+
if (!open)
|
|
434
|
+
return;
|
|
435
|
+
open = false;
|
|
436
|
+
closedByCaller = true;
|
|
437
|
+
rejectAgentAck();
|
|
438
|
+
flushOutbox();
|
|
439
|
+
child.stdin.end();
|
|
440
|
+
killTimer = setTimeout(() => {
|
|
441
|
+
if (child.exitCode !== null || child.signalCode !== null)
|
|
442
|
+
return;
|
|
443
|
+
terminatedByCezar = true;
|
|
444
|
+
signalSession(child, 'SIGTERM');
|
|
445
|
+
// An OMP that ignores SIGTERM would otherwise hold `result` open forever: the deadline's
|
|
446
|
+
// interrupt() is a no-op once `open` is false.
|
|
447
|
+
killTimer = setTimeout(() => {
|
|
448
|
+
if (child.exitCode === null && child.signalCode === null)
|
|
449
|
+
signalSession(child, 'SIGKILL');
|
|
450
|
+
}, killGraceMs);
|
|
451
|
+
killTimer.unref?.();
|
|
452
|
+
}, killGraceMs);
|
|
453
|
+
killTimer.unref?.();
|
|
454
|
+
};
|
|
455
|
+
const interrupt = () => {
|
|
456
|
+
if (!open)
|
|
457
|
+
return;
|
|
458
|
+
if (sawFrame)
|
|
459
|
+
writeNow({ type: 'abort' });
|
|
460
|
+
open = false;
|
|
461
|
+
closedByCaller = true;
|
|
462
|
+
rejectAgentAck();
|
|
463
|
+
terminatedByCezar = true;
|
|
464
|
+
signalSession(child, 'SIGTERM');
|
|
465
|
+
interruptKillTimer = setTimeout(() => {
|
|
466
|
+
if (child.exitCode === null && child.signalCode === null)
|
|
467
|
+
signalSession(child, 'SIGKILL');
|
|
468
|
+
}, killGraceMs);
|
|
469
|
+
interruptKillTimer.unref?.();
|
|
470
|
+
};
|
|
471
|
+
const dropped = ompTools(spec.allowedTools, {
|
|
472
|
+
bashAllowlist: spec.bashAllowlist,
|
|
473
|
+
restrictNativeDelegation: spec.restrictNativeDelegation,
|
|
474
|
+
}).dropped;
|
|
475
|
+
if (dropped.length > 0) {
|
|
476
|
+
onEvent?.({ type: 'note', message: `omp: dropped tools with no OMP equivalent: ${dropped.join(', ')}` });
|
|
477
|
+
}
|
|
478
|
+
const startup = () => {
|
|
479
|
+
// Spec § Session lifecycle: OMP queues these until it is ready.
|
|
480
|
+
write({ id: 'cezar-state', type: 'get_state' });
|
|
481
|
+
// #551 parity: OMP defaults steeringMode to one-at-a-time, and sendAgentMessage requires
|
|
482
|
+
// every accepted steer at the next model call.
|
|
483
|
+
write({ id: 'cezar-steering', type: 'set_steering_mode', mode: 'all' });
|
|
484
|
+
write({ id: 'cezar-subagents', type: 'set_subagent_subscription', level: 'events' });
|
|
485
|
+
// Drops the per-delta `partial` snapshot; the coalescer and the mapper read deltas only.
|
|
486
|
+
write({ id: 'cezar-event-filter', type: 'set_event_filter', events: null, messageUpdates: 'delta' });
|
|
487
|
+
sendMessage([...(spec.images ?? []), { type: 'text', text: spec.userPrompt }]);
|
|
488
|
+
};
|
|
489
|
+
startup();
|
|
490
|
+
const limitMs = spec.timeoutMs ?? this.timeoutMs;
|
|
491
|
+
// One wall-clock limit for the whole run: a Ruling 13 respawn re-arms with what is left.
|
|
492
|
+
const deadlineAt = Date.now() + limitMs;
|
|
493
|
+
const armDeadline = () => {
|
|
494
|
+
const timer = limitMs > 0
|
|
495
|
+
? setTimeout(() => {
|
|
496
|
+
timedOut = true;
|
|
497
|
+
interrupt();
|
|
498
|
+
}, Math.max(0, deadlineAt - Date.now()))
|
|
499
|
+
: undefined;
|
|
500
|
+
timer?.unref?.();
|
|
501
|
+
return timer;
|
|
502
|
+
};
|
|
503
|
+
let deadline = armDeadline();
|
|
504
|
+
// A refusal is only retried before any frame, once per class, and never after the caller or a
|
|
505
|
+
// deadline ended the session; a spawn with nothing to drop has `uiHold` null.
|
|
506
|
+
const retryPossible = () => !sawFrame && !(toolsRetried && flagsRetried) && !timedOut && !terminatedByCezar && !closedByCaller && uiHold !== null;
|
|
507
|
+
const result = (async function runOmp() {
|
|
508
|
+
try {
|
|
509
|
+
for await (const line of readNdjson(child.stdout)) {
|
|
510
|
+
if (!sawFrame) {
|
|
511
|
+
sawFrame = true;
|
|
512
|
+
releaseUiHold();
|
|
513
|
+
flushOutbox();
|
|
514
|
+
}
|
|
515
|
+
let value;
|
|
516
|
+
try {
|
|
517
|
+
value = JSON.parse(line);
|
|
518
|
+
}
|
|
519
|
+
catch {
|
|
520
|
+
onEvent?.({ type: 'note', message: `omp: skipped unparseable RPC line: ${truncate(line)}` });
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
opts.onActivity?.();
|
|
524
|
+
// Flush before message/tool/turn boundaries so v2 events never overtake a pending
|
|
525
|
+
// block; never on prompt acks, which can land between text_deltas (pi-runner.ts).
|
|
526
|
+
if (isRecord(value) &&
|
|
527
|
+
(value.type === 'tool_execution_start' ||
|
|
528
|
+
value.type === 'message_end' ||
|
|
529
|
+
value.type === 'session_settled' ||
|
|
530
|
+
value.type === 'prompt_result' ||
|
|
531
|
+
value.type === 'turn_end' ||
|
|
532
|
+
value.type === 'agent_end')) {
|
|
533
|
+
flushText();
|
|
534
|
+
}
|
|
535
|
+
// A boundary only ends a turn that is open: OMP never settles an idle session, and a
|
|
536
|
+
// second boundary for the same prompt must not end the next one.
|
|
537
|
+
const turnOpen = ompUi.turnId !== null;
|
|
538
|
+
emitUi(value);
|
|
539
|
+
const usageReport = ompMessageUsage(value);
|
|
540
|
+
if (usageReport) {
|
|
541
|
+
const { input, output, cacheRead = 0, cacheWrite = 0 } = usageReport.usage;
|
|
542
|
+
// v1 keeps its historical per-message weighted token convention; v2 exposes
|
|
543
|
+
// the raw components of these same parent and child reports.
|
|
544
|
+
tokensUsed += Math.round(input + output + cacheRead * 0.1 + cacheWrite * 1.25);
|
|
545
|
+
onEvent?.({ type: 'token-usage', tokensUsed });
|
|
546
|
+
if (usageReport.costUsd !== undefined)
|
|
547
|
+
onEvent?.({ type: 'cost', usd: usageReport.costUsd });
|
|
548
|
+
}
|
|
549
|
+
// The mapper closed its turn on this frame: v1 ends the turn with it.
|
|
550
|
+
const endsTurn = turnOpen && ompUi.turnId === null;
|
|
551
|
+
if (!isRecord(value))
|
|
552
|
+
continue;
|
|
553
|
+
if (value.type === 'response' && value.command === 'get_state' && value.success === true && isRecord(value.data)) {
|
|
554
|
+
const discovered = string(value.data.sessionId);
|
|
555
|
+
if (discovered && discovered !== sessionId) {
|
|
556
|
+
sessionId = discovered;
|
|
557
|
+
onEvent?.({ type: 'session', sessionId: discovered });
|
|
558
|
+
}
|
|
559
|
+
// OMP v18.6.1 still has no fresh persistent-session flag. Its autoResume
|
|
560
|
+
// setting can restore history without --resume (hearsay-tools/cezarion#832).
|
|
561
|
+
// Only the startup query is evidence: later reads include our own messages.
|
|
562
|
+
if (value.id === 'cezar-state' && !startupStateChecked) {
|
|
563
|
+
startupStateChecked = true;
|
|
564
|
+
const count = value.data.messageCount;
|
|
565
|
+
if (!spec.resume && typeof count === 'number' && count > 0) {
|
|
566
|
+
const message = `omp: unexpectedly restored a session with ${count} messages for a fresh step. Previous conversation context is active; check OMP's autoResume setting.`;
|
|
567
|
+
onEvent?.({ type: 'note', message });
|
|
568
|
+
emitUiEvent({ type: 'session.error', message, fatal: false });
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
else if (value.type === 'response' && string(value.command)?.startsWith('set_')) {
|
|
573
|
+
if (value.success !== true) {
|
|
574
|
+
onEvent?.({ type: 'note', message: `omp: ${string(value.command)} failed: ${rpcError(value)}` });
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
else if (value.type === 'response' && value.command === 'prompt') {
|
|
578
|
+
if (value.success === false) {
|
|
579
|
+
// The turn-opening prompt rejected before admission gets no `prompt_result`, so it
|
|
580
|
+
// is the turn's error here (Ruling 10). A failure after admission is reported by the
|
|
581
|
+
// `prompt_result` that follows it (rpc-prompt-results.ts `fail`), and a steer's
|
|
582
|
+
// failure leaves the turn running: both are only a note.
|
|
583
|
+
const message = `omp: prompt failed: ${rpcError(value)}`;
|
|
584
|
+
if (endsTurn) {
|
|
585
|
+
onEvent?.({ type: 'error', message });
|
|
586
|
+
turnFailed = true;
|
|
587
|
+
}
|
|
588
|
+
else {
|
|
589
|
+
onEvent?.({ type: 'note', message });
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
const pending = agentAck;
|
|
593
|
+
const id = string(value.id);
|
|
594
|
+
if (pending && id === pending.id) {
|
|
595
|
+
agentAck = undefined;
|
|
596
|
+
if (value.success === true) {
|
|
597
|
+
acked.add(pending.id);
|
|
598
|
+
pending.resolve();
|
|
599
|
+
}
|
|
600
|
+
else
|
|
601
|
+
pending.reject(new Error(rpcError(value)));
|
|
602
|
+
}
|
|
603
|
+
else if (id) {
|
|
604
|
+
humanAcks.delete(id);
|
|
605
|
+
}
|
|
606
|
+
readyAfterAck();
|
|
607
|
+
}
|
|
608
|
+
else if (value.type === 'response' && value.success === false) {
|
|
609
|
+
onEvent?.({ type: 'error', message: rpcError(value) });
|
|
610
|
+
}
|
|
611
|
+
else if (value.type === 'prompt_result' && value.agentInvoked === false && value.status === 'error') {
|
|
612
|
+
// The prompt failed before the agent ran: no settle follows, so the turn ends here
|
|
613
|
+
// when it opened the turn. A steer failing mid-turn leaves it running.
|
|
614
|
+
const error = isRecord(value.error) ? value.error : {};
|
|
615
|
+
const message = ompProviderErrorMessage({ provider: error.provider, model: error.model, errorMessage: error.message });
|
|
616
|
+
if (endsTurn) {
|
|
617
|
+
onEvent?.({ type: 'error', message });
|
|
618
|
+
turnFailed = true;
|
|
619
|
+
}
|
|
620
|
+
else {
|
|
621
|
+
// v2 reports it as a session error whether or not a turn is open.
|
|
622
|
+
onEvent?.({ type: 'note', message });
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
else if (value.type === 'message_update' && isRecord(value.assistantMessageEvent)) {
|
|
626
|
+
const update = value.assistantMessageEvent;
|
|
627
|
+
const contentKey = textBlockKey(update.contentIndex);
|
|
628
|
+
if (update.type === 'text_delta' && typeof update.delta === 'string') {
|
|
629
|
+
textCoalescer.append(contentKey, update.delta);
|
|
630
|
+
}
|
|
631
|
+
else if (update.type === 'text_end') {
|
|
632
|
+
const snapshot = typeof update.content === 'string' ? update.content : undefined;
|
|
633
|
+
textCoalescer.complete(contentKey, snapshot);
|
|
634
|
+
if (typeof update.contentIndex === 'number')
|
|
635
|
+
openTextBlockKeys.delete(update.contentIndex);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
else if (value.type === 'message_end' && isRecord(value.message) && value.message.role === 'assistant') {
|
|
639
|
+
flushText();
|
|
640
|
+
// Latched until the turn ends, cleared by a later success: OMP retries past
|
|
641
|
+
// provider flakes and a recovered retry must stay silent (#256, #316).
|
|
642
|
+
if (string(value.message.stopReason) === 'error') {
|
|
643
|
+
turnFailed = true;
|
|
644
|
+
latchedProviderError = ompProviderErrorMessage(value.message);
|
|
645
|
+
}
|
|
646
|
+
else {
|
|
647
|
+
latchedProviderError = undefined;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
else if (value.type === 'agent_start') {
|
|
651
|
+
// Only a prompt OMP acknowledged before this agent_start can be in its turn (#505).
|
|
652
|
+
carried = new Set(submissions.pendingIds().filter(id => acked.has(id)));
|
|
653
|
+
turnFailed = false;
|
|
654
|
+
}
|
|
655
|
+
else if (value.type === 'message_start' && isRecord(value.message) && value.message.role === 'user') {
|
|
656
|
+
// The model received this prompt now (#505).
|
|
657
|
+
const ids = submissions.consumeOldestByText(ompMessageText(value.message));
|
|
658
|
+
if (ids.length)
|
|
659
|
+
opts.onAgentInputConsumed?.(ids);
|
|
660
|
+
}
|
|
661
|
+
else if (value.type === 'tool_execution_start') {
|
|
662
|
+
flushText();
|
|
663
|
+
const id = string(value.toolCallId);
|
|
664
|
+
const name = string(value.toolName);
|
|
665
|
+
if (id && name) {
|
|
666
|
+
toolCalls.push({ id, name, input: value.args });
|
|
667
|
+
onEvent?.({ type: 'tool-call', id, tool: name, input: value.args });
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
else if (value.type === 'tool_execution_end') {
|
|
671
|
+
const id = string(value.toolCallId);
|
|
672
|
+
if (id) {
|
|
673
|
+
onEvent?.({
|
|
674
|
+
type: 'tool-result',
|
|
675
|
+
toolCallId: id,
|
|
676
|
+
result: contentText(isRecord(value.result) ? value.result.content : undefined) ?? '',
|
|
677
|
+
isError: value.isError === true,
|
|
678
|
+
});
|
|
679
|
+
emitImages(isRecord(value.result) ? value.result.content : undefined, onEvent);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
else if (value.type === 'extension_error') {
|
|
683
|
+
onEvent?.({ type: 'note', message: string(value.error) ?? string(value.message) ?? 'omp extension error' });
|
|
684
|
+
}
|
|
685
|
+
else if (value.type === 'notice') {
|
|
686
|
+
const message = string(value.message);
|
|
687
|
+
if (message)
|
|
688
|
+
onEvent?.({ type: 'note', message: `omp: ${message}` });
|
|
689
|
+
}
|
|
690
|
+
if (endsTurn) {
|
|
691
|
+
flushText();
|
|
692
|
+
emitLatchedProviderError();
|
|
693
|
+
pendingMarkerAsk = parseAskMarker(textChunks.slice(turnTextStart).join('\n')) !== null;
|
|
694
|
+
agentInputReady = true;
|
|
695
|
+
// Input queued before this turn began was processed by it, even without a user
|
|
696
|
+
// message_start (#505) — unless the turn failed and may never have reached the model.
|
|
697
|
+
const read = turnFailed ? [] : [...carried].flatMap(id => submissions.consume(id));
|
|
698
|
+
turnFailed = false;
|
|
699
|
+
carried = new Set();
|
|
700
|
+
if (read.length)
|
|
701
|
+
opts.onAgentInputConsumed?.(read);
|
|
702
|
+
onEvent?.({ type: 'turn-end' });
|
|
703
|
+
scheduleAutoEnd();
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
catch (error) {
|
|
708
|
+
if (child.exitCode === null && child.signalCode === null) {
|
|
709
|
+
// A live child whose stream failed is not a refusal: no respawn follows, so nothing
|
|
710
|
+
// may still be accepted into the outbox.
|
|
711
|
+
open = false;
|
|
712
|
+
// The run is reported failed, so the CLI must not keep running tools in the worktree:
|
|
713
|
+
// SIGTERM now, SIGKILL after the grace period (a timer `finally` does not clear).
|
|
714
|
+
terminatedByCezar = true;
|
|
715
|
+
signalSession(child, 'SIGTERM');
|
|
716
|
+
const reap = setTimeout(() => {
|
|
717
|
+
if (child.exitCode === null && child.signalCode === null)
|
|
718
|
+
signalSession(child, 'SIGKILL');
|
|
719
|
+
}, killGraceMs);
|
|
720
|
+
reap.unref?.();
|
|
721
|
+
throw error;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
finally {
|
|
725
|
+
if (deadline)
|
|
726
|
+
clearTimeout(deadline);
|
|
727
|
+
if (autoEndTimer)
|
|
728
|
+
clearTimeout(autoEndTimer);
|
|
729
|
+
if (killTimer)
|
|
730
|
+
clearTimeout(killTimer);
|
|
731
|
+
// A refused spawn may still be respawned: stay open so input accepted meanwhile is kept
|
|
732
|
+
// for the new child instead of being refused in the gap.
|
|
733
|
+
if (!retryPossible())
|
|
734
|
+
open = false;
|
|
735
|
+
rejectAgentAck();
|
|
736
|
+
}
|
|
737
|
+
flushText();
|
|
738
|
+
emitLatchedProviderError();
|
|
739
|
+
emitLatchedUiProviderError();
|
|
740
|
+
const exitCode = await waitForExit(child);
|
|
741
|
+
if (interruptKillTimer)
|
|
742
|
+
clearTimeout(interruptKillTimer);
|
|
743
|
+
// Ruling 13: exit 2 before any frame, naming tools the user's OMP settings disabled. Respawn
|
|
744
|
+
// once without them: the list only narrows, so this never widens what the step granted.
|
|
745
|
+
// Ruling 20 widens the same respawn to MCP names OMP did not register (a Claude spelling
|
|
746
|
+
// it cannot match, or a server slower than OMP's discovery window).
|
|
747
|
+
const canRetry = exitCode === 2 && !spawnError && retryPossible();
|
|
748
|
+
// Ruling 22: OMP rejects unknown flags while parsing, before it validates --tools, so this
|
|
749
|
+
// refusal comes first and a tool refusal may still follow on the respawn.
|
|
750
|
+
const droppedDirs = canRetry && !flagsRetried ? refusedOmpAddDir(stderr.join(''), spawnArgs) : null;
|
|
751
|
+
const refusal = canRetry && !droppedDirs && !toolsRetried
|
|
752
|
+
? refusedOmpTools(stderr.join(''), ompToolList(spawnArgs))
|
|
753
|
+
: null;
|
|
754
|
+
if (droppedDirs || refusal) {
|
|
755
|
+
stderr.length = 0;
|
|
756
|
+
if (droppedDirs) {
|
|
757
|
+
flagsRetried = true;
|
|
758
|
+
omitAddDir = true;
|
|
759
|
+
onEvent?.({
|
|
760
|
+
type: 'note',
|
|
761
|
+
message: `omp: this omp CLI does not accept --add-dir (update OMP); ran without the additional directories: ${droppedDirs.join(', ')}`,
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
if (refusal) {
|
|
765
|
+
toolsRetried = true;
|
|
766
|
+
refusedTools = [...refusal.disabled, ...refusal.mcp];
|
|
767
|
+
const dropped = [
|
|
768
|
+
...(refusal.disabled.length > 0 ? [`tools disabled by your OMP settings were dropped: ${refusal.disabled.join(', ')}`] : []),
|
|
769
|
+
...(refusal.mcp.length > 0 ? [`MCP tools OMP has not registered were dropped: ${refusal.mcp.join(', ')}`] : []),
|
|
770
|
+
];
|
|
771
|
+
onEvent?.({ type: 'note', message: `omp: ${dropped.join('; ')}` });
|
|
772
|
+
}
|
|
773
|
+
// Session state (mapper, held v2 events, ack sets) follows what was accepted, not the
|
|
774
|
+
// child, and the outbox still holds every command accepted so far: the new child is
|
|
775
|
+
// sent exactly those, once, when it speaks.
|
|
776
|
+
spawnArgs = buildOmpArgs(spec, refusedTools, { omitAddDir });
|
|
777
|
+
child = spawnOmp(spawnArgs);
|
|
778
|
+
if (child.pid !== undefined)
|
|
779
|
+
opts.onPidChange?.(child.pid);
|
|
780
|
+
deadline = armDeadline();
|
|
781
|
+
return runOmp();
|
|
782
|
+
}
|
|
783
|
+
open = false;
|
|
784
|
+
releaseUiHold();
|
|
785
|
+
if (spawnError)
|
|
786
|
+
throw spawnError;
|
|
787
|
+
if (timedOut) {
|
|
788
|
+
const message = `omp CLI timed out after ${Math.round((limitMs / 60_000) * 10) / 10}m and was killed`;
|
|
789
|
+
onEvent?.({ type: 'error', message });
|
|
790
|
+
onEvent?.({ type: 'done' });
|
|
791
|
+
return { text: textChunks.join('\n').trim(), toolCalls, tokensUsed, sessionId };
|
|
792
|
+
}
|
|
793
|
+
// A signal we sent is teardown, not a second agent failure (#73).
|
|
794
|
+
if (terminatedByCezar && (isSignalTerminationExit(exitCode) || (exitCode === null && child.signalCode !== null))) {
|
|
795
|
+
onEvent?.({
|
|
796
|
+
type: 'note',
|
|
797
|
+
message: `omp CLI did not exit on its own after close; terminated by cezar (${exitCode === null ? child.signalCode : `code ${exitCode}`})`,
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
else if (exitCode === null && child.signalCode !== null && !terminatedByCezar) {
|
|
801
|
+
// Killed from outside (OOM killer, a stray kill): a truncated run, never a success.
|
|
802
|
+
const message = `omp CLI was killed by signal ${child.signalCode}`;
|
|
803
|
+
onEvent?.({ type: 'error', message });
|
|
804
|
+
throw new Error(message);
|
|
805
|
+
}
|
|
806
|
+
else if (exitCode !== 0 && exitCode !== null) {
|
|
807
|
+
const diagnostic = stderr.join('');
|
|
808
|
+
if (diagnostic.trim())
|
|
809
|
+
onEvent?.({ type: 'note', message: `omp CLI stderr:\n${diagnostic}` });
|
|
810
|
+
const detail = ompStderrDetail(diagnostic);
|
|
811
|
+
const message = `omp CLI exited with code ${exitCode}${detail ? ` — ${detail}` : ''}`;
|
|
812
|
+
onEvent?.({ type: 'error', message });
|
|
813
|
+
throw new Error(message);
|
|
814
|
+
}
|
|
815
|
+
if (ompUi.turnId)
|
|
816
|
+
onEvent?.({ type: 'note', message: 'omp RPC session ended before session_settled' });
|
|
817
|
+
if (tokensUsed === 0)
|
|
818
|
+
onEvent?.({ type: 'note', message: 'token usage not reported by omp CLI' });
|
|
819
|
+
emitUiEvent({ type: 'session.ended', reason: ompUi.stopReason });
|
|
820
|
+
onEvent?.({ type: 'done' });
|
|
821
|
+
return { text: textChunks.join('\n').trim(), toolCalls, tokensUsed, sessionId };
|
|
822
|
+
})();
|
|
823
|
+
const session = {
|
|
824
|
+
result,
|
|
825
|
+
sendMessage,
|
|
826
|
+
sendAgentMessage: (content, inputIds = []) => {
|
|
827
|
+
// #505: a running turn is steered; only a pending CEZ:ASK or an unacknowledged
|
|
828
|
+
// prompt refuses.
|
|
829
|
+
if (open && !pendingMarkerAsk && (agentAck || humanAcks.size)) {
|
|
830
|
+
refusedForAck = true;
|
|
831
|
+
return false;
|
|
832
|
+
}
|
|
833
|
+
if (!open || pendingMarkerAsk)
|
|
834
|
+
return false;
|
|
835
|
+
const id = `cezar-agent-${++promptSerial}`;
|
|
836
|
+
let resolve, reject;
|
|
837
|
+
const acknowledged = new Promise((yes, no) => { resolve = yes; reject = no; });
|
|
838
|
+
agentAck = { id, resolve, reject };
|
|
839
|
+
if (!sendMessage(content, id, inputIds)) {
|
|
840
|
+
agentAck = undefined;
|
|
841
|
+
return false;
|
|
842
|
+
}
|
|
843
|
+
return acknowledged;
|
|
844
|
+
},
|
|
845
|
+
discardQueuedMessages: () => undefined,
|
|
846
|
+
holdsHumanInput: () => humanAcks.size > 0,
|
|
847
|
+
heldHumanInputCount: () => humanAcks.size,
|
|
848
|
+
end,
|
|
849
|
+
interrupt,
|
|
850
|
+
// The live child: a Ruling 13 respawn replaces it (`onPidChange` tells the caller who read it).
|
|
851
|
+
get pid() {
|
|
852
|
+
return child.pid;
|
|
853
|
+
},
|
|
854
|
+
get open() {
|
|
855
|
+
return open;
|
|
856
|
+
},
|
|
857
|
+
};
|
|
858
|
+
this.lastSession = session;
|
|
859
|
+
return session;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* The stderr line an exit error carries. OMP's "no usable model" exit prints its reason first
|
|
864
|
+
* and setup hints after it, which the generic last-lines summary would keep instead.
|
|
865
|
+
*/
|
|
866
|
+
function ompStderrDetail(stderr) {
|
|
867
|
+
const first = stderr.split(/\r?\n/).find(line => line.trim())?.trim();
|
|
868
|
+
if (first?.startsWith('No models available'))
|
|
869
|
+
return first;
|
|
870
|
+
return summarizeRunnerStderr(stderr);
|
|
871
|
+
}
|
|
872
|
+
/** The `--tools` list in an omp argv; empty when none was passed. */
|
|
873
|
+
function ompToolList(args) {
|
|
874
|
+
const at = args.indexOf('--tools');
|
|
875
|
+
return at >= 0 ? (args[at + 1] ?? '').split(',').filter(Boolean) : [];
|
|
876
|
+
}
|
|
877
|
+
/**
|
|
878
|
+
* What OMP's startup refusal (v18.4.11 `emt()`, exit 2) lets a respawn drop, limited to names this
|
|
879
|
+
* spawn passed so a retry only narrows. One error carries up to two lists:
|
|
880
|
+
* `Built-in tool(s) unavailable in this session: a, b.` (tools the user's settings disable, Ruling 13)
|
|
881
|
+
* and `Unknown tool(s) in --tools: a, b.` (Ruling 20: droppable only when every name is an
|
|
882
|
+
* `mcp__` name this spawn passed; any other unknown name stays fatal, so nothing is retried).
|
|
883
|
+
* `null` when there is nothing to drop.
|
|
884
|
+
*/
|
|
885
|
+
function refusedOmpTools(stderr, passed) {
|
|
886
|
+
const listed = (pattern) => {
|
|
887
|
+
const match = pattern.exec(stderr);
|
|
888
|
+
return match ? (match[1] ?? '').replace(/\.\s*$/, '').split(/[\s,]+/).filter(Boolean) : undefined;
|
|
889
|
+
};
|
|
890
|
+
const unknown = listed(/Unknown tools? in --tools:[ \t]*([^\r\n]*)/) ?? [];
|
|
891
|
+
if (unknown.some(name => !name.startsWith(MCP_PREFIX) || !passed.includes(name)))
|
|
892
|
+
return null;
|
|
893
|
+
const disabled = (listed(/Built-in tools? unavailable in this session:[ \t]*([^\r\n]*)/) ?? [])
|
|
894
|
+
.filter(name => passed.includes(name));
|
|
895
|
+
return disabled.length + unknown.length > 0 ? { disabled, mcp: unknown } : null;
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
898
|
+
* Ruling 22: the directories to drop when this omp build refused `--add-dir` (`Error: unknown
|
|
899
|
+
* flag(s): …`, exit 2, before any session work). Only `--add-dir` is droppable: every other
|
|
900
|
+
* unknown flag carries something the step needs (model, prompt, resume, tools), so the refusal
|
|
901
|
+
* stays fatal and nothing is retried. `null` when there is nothing to drop.
|
|
902
|
+
*/
|
|
903
|
+
function refusedOmpAddDir(stderr, args) {
|
|
904
|
+
const match = /unknown flags?:[ \t]*([^\r\n]*)/i.exec(stderr);
|
|
905
|
+
if (!match)
|
|
906
|
+
return null;
|
|
907
|
+
const unknown = (match[1] ?? '').replace(/\.\s*$/, '').split(/[\s,]+/).filter(Boolean);
|
|
908
|
+
if (unknown.length === 0 || unknown.some(flag => flag !== '--add-dir'))
|
|
909
|
+
return null;
|
|
910
|
+
const dirs = args.flatMap((arg, i) => (arg === '--add-dir' ? [args[i + 1] ?? ''] : [])).filter(Boolean);
|
|
911
|
+
return dirs.length > 0 ? dirs : null;
|
|
912
|
+
}
|
|
913
|
+
/** Same as pi-runner.ts `toPiPrompt`. */
|
|
914
|
+
function toOmpPrompt(content) {
|
|
915
|
+
const text = [];
|
|
916
|
+
const images = [];
|
|
917
|
+
for (const block of content) {
|
|
918
|
+
if (block.type === 'text')
|
|
919
|
+
text.push(block.text);
|
|
920
|
+
else
|
|
921
|
+
images.push({ type: 'image', data: block.source.data, mimeType: block.source.media_type });
|
|
922
|
+
}
|
|
923
|
+
return { message: text.join('\n'), images };
|
|
924
|
+
}
|
|
925
|
+
/** Same as pi-runner.ts `emitImages`. */
|
|
926
|
+
function emitImages(value, onEvent) {
|
|
927
|
+
if (!Array.isArray(value))
|
|
928
|
+
return;
|
|
929
|
+
for (const part of value) {
|
|
930
|
+
if (isRecord(part) && part.type === 'image') {
|
|
931
|
+
const data = string(part.data);
|
|
932
|
+
const mediaType = string(part.mimeType);
|
|
933
|
+
if (data && mediaType)
|
|
934
|
+
onEvent?.({ type: 'image', data, mediaType });
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
/** Same as pi-runner.ts `piMessageText`. */
|
|
939
|
+
function ompMessageText(message) {
|
|
940
|
+
if (typeof message.content === 'string')
|
|
941
|
+
return message.content;
|
|
942
|
+
return Array.isArray(message.content)
|
|
943
|
+
? message.content.flatMap(part => isRecord(part) && part.type === 'text' && typeof part.text === 'string' ? [part.text] : []).join('\n')
|
|
944
|
+
: '';
|
|
945
|
+
}
|
|
946
|
+
/** Same as pi-runner.ts `contentText`. */
|
|
947
|
+
function contentText(value) {
|
|
948
|
+
if (typeof value === 'string')
|
|
949
|
+
return value;
|
|
950
|
+
if (!Array.isArray(value))
|
|
951
|
+
return undefined;
|
|
952
|
+
const text = value
|
|
953
|
+
.map((part) => (isRecord(part) && part.type === 'text' ? string(part.text) : undefined))
|
|
954
|
+
.filter((part) => part !== undefined);
|
|
955
|
+
return text.length > 0 ? text.join('\n') : undefined;
|
|
956
|
+
}
|
|
957
|
+
/** OMP's failure response carries `error` as a string (rpc-mode.ts, v18.4.11). */
|
|
958
|
+
function rpcError(value) {
|
|
959
|
+
if (typeof value.error === 'string')
|
|
960
|
+
return value.error;
|
|
961
|
+
const error = isRecord(value.error) ? value.error : undefined;
|
|
962
|
+
return string(error?.message) ?? string(value.message) ?? `omp RPC command ${string(value.command) ?? 'unknown'} failed`;
|
|
963
|
+
}
|
|
964
|
+
function waitForExit(child) {
|
|
965
|
+
if (child.exitCode !== null || child.signalCode !== null)
|
|
966
|
+
return Promise.resolve(child.exitCode);
|
|
967
|
+
return new Promise((resolve) => child.once('close', resolve));
|
|
968
|
+
}
|
|
969
|
+
function wrapSpawnError(error, bin) {
|
|
970
|
+
if (error.code === 'ENOENT') {
|
|
971
|
+
return new Error(`\`${bin}\` not found on PATH: install OMP (https://omp.sh) and run \`omp login\``);
|
|
972
|
+
}
|
|
973
|
+
return error;
|
|
974
|
+
}
|
|
975
|
+
/** Path to the bundled mock (`scripts/mock-omp-rpc.mjs`), for CEZ_DRY_RUN=1; as pi-runner.ts `mockPiPath`. */
|
|
976
|
+
function mockOmpPath() {
|
|
977
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
978
|
+
// here = <pkg>/dist/core (built) or <pkg>/src/core (tsx dev).
|
|
979
|
+
return resolvePath(here, '..', '..', 'scripts', 'mock-omp-rpc.mjs');
|
|
980
|
+
}
|
|
981
|
+
function truncate(value, max = 200) {
|
|
982
|
+
return value.length > max ? `${value.slice(0, max)}…` : value;
|
|
983
|
+
}
|
|
984
|
+
function isRecord(value) {
|
|
985
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
986
|
+
}
|
|
987
|
+
function string(value) {
|
|
988
|
+
return typeof value === 'string' ? value : undefined;
|
|
989
|
+
}
|
|
990
|
+
//# sourceMappingURL=omp-runner.js.map
|