@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,882 @@
|
|
|
1
|
+
import { spawn as nodeSpawn } from 'node:child_process';
|
|
2
|
+
import { request as httpRequest } from 'node:http';
|
|
3
|
+
import { prependSystemPrompt, trackChildExit } from './agent-runner.js';
|
|
4
|
+
import { buildChildEnv } from './agent-env.js';
|
|
5
|
+
import { parseAskRequest } from './ask.js';
|
|
6
|
+
import { AUTO_END_DELAY_MS, DEFAULT_RUN_TIMEOUT_MS } from './claude-cli-runner.js';
|
|
7
|
+
import { parseModelIdentity } from './model-identity.js';
|
|
8
|
+
import { V1TextCoalescer } from './v1-text-coalescer.js';
|
|
9
|
+
import { createOpencodeUiState, mapOpencodeEvent, opencodeSessionStarted, opencodeTurnStarted, } from './opencode-ui-mapper.js';
|
|
10
|
+
const SERVER_START_TIMEOUT_MS = 30_000;
|
|
11
|
+
/** Grace between the teardown SIGTERM and the SIGKILL that follows it. */
|
|
12
|
+
export const KILL_GRACE_MS = 4_000;
|
|
13
|
+
/**
|
|
14
|
+
* `AgentRunner` over `opencode serve` — a headless HTTP server (the same one
|
|
15
|
+
* the opencode TUI talks to) with an SSE event stream. One server per session,
|
|
16
|
+
* bound to the run's `cwd` (worktree), gives OpenCode the same multi-turn shape
|
|
17
|
+
* as the Claude runner: each `sendMessage` posts another prompt to the same
|
|
18
|
+
* session (history is kept server-side), `session/abort` cancels, and reusing
|
|
19
|
+
* the session id resumes for "Continue".
|
|
20
|
+
*
|
|
21
|
+
* Auth = the host's opencode config/logins. The agent runs autonomously
|
|
22
|
+
* (auto-approved permissions); OpenCode has no per-tool allowlist, so
|
|
23
|
+
* `spec.allowedTools` is ignored. `spec.model` is `provider/model`.
|
|
24
|
+
*/
|
|
25
|
+
export class OpencodeServerRunner {
|
|
26
|
+
backend = 'opencode';
|
|
27
|
+
bin;
|
|
28
|
+
timeoutMs;
|
|
29
|
+
lastSession = null;
|
|
30
|
+
constructor(opts = {}) {
|
|
31
|
+
this.bin = opts.bin ?? process.env.CEZ_OPENCODE_BIN ?? 'opencode';
|
|
32
|
+
this.timeoutMs = opts.timeoutMs ?? DEFAULT_RUN_TIMEOUT_MS;
|
|
33
|
+
}
|
|
34
|
+
run(spec, onEvent) {
|
|
35
|
+
return this.startSession(spec, onEvent, { autoEndAfterFirstTurn: true }).result;
|
|
36
|
+
}
|
|
37
|
+
async interrupt() {
|
|
38
|
+
this.lastSession?.interrupt();
|
|
39
|
+
}
|
|
40
|
+
startSession(spec, onEvent, opts = {}) {
|
|
41
|
+
const session = new OpencodeSession(this.bin, this.timeoutMs, spec, onEvent, opts);
|
|
42
|
+
this.lastSession = session;
|
|
43
|
+
return session;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** One live `opencode serve` process driving a single session. */
|
|
47
|
+
class OpencodeSession {
|
|
48
|
+
bin;
|
|
49
|
+
spec;
|
|
50
|
+
onEvent;
|
|
51
|
+
opts;
|
|
52
|
+
result;
|
|
53
|
+
child;
|
|
54
|
+
/** "Has the server actually terminated?" — never `child.killed`, which only
|
|
55
|
+
* reports delivery and would disarm the escalation (#844/#858). */
|
|
56
|
+
hasExited;
|
|
57
|
+
serverOpen = true;
|
|
58
|
+
baseUrl;
|
|
59
|
+
sessionId;
|
|
60
|
+
ready;
|
|
61
|
+
resolveExit;
|
|
62
|
+
exited;
|
|
63
|
+
sse = new AbortController();
|
|
64
|
+
toolCalls = [];
|
|
65
|
+
textChunks = [];
|
|
66
|
+
/** Per text-part cursor so only newly-appended text is buffered (deltas). */
|
|
67
|
+
textSeen = new Map();
|
|
68
|
+
/** Streamed part deltas buffered per part — v1 `text` is emitted once per
|
|
69
|
+
* finished part (claude parity: one event per complete block), never per
|
|
70
|
+
* delta, so the persisted transcript and the headless CLI get whole
|
|
71
|
+
* paragraphs. Streaming display rides protocol v2's `item.delta`. */
|
|
72
|
+
textCoalescer = new V1TextCoalescer((text) => {
|
|
73
|
+
this.textChunks.push(text);
|
|
74
|
+
this.emit({ type: 'text', text });
|
|
75
|
+
});
|
|
76
|
+
toolsSeen = new Set();
|
|
77
|
+
/** messageID → role. Parts carry no role; only assistant parts are surfaced
|
|
78
|
+
* (the user's own message also streams as parts over the same SSE feed). */
|
|
79
|
+
msgRole = new Map();
|
|
80
|
+
tokensUsed = 0;
|
|
81
|
+
lastCost = 0;
|
|
82
|
+
/** A prompt was posted and its `session.idle` has not arrived yet. */
|
|
83
|
+
turnActive = false;
|
|
84
|
+
/** `finishTurn` ran for the current turn — repeats and stray idles no-op. */
|
|
85
|
+
turnEnded = false;
|
|
86
|
+
/** Monotonic identity used to reject async work completed by an older turn. */
|
|
87
|
+
turnSerial = 0;
|
|
88
|
+
/** Settles when the open turn finishes; re-armed at each turn start. The
|
|
89
|
+
* gate `prompt` waits on so turns never overlap (see there). */
|
|
90
|
+
turnFinished = Promise.resolve();
|
|
91
|
+
turnFinishedResolve = () => { };
|
|
92
|
+
/** Protocol v2 emission — additive alongside v1 (`onEvent` keeps flowing
|
|
93
|
+
* byte-identical); the channel is `opts.onUiEvent` (RunManager wiring
|
|
94
|
+
* lands in R2 step 2.1). Both streams take their turn-end from the wire
|
|
95
|
+
* `session.idle` (v1 since #4 — see `finishTurn`). */
|
|
96
|
+
uiState = createOpencodeUiState();
|
|
97
|
+
/** Question tool parts are snapshots. Once capture starts for an identity,
|
|
98
|
+
* every later state snapshot for that same part/call is already handled. */
|
|
99
|
+
handledQuestionParts = new Set();
|
|
100
|
+
pendingQuestion;
|
|
101
|
+
questionCapture;
|
|
102
|
+
questionReply;
|
|
103
|
+
/** User text submitted while a question reply POST is unsettled. It is not
|
|
104
|
+
* another answer; deliver it as ordinary prompts only after reply success. */
|
|
105
|
+
queuedQuestionMessages = [];
|
|
106
|
+
autoEndTimer;
|
|
107
|
+
spawnFailed = null;
|
|
108
|
+
timedOut = false;
|
|
109
|
+
/** One teardown per session — see `terminate()`. */
|
|
110
|
+
signalled = false;
|
|
111
|
+
constructor(bin, timeoutMs, spec, onEvent, opts) {
|
|
112
|
+
this.bin = bin;
|
|
113
|
+
this.spec = spec;
|
|
114
|
+
this.onEvent = onEvent;
|
|
115
|
+
this.opts = opts;
|
|
116
|
+
// Random high port; the actual bound URL is read back from stdout.
|
|
117
|
+
const port = 40000 + Math.floor(Math.random() * 20000);
|
|
118
|
+
try {
|
|
119
|
+
this.child = nodeSpawn(bin, ['serve', '--hostname', '127.0.0.1', '--port', String(port)], {
|
|
120
|
+
cwd: spec.cwd,
|
|
121
|
+
env: buildChildEnv({ backend: 'opencode', extraEnv: spec.env }),
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
throw wrapSpawnError(err, bin);
|
|
126
|
+
}
|
|
127
|
+
this.hasExited = trackChildExit(this.child);
|
|
128
|
+
this.child.on('error', (err) => {
|
|
129
|
+
this.spawnFailed = wrapSpawnError(err, bin);
|
|
130
|
+
});
|
|
131
|
+
this.exited = new Promise((resolve) => {
|
|
132
|
+
this.resolveExit = resolve;
|
|
133
|
+
});
|
|
134
|
+
this.child.once('exit', () => this.resolveExit());
|
|
135
|
+
this.child.once('close', () => this.resolveExit());
|
|
136
|
+
const stderrChunks = [];
|
|
137
|
+
this.child.stderr.setEncoding('utf8');
|
|
138
|
+
this.child.stderr.on('data', (chunk) => stderrChunks.push(chunk));
|
|
139
|
+
// The server prints its URL on stdout once listening.
|
|
140
|
+
const urlReady = this.waitForServerUrl(port);
|
|
141
|
+
const limitMs = spec.timeoutMs ?? timeoutMs;
|
|
142
|
+
let deadline;
|
|
143
|
+
if (limitMs > 0) {
|
|
144
|
+
deadline = setTimeout(() => {
|
|
145
|
+
this.timedOut = true;
|
|
146
|
+
this.interrupt();
|
|
147
|
+
}, limitMs);
|
|
148
|
+
deadline.unref?.();
|
|
149
|
+
}
|
|
150
|
+
this.ready = (async () => {
|
|
151
|
+
this.baseUrl = await urlReady;
|
|
152
|
+
await this.bootstrap();
|
|
153
|
+
})();
|
|
154
|
+
this.result = (async () => {
|
|
155
|
+
try {
|
|
156
|
+
await this.ready;
|
|
157
|
+
// Live for the whole session; the SSE loop runs until end()/interrupt.
|
|
158
|
+
await this.exited;
|
|
159
|
+
}
|
|
160
|
+
catch (err) {
|
|
161
|
+
if (!this.timedOut) {
|
|
162
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
163
|
+
this.emit({ type: 'error', message: `opencode: ${message}` });
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
finally {
|
|
167
|
+
if (deadline)
|
|
168
|
+
clearTimeout(deadline);
|
|
169
|
+
if (this.autoEndTimer)
|
|
170
|
+
clearTimeout(this.autoEndTimer);
|
|
171
|
+
this.sse.abort();
|
|
172
|
+
this.serverOpen = false;
|
|
173
|
+
this.terminate();
|
|
174
|
+
}
|
|
175
|
+
await this.exited;
|
|
176
|
+
if (this.spawnFailed)
|
|
177
|
+
throw this.spawnFailed;
|
|
178
|
+
// Timeout/interrupt can cut the SSE feed before its `session.idle` —
|
|
179
|
+
// close the turn (idempotent) so consumers still see the v1 boundary,
|
|
180
|
+
// and recover prose buffered mid-part.
|
|
181
|
+
this.finishTurn();
|
|
182
|
+
this.textCoalescer.flush();
|
|
183
|
+
// Chunks are whole blocks now (one per finished part), so newline-join
|
|
184
|
+
// like the other runners, not the old delta concatenation.
|
|
185
|
+
const text = this.textChunks.join('\n').trim();
|
|
186
|
+
const base = {
|
|
187
|
+
text,
|
|
188
|
+
toolCalls: this.toolCalls,
|
|
189
|
+
tokensUsed: this.tokensUsed,
|
|
190
|
+
sessionId: this.sessionId ?? spec.sessionId,
|
|
191
|
+
};
|
|
192
|
+
if (this.timedOut) {
|
|
193
|
+
const mins = Math.round((limitMs / 60_000) * 10) / 10;
|
|
194
|
+
this.emit({ type: 'error', message: `opencode timed out after ${mins}m and was killed` });
|
|
195
|
+
}
|
|
196
|
+
this.emit({ type: 'done' });
|
|
197
|
+
return base;
|
|
198
|
+
})();
|
|
199
|
+
}
|
|
200
|
+
get open() {
|
|
201
|
+
return this.serverOpen;
|
|
202
|
+
}
|
|
203
|
+
get pid() {
|
|
204
|
+
return this.child.pid;
|
|
205
|
+
}
|
|
206
|
+
sendMessage(content) {
|
|
207
|
+
if (!this.serverOpen)
|
|
208
|
+
return false;
|
|
209
|
+
this.cancelAutoEnd();
|
|
210
|
+
const text = textOf(content);
|
|
211
|
+
if (!text)
|
|
212
|
+
return true;
|
|
213
|
+
if (this.questionReply) {
|
|
214
|
+
this.queuedQuestionMessages.push(text);
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
if (this.pendingQuestion) {
|
|
218
|
+
const pending = this.pendingQuestion;
|
|
219
|
+
const reply = this.replyQuestion(pending, text);
|
|
220
|
+
this.questionReply = reply;
|
|
221
|
+
const clearReply = () => {
|
|
222
|
+
if (this.questionReply === reply)
|
|
223
|
+
this.questionReply = undefined;
|
|
224
|
+
};
|
|
225
|
+
void reply.then(clearReply, clearReply);
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
// `prompt` already emitted the note and closed the turn before rethrowing,
|
|
229
|
+
// and a rejected `ready` already failed the session on the result path —
|
|
230
|
+
// there is nothing left to report here.
|
|
231
|
+
this.deliverPrompt(text);
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
deliverPrompt(text) {
|
|
235
|
+
void this.ready.then(() => this.prompt(text)).catch(() => undefined);
|
|
236
|
+
}
|
|
237
|
+
cancelAutoEnd() {
|
|
238
|
+
if (!this.autoEndTimer)
|
|
239
|
+
return;
|
|
240
|
+
clearTimeout(this.autoEndTimer);
|
|
241
|
+
this.autoEndTimer = undefined;
|
|
242
|
+
}
|
|
243
|
+
scheduleAutoEnd() {
|
|
244
|
+
if (!this.opts.autoEndAfterFirstTurn || !this.serverOpen || this.autoEndTimer)
|
|
245
|
+
return;
|
|
246
|
+
this.autoEndTimer = setTimeout(() => this.end(), AUTO_END_DELAY_MS);
|
|
247
|
+
this.autoEndTimer.unref?.();
|
|
248
|
+
}
|
|
249
|
+
end() {
|
|
250
|
+
if (!this.serverOpen)
|
|
251
|
+
return;
|
|
252
|
+
this.serverOpen = false;
|
|
253
|
+
this.sse.abort();
|
|
254
|
+
this.terminate();
|
|
255
|
+
}
|
|
256
|
+
interrupt() {
|
|
257
|
+
this.serverOpen = false;
|
|
258
|
+
if (this.baseUrl && this.sessionId) {
|
|
259
|
+
void this.http('POST', `/session/${this.sessionId}/abort`, undefined).catch(() => undefined);
|
|
260
|
+
}
|
|
261
|
+
this.sse.abort();
|
|
262
|
+
this.terminate();
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* The one place either signal is sent: SIGTERM now, SIGKILL once the grace
|
|
266
|
+
* window elapses.
|
|
267
|
+
*
|
|
268
|
+
* Both steps gate on `hasExited()`, never on `child.killed` — the latter
|
|
269
|
+
* flips the moment SIGTERM is *delivered*, so the old nested
|
|
270
|
+
* `exitCode == null && !killed` guard disarmed the escalation for exactly the
|
|
271
|
+
* server it was written for: one that installs its own SIGTERM handler stayed
|
|
272
|
+
* alive with `killed = true` and `exitCode === null`, outliving the whole
|
|
273
|
+
* window (#858, the same defect #844 fixed for the other two backends). Every
|
|
274
|
+
* caller here is followed by `await this.exited`, so a server that survived
|
|
275
|
+
* SIGTERM did not just leak — it hung the session's result forever.
|
|
276
|
+
*
|
|
277
|
+
* One teardown per session: all three call sites can run for the same session
|
|
278
|
+
* (`interrupt()` on the deadline, then the result promise's `finally`), and
|
|
279
|
+
* once SIGTERM is out with SIGKILL armed there is nothing a second pass adds.
|
|
280
|
+
* The old `!child.killed` test deduplicated this as a side effect of being
|
|
281
|
+
* wrong; `signalled` keeps that property on purpose.
|
|
282
|
+
*/
|
|
283
|
+
terminate() {
|
|
284
|
+
if (this.signalled || this.hasExited())
|
|
285
|
+
return;
|
|
286
|
+
this.signalled = true;
|
|
287
|
+
this.child.kill('SIGTERM');
|
|
288
|
+
setTimeout(() => {
|
|
289
|
+
if (this.hasExited())
|
|
290
|
+
return;
|
|
291
|
+
this.child.kill('SIGKILL');
|
|
292
|
+
}, KILL_GRACE_MS).unref?.();
|
|
293
|
+
}
|
|
294
|
+
// ---- server lifecycle ---------------------------------------------------
|
|
295
|
+
waitForServerUrl(fallbackPort) {
|
|
296
|
+
return new Promise((resolve, reject) => {
|
|
297
|
+
let buffer = '';
|
|
298
|
+
const timer = setTimeout(() => {
|
|
299
|
+
cleanup();
|
|
300
|
+
// Nothing parsed — try the port we asked for.
|
|
301
|
+
resolve(`http://127.0.0.1:${fallbackPort}`);
|
|
302
|
+
}, SERVER_START_TIMEOUT_MS);
|
|
303
|
+
timer.unref?.();
|
|
304
|
+
const onData = (chunk) => {
|
|
305
|
+
buffer += chunk;
|
|
306
|
+
const m = /https?:\/\/[\d.]+:\d+/.exec(buffer);
|
|
307
|
+
if (m) {
|
|
308
|
+
cleanup();
|
|
309
|
+
resolve(m[0]);
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
const onExit = () => {
|
|
313
|
+
cleanup();
|
|
314
|
+
reject(new Error('opencode serve exited before it started listening'));
|
|
315
|
+
};
|
|
316
|
+
const cleanup = () => {
|
|
317
|
+
clearTimeout(timer);
|
|
318
|
+
this.child.stdout.off('data', onData);
|
|
319
|
+
this.child.off('exit', onExit);
|
|
320
|
+
};
|
|
321
|
+
this.child.stdout.setEncoding('utf8');
|
|
322
|
+
this.child.stdout.on('data', onData);
|
|
323
|
+
this.child.once('exit', onExit);
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
async bootstrap() {
|
|
327
|
+
const created = await this.http('POST', '/session', { title: 'cezar task' });
|
|
328
|
+
this.sessionId = stringField(created, 'id');
|
|
329
|
+
if (!this.sessionId)
|
|
330
|
+
throw new Error('opencode did not return a session id');
|
|
331
|
+
this.emit({ type: 'session', sessionId: this.sessionId });
|
|
332
|
+
const sessionId = this.sessionId;
|
|
333
|
+
this.emitUi((state) => opencodeSessionStarted(sessionId, state));
|
|
334
|
+
// The SSE subscription must be LIVE before the first prompt posts —
|
|
335
|
+
// events the server emits while the POST is in flight would otherwise be
|
|
336
|
+
// lost (a race this await closes; the bundled mock made it visible).
|
|
337
|
+
await this.consumeEvents();
|
|
338
|
+
const first = prependSystemPrompt(this.spec.systemPrompt, this.spec.userPrompt);
|
|
339
|
+
await this.prompt(first);
|
|
340
|
+
}
|
|
341
|
+
async prompt(text) {
|
|
342
|
+
// One turn at a time. `prompt_async` acknowledges before the turn runs,
|
|
343
|
+
// so `ready` no longer serializes prompts the way the long-poll did — a
|
|
344
|
+
// follow-up posted mid-turn would share the turnActive/turnEnded pair
|
|
345
|
+
// with the running turn and let that turn's idle close this one. Waiters
|
|
346
|
+
// resume in FIFO order; a teardown (`finishTurn` runs on every exit
|
|
347
|
+
// path) releases them into the `serverOpen` check below.
|
|
348
|
+
while (this.turnActive)
|
|
349
|
+
await this.turnFinished;
|
|
350
|
+
// A queued prompt may have started waiting before the preceding idle armed
|
|
351
|
+
// auto-end. Cancel at actual delivery time, not only at sendMessage time.
|
|
352
|
+
this.cancelAutoEnd();
|
|
353
|
+
if (!this.sessionId || !this.serverOpen)
|
|
354
|
+
return;
|
|
355
|
+
this.turnActive = true;
|
|
356
|
+
this.turnEnded = false;
|
|
357
|
+
this.turnSerial += 1;
|
|
358
|
+
this.turnFinished = new Promise((resolve) => {
|
|
359
|
+
this.turnFinishedResolve = resolve;
|
|
360
|
+
});
|
|
361
|
+
// Turn boundary, v1 and v2 alike — the prompt POST is the turn start
|
|
362
|
+
// (§7.1); the end comes from the SSE `session.idle`, never from the HTTP
|
|
363
|
+
// response below. `POST /session/:id/message` long-polled the whole turn,
|
|
364
|
+
// and undici's default 300s headers timeout ended it as `prompt failed:
|
|
365
|
+
// fetch failed` (#4, upstream #897); `prompt_async` returns immediately.
|
|
366
|
+
this.emitUi(opencodeTurnStarted);
|
|
367
|
+
const body = { parts: [{ type: 'text', text }] };
|
|
368
|
+
// `spec.model` arrives already normalised to canonical `provider/model`
|
|
369
|
+
// (the run wiring's fail-loud gate). Split it with the shared parser — the
|
|
370
|
+
// one every runner uses — into opencode's `{ providerID, modelID }`.
|
|
371
|
+
const id = parseModelIdentity(this.spec.model);
|
|
372
|
+
if (id)
|
|
373
|
+
body.model = { providerID: id.provider, modelID: id.model };
|
|
374
|
+
try {
|
|
375
|
+
await this.http('POST', `/session/${this.sessionId}/prompt_async`, body);
|
|
376
|
+
}
|
|
377
|
+
catch (err) {
|
|
378
|
+
// No turn started server-side, so no `session.idle` will ever close it —
|
|
379
|
+
// surface the failure and end the turn here instead of parking the run.
|
|
380
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
381
|
+
this.emit({ type: 'note', message: `opencode: prompt failed: ${message}` });
|
|
382
|
+
this.finishTurn();
|
|
383
|
+
// Rethrow so `bootstrap` still rejects when the FIRST prompt cannot be
|
|
384
|
+
// posted: the run's result path turns that into a v1 `error`, which is
|
|
385
|
+
// the only event run.ts records failure from — a swallowed rejection
|
|
386
|
+
// would let an empty result mark the step successful. `sendMessage`
|
|
387
|
+
// discards the rethrow (the note + turn-end above already told the
|
|
388
|
+
// session's user), so a mid-session failure stays non-fatal, as before.
|
|
389
|
+
throw err;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
/** The single v1 turn-end: from the session's own `session.idle`, the
|
|
393
|
+
* prompt-POST failure path, or the teardown safety net — whichever comes
|
|
394
|
+
* first; the guards make every later call a no-op. */
|
|
395
|
+
finishTurn() {
|
|
396
|
+
if (!this.turnActive || this.turnEnded)
|
|
397
|
+
return;
|
|
398
|
+
this.turnEnded = true;
|
|
399
|
+
this.turnActive = false;
|
|
400
|
+
this.questionCapture = undefined;
|
|
401
|
+
// SSE idle can beat the independent reply HTTP response. The active reply
|
|
402
|
+
// owns pending-question cleanup; without one, the pending ask is stale.
|
|
403
|
+
if (!this.questionReply)
|
|
404
|
+
this.pendingQuestion = undefined;
|
|
405
|
+
this.turnFinishedResolve();
|
|
406
|
+
// A part that never saw `time.end` (abort, server quirk) still surfaces
|
|
407
|
+
// its prose before the turn boundary (run.ts reads markers there).
|
|
408
|
+
this.textCoalescer.flush();
|
|
409
|
+
this.emit({ type: 'turn-end' });
|
|
410
|
+
if (!this.questionReply)
|
|
411
|
+
this.scheduleAutoEnd();
|
|
412
|
+
}
|
|
413
|
+
// ---- SSE stream ---------------------------------------------------------
|
|
414
|
+
/** Resolves once the SSE stream is CONNECTED (headers in) — the frames are
|
|
415
|
+
* then drained in the background. Callers await the connection so no
|
|
416
|
+
* event emitted after this resolves can be missed.
|
|
417
|
+
*
|
|
418
|
+
* Speaks node:http, not fetch: undici's default Agent cuts a response
|
|
419
|
+
* body idle for 300s, which would sever a quiet turn's bus mid-run (#4) —
|
|
420
|
+
* node's own client has no idle timeout. (`process.getBuiltinModule`
|
|
421
|
+
* cannot fix that: undici is bundled into node but not exposed as a
|
|
422
|
+
* built-in, so there is no no-timeout dispatcher to hand fetch without
|
|
423
|
+
* taking undici as a real dependency.)
|
|
424
|
+
*
|
|
425
|
+
* A connection that cannot be established throws, failing `bootstrap`:
|
|
426
|
+
* with `prompt_async` this stream is the only source of turn-ends, so a
|
|
427
|
+
* session that cannot hear it is dead on arrival, not degraded. */
|
|
428
|
+
async consumeEvents() {
|
|
429
|
+
if (!this.baseUrl)
|
|
430
|
+
return;
|
|
431
|
+
let res;
|
|
432
|
+
try {
|
|
433
|
+
res = await new Promise((resolve, reject) => {
|
|
434
|
+
const req = httpRequest(`${this.baseUrl}/event`, { headers: { accept: 'text/event-stream' }, signal: this.sse.signal }, resolve);
|
|
435
|
+
req.on('error', reject);
|
|
436
|
+
req.end();
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
catch (err) {
|
|
440
|
+
if (this.sse.signal.aborted)
|
|
441
|
+
return; // torn down during bootstrap — not a failure
|
|
442
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
443
|
+
throw new Error(`event stream failed to connect: ${message}`);
|
|
444
|
+
}
|
|
445
|
+
// node:http hands over EVERY response, error statuses included — a proxy's
|
|
446
|
+
// 401 or a non-SSE 500 body is not an event bus, however well it parses.
|
|
447
|
+
const status = res.statusCode ?? 0;
|
|
448
|
+
if (status < 200 || status >= 300) {
|
|
449
|
+
res.resume(); // discard the body so the socket is released
|
|
450
|
+
throw new Error(`event stream failed to connect: HTTP ${status}`);
|
|
451
|
+
}
|
|
452
|
+
void this.readEvents(res);
|
|
453
|
+
}
|
|
454
|
+
async readEvents(res) {
|
|
455
|
+
// StringDecoder under the hood — multi-byte characters split across
|
|
456
|
+
// chunks arrive whole, like the TextDecoder streaming mode did.
|
|
457
|
+
res.setEncoding('utf8');
|
|
458
|
+
let buffer = '';
|
|
459
|
+
try {
|
|
460
|
+
for await (const chunk of res) {
|
|
461
|
+
buffer += chunk;
|
|
462
|
+
let sep;
|
|
463
|
+
while ((sep = buffer.indexOf('\n\n')) >= 0) {
|
|
464
|
+
const frame = buffer.slice(0, sep);
|
|
465
|
+
buffer = buffer.slice(sep + 2);
|
|
466
|
+
this.handleFrame(frame);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
catch {
|
|
471
|
+
// aborted — normal on end()/interrupt
|
|
472
|
+
}
|
|
473
|
+
finally {
|
|
474
|
+
// `session.idle` rides this stream and nothing else — a feed that dies
|
|
475
|
+
// while the server is still up would otherwise park the current turn
|
|
476
|
+
// forever and leave every later prompt unanswerable. An `error`, not a
|
|
477
|
+
// note: the turn's remaining output is lost, so the step must record a
|
|
478
|
+
// failure (run.ts classifies from v1 `error` only), never pass as an
|
|
479
|
+
// empty success. Then close the turn (flushing what did arrive) and end
|
|
480
|
+
// the session; "Continue" resumes it on a fresh server.
|
|
481
|
+
if (this.serverOpen && !this.sse.signal.aborted) {
|
|
482
|
+
this.emit({ type: 'error', message: 'opencode: event stream closed unexpectedly' });
|
|
483
|
+
this.finishTurn();
|
|
484
|
+
this.end();
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
handleFrame(frame) {
|
|
489
|
+
const dataLines = frame
|
|
490
|
+
.split('\n')
|
|
491
|
+
.filter((l) => l.startsWith('data:'))
|
|
492
|
+
.map((l) => l.slice(5).trim());
|
|
493
|
+
if (dataLines.length === 0)
|
|
494
|
+
return;
|
|
495
|
+
let evt;
|
|
496
|
+
try {
|
|
497
|
+
evt = JSON.parse(dataLines.join('\n'));
|
|
498
|
+
}
|
|
499
|
+
catch {
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
this.emitUi((state) => mapOpencodeEvent(evt, state));
|
|
503
|
+
this.handleEvent(evt);
|
|
504
|
+
}
|
|
505
|
+
handleEvent(evt) {
|
|
506
|
+
const type = evt.type ?? '';
|
|
507
|
+
const props = evt.properties ?? {};
|
|
508
|
+
if (type === 'message.updated' || type === 'message.created' || type === 'message.completed') {
|
|
509
|
+
const info = props.info ?? props;
|
|
510
|
+
const mid = stringField(info, 'id');
|
|
511
|
+
const role = stringField(info, 'role');
|
|
512
|
+
if (mid && role)
|
|
513
|
+
this.msgRole.set(mid, role);
|
|
514
|
+
this.absorbUsage(info);
|
|
515
|
+
}
|
|
516
|
+
else if (type === 'message.part.updated' || type === 'message.part.created') {
|
|
517
|
+
this.handlePart(props.part ?? props);
|
|
518
|
+
}
|
|
519
|
+
else if (type === 'session.idle') {
|
|
520
|
+
// THE turn-end signal. The SSE bus is server-wide, so a child session's
|
|
521
|
+
// (sub-agent's) idle must not close the main turn; an idle with no
|
|
522
|
+
// sessionID is treated as ours, like the v2 mapper does.
|
|
523
|
+
const sid = stringField(props, 'sessionID');
|
|
524
|
+
if (sid === undefined || sid === this.sessionId)
|
|
525
|
+
this.finishTurn();
|
|
526
|
+
}
|
|
527
|
+
else if (type === 'session.error') {
|
|
528
|
+
// The wire's only failure signal, now that the prompt POST returns
|
|
529
|
+
// before the turn runs: forward it to v1, whose `error` events are what
|
|
530
|
+
// run.ts records failed steps from — dropped, the terminal idle would
|
|
531
|
+
// file a provider/auth failure as a successful step. The idle that
|
|
532
|
+
// follows still closes the turn.
|
|
533
|
+
const sid = stringField(props, 'sessionID');
|
|
534
|
+
if (sid === undefined || sid === this.sessionId) {
|
|
535
|
+
this.emit({ type: 'error', message: `opencode: ${sessionErrorText(props.error)}` });
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
handlePart(part) {
|
|
540
|
+
// The SSE bus carries every session on the server — drop parts that
|
|
541
|
+
// belong to another one (a sub-agent's stream is surfaced by v2 only).
|
|
542
|
+
const partSession = stringField(part, 'sessionID');
|
|
543
|
+
if (partSession !== undefined && this.sessionId !== undefined && partSession !== this.sessionId)
|
|
544
|
+
return;
|
|
545
|
+
// Only surface parts of assistant messages — the user's own message streams
|
|
546
|
+
// over the same feed. Role is known early (the message.updated event
|
|
547
|
+
// precedes its parts); an unknown role means "not assistant yet" → skip.
|
|
548
|
+
const messageID = stringField(part, 'messageID');
|
|
549
|
+
if (messageID && this.msgRole.get(messageID) !== 'assistant')
|
|
550
|
+
return;
|
|
551
|
+
const kind = stringField(part, 'type');
|
|
552
|
+
const id = stringField(part, 'id') ?? messageID ?? '';
|
|
553
|
+
if (kind === 'text') {
|
|
554
|
+
const full = stringField(part, 'text') ?? '';
|
|
555
|
+
const seen = this.textSeen.get(id) ?? 0;
|
|
556
|
+
if (full.length > seen) {
|
|
557
|
+
this.textSeen.set(id, full.length);
|
|
558
|
+
this.textCoalescer.append(id, full.slice(seen));
|
|
559
|
+
}
|
|
560
|
+
// `time.end` marks the part finished (same signal the v2 mapper uses) —
|
|
561
|
+
// emit the whole block once, preferring the snapshot's full text.
|
|
562
|
+
const time = part.time;
|
|
563
|
+
if (time && typeof time === 'object' && typeof time.end === 'number') {
|
|
564
|
+
this.textCoalescer.complete(id, full);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
else if (kind === 'tool') {
|
|
568
|
+
const state = part.state ?? {};
|
|
569
|
+
const status = stringField(state, 'status');
|
|
570
|
+
const name = stringField(part, 'tool') ?? stringField(part, 'name') ?? 'tool';
|
|
571
|
+
const questionPartId = stringField(part, 'id') ?? stringField(part, 'callID') ?? messageID;
|
|
572
|
+
const questionInput = state.input ?? state;
|
|
573
|
+
const questionReady = name === 'question' && (toCezarQuestions(questionInput) !== null || status !== 'pending');
|
|
574
|
+
if (questionReady &&
|
|
575
|
+
this.turnActive &&
|
|
576
|
+
this.pendingQuestion === undefined &&
|
|
577
|
+
this.questionCapture === undefined &&
|
|
578
|
+
this.questionReply === undefined &&
|
|
579
|
+
(questionPartId === undefined || !this.handledQuestionParts.has(questionPartId))) {
|
|
580
|
+
if (questionPartId)
|
|
581
|
+
this.handledQuestionParts.add(questionPartId);
|
|
582
|
+
const capture = this.captureQuestion(questionInput);
|
|
583
|
+
this.questionCapture = capture;
|
|
584
|
+
const clearCapture = () => {
|
|
585
|
+
if (this.questionCapture === capture)
|
|
586
|
+
this.questionCapture = undefined;
|
|
587
|
+
};
|
|
588
|
+
void capture.then(clearCapture, clearCapture);
|
|
589
|
+
}
|
|
590
|
+
const callId = id || `${name}-${this.toolsSeen.size}`;
|
|
591
|
+
if (!this.toolsSeen.has(callId)) {
|
|
592
|
+
this.toolsSeen.add(callId);
|
|
593
|
+
this.toolCalls.push({ id: callId, name, input: state.input ?? state });
|
|
594
|
+
this.emit({ type: 'tool-call', id: callId, tool: name, input: state.input ?? state });
|
|
595
|
+
}
|
|
596
|
+
if (status === 'completed' || status === 'error') {
|
|
597
|
+
this.emit({
|
|
598
|
+
type: 'tool-result',
|
|
599
|
+
toolCallId: callId,
|
|
600
|
+
result: safeStringify(state.output ?? state.result ?? state),
|
|
601
|
+
isError: status === 'error',
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
/** Pull cumulative tokens/cost out of an assistant message info object. */
|
|
607
|
+
absorbUsage(info) {
|
|
608
|
+
if (!info)
|
|
609
|
+
return;
|
|
610
|
+
const tokens = info.tokens;
|
|
611
|
+
if (tokens) {
|
|
612
|
+
const input = numField(tokens, 'input');
|
|
613
|
+
const output = numField(tokens, 'output');
|
|
614
|
+
const reasoning = numField(tokens, 'reasoning');
|
|
615
|
+
const total = input + output + reasoning;
|
|
616
|
+
if (total > this.tokensUsed) {
|
|
617
|
+
this.tokensUsed = total;
|
|
618
|
+
this.emit({ type: 'token-usage', tokensUsed: this.tokensUsed });
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
const cost = numField(info, 'cost');
|
|
622
|
+
if (cost > this.lastCost) {
|
|
623
|
+
this.emit({ type: 'cost', usd: cost - this.lastCost });
|
|
624
|
+
this.lastCost = cost;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
async captureQuestion(input) {
|
|
628
|
+
const questions = toCezarQuestions(input);
|
|
629
|
+
const turnSerial = this.turnSerial;
|
|
630
|
+
let requestId;
|
|
631
|
+
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
632
|
+
if (!this.turnActive || this.turnSerial !== turnSerial)
|
|
633
|
+
return;
|
|
634
|
+
requestId = await this.pendingQuestionId().catch(() => undefined);
|
|
635
|
+
if (requestId)
|
|
636
|
+
break;
|
|
637
|
+
if (attempt < 7)
|
|
638
|
+
await sleep(150);
|
|
639
|
+
}
|
|
640
|
+
if (!this.turnActive || this.turnSerial !== turnSerial)
|
|
641
|
+
return;
|
|
642
|
+
if (!questions) {
|
|
643
|
+
if (!requestId) {
|
|
644
|
+
this.emit({
|
|
645
|
+
type: 'error',
|
|
646
|
+
message: 'opencode: unsupported native question could not be rejected: no pending question id',
|
|
647
|
+
});
|
|
648
|
+
this.finishTurn();
|
|
649
|
+
this.end();
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
try {
|
|
653
|
+
await this.http('POST', `/question/${encodeURIComponent(requestId)}/reject`, undefined);
|
|
654
|
+
this.emit({ type: 'note', message: 'opencode: unsupported native question rejected' });
|
|
655
|
+
}
|
|
656
|
+
catch (err) {
|
|
657
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
658
|
+
this.emit({
|
|
659
|
+
type: 'error',
|
|
660
|
+
message: `opencode: unsupported native question rejection failed: ${message}`,
|
|
661
|
+
});
|
|
662
|
+
this.finishTurn();
|
|
663
|
+
this.end();
|
|
664
|
+
}
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
const pending = {
|
|
668
|
+
requestId,
|
|
669
|
+
askRequestId: requestId ?? `opencode-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
670
|
+
questions,
|
|
671
|
+
};
|
|
672
|
+
this.pendingQuestion = pending;
|
|
673
|
+
this.emitQuestion(pending);
|
|
674
|
+
}
|
|
675
|
+
emitQuestion(pending) {
|
|
676
|
+
this.emitUi((state) => ({
|
|
677
|
+
state,
|
|
678
|
+
events: [
|
|
679
|
+
{
|
|
680
|
+
type: 'ask.requested',
|
|
681
|
+
requestId: pending.askRequestId,
|
|
682
|
+
questions: pending.questions,
|
|
683
|
+
},
|
|
684
|
+
],
|
|
685
|
+
}));
|
|
686
|
+
}
|
|
687
|
+
async pendingQuestionId() {
|
|
688
|
+
const value = await this.http('GET', '/question', undefined);
|
|
689
|
+
if (!Array.isArray(value))
|
|
690
|
+
return undefined;
|
|
691
|
+
for (const entry of value) {
|
|
692
|
+
if (!isRecord(entry) || stringField(entry, 'sessionID') !== this.sessionId)
|
|
693
|
+
continue;
|
|
694
|
+
const id = stringField(entry, 'id');
|
|
695
|
+
if (id)
|
|
696
|
+
return id;
|
|
697
|
+
}
|
|
698
|
+
return undefined;
|
|
699
|
+
}
|
|
700
|
+
async replyQuestion(pending, text) {
|
|
701
|
+
try {
|
|
702
|
+
const requestId = pending.requestId ?? (await this.pendingQuestionId());
|
|
703
|
+
if (!requestId)
|
|
704
|
+
throw new Error('no pending question id');
|
|
705
|
+
pending.requestId = requestId;
|
|
706
|
+
await this.http('POST', `/question/${encodeURIComponent(requestId)}/reply`, {
|
|
707
|
+
answers: questionAnswers(pending.questions, text),
|
|
708
|
+
});
|
|
709
|
+
if (this.pendingQuestion === pending)
|
|
710
|
+
this.pendingQuestion = undefined;
|
|
711
|
+
const queued = this.queuedQuestionMessages.splice(0);
|
|
712
|
+
for (const message of queued)
|
|
713
|
+
this.deliverPrompt(message);
|
|
714
|
+
if (queued.length === 0 && this.turnEnded)
|
|
715
|
+
this.scheduleAutoEnd();
|
|
716
|
+
}
|
|
717
|
+
catch (err) {
|
|
718
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
719
|
+
this.emit({ type: 'note', message: `opencode: question reply failed: ${message}` });
|
|
720
|
+
if (this.pendingQuestion === pending)
|
|
721
|
+
this.emitQuestion(pending);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
// ---- http ---------------------------------------------------------------
|
|
725
|
+
async http(method, path, body) {
|
|
726
|
+
if (!this.baseUrl)
|
|
727
|
+
throw new Error('opencode server not ready');
|
|
728
|
+
// Plain fetch is fine here: every call is a short round-trip now that
|
|
729
|
+
// prompts go through `prompt_async` — nothing long-polls anymore.
|
|
730
|
+
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
731
|
+
method,
|
|
732
|
+
headers: body !== undefined ? { 'content-type': 'application/json' } : {},
|
|
733
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
734
|
+
});
|
|
735
|
+
if (!res.ok) {
|
|
736
|
+
const detail = await res.text().catch(() => '');
|
|
737
|
+
throw new Error(`${method} ${path} → ${res.status} ${detail.slice(0, 200)}`);
|
|
738
|
+
}
|
|
739
|
+
const text = await res.text();
|
|
740
|
+
if (!text)
|
|
741
|
+
return {};
|
|
742
|
+
try {
|
|
743
|
+
return JSON.parse(text);
|
|
744
|
+
}
|
|
745
|
+
catch {
|
|
746
|
+
return {};
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
emit(event) {
|
|
750
|
+
this.onEvent?.(event);
|
|
751
|
+
}
|
|
752
|
+
/** The mapper never throws, but a defect in it must still never disturb
|
|
753
|
+
* the v1 stream — hence the belt-and-braces try. */
|
|
754
|
+
emitUi(map) {
|
|
755
|
+
try {
|
|
756
|
+
const mapped = map(this.uiState);
|
|
757
|
+
this.uiState = mapped.state;
|
|
758
|
+
if (this.opts.onUiEvent) {
|
|
759
|
+
for (const event of mapped.events)
|
|
760
|
+
this.opts.onUiEvent(event);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
catch {
|
|
764
|
+
// v2 mapping is best-effort; v1 consumers stay unaffected.
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
function textOf(content) {
|
|
769
|
+
return content
|
|
770
|
+
.filter((b) => b.type === 'text')
|
|
771
|
+
.map((b) => b.text)
|
|
772
|
+
.join('\n')
|
|
773
|
+
.trim();
|
|
774
|
+
}
|
|
775
|
+
function toCezarQuestions(value) {
|
|
776
|
+
if (!isRecord(value) || !Array.isArray(value.questions))
|
|
777
|
+
return null;
|
|
778
|
+
if (value.questions.length < 1 || value.questions.length > 4)
|
|
779
|
+
return null;
|
|
780
|
+
const questions = [];
|
|
781
|
+
for (const rawQuestion of value.questions) {
|
|
782
|
+
if (!isRecord(rawQuestion))
|
|
783
|
+
return null;
|
|
784
|
+
const header = clippedString(rawQuestion.header, 12);
|
|
785
|
+
const question = clippedString(rawQuestion.question, 400);
|
|
786
|
+
if (!header || !question || !Array.isArray(rawQuestion.options))
|
|
787
|
+
return null;
|
|
788
|
+
const options = [];
|
|
789
|
+
for (const rawOption of rawQuestion.options) {
|
|
790
|
+
if (options.length === 4)
|
|
791
|
+
break;
|
|
792
|
+
if (!isRecord(rawOption))
|
|
793
|
+
continue;
|
|
794
|
+
const label = clippedString(rawOption.label, 60);
|
|
795
|
+
if (!label)
|
|
796
|
+
continue;
|
|
797
|
+
const description = clippedString(rawOption.description, 280);
|
|
798
|
+
options.push({ label, ...(description ? { description } : {}) });
|
|
799
|
+
}
|
|
800
|
+
if (options.length < 2)
|
|
801
|
+
return null;
|
|
802
|
+
questions.push({
|
|
803
|
+
header,
|
|
804
|
+
question,
|
|
805
|
+
options,
|
|
806
|
+
...(rawQuestion.multiple === true ? { multiSelect: true } : {}),
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
return parseAskRequest({ questions })?.questions ?? null;
|
|
810
|
+
}
|
|
811
|
+
function questionAnswers(questions, text) {
|
|
812
|
+
const answers = questions.map(() => []);
|
|
813
|
+
const matched = new Set();
|
|
814
|
+
for (const line of text.split('\n')) {
|
|
815
|
+
const trimmed = line.trim();
|
|
816
|
+
const index = questions.findIndex((question, questionIndex) => !matched.has(questionIndex) && trimmed.startsWith(`${question.header}:`));
|
|
817
|
+
if (index < 0)
|
|
818
|
+
continue;
|
|
819
|
+
matched.add(index);
|
|
820
|
+
answers[index] = trimmed
|
|
821
|
+
.slice(questions[index].header.length + 1)
|
|
822
|
+
.split(',')
|
|
823
|
+
.map((answer) => answer.trim())
|
|
824
|
+
.filter(Boolean);
|
|
825
|
+
}
|
|
826
|
+
if (matched.size === 0 && answers[0])
|
|
827
|
+
answers[0] = [text.trim()];
|
|
828
|
+
return answers;
|
|
829
|
+
}
|
|
830
|
+
function clippedString(value, max) {
|
|
831
|
+
if (typeof value !== 'string')
|
|
832
|
+
return undefined;
|
|
833
|
+
const trimmed = value.trim();
|
|
834
|
+
return trimmed ? trimmed.slice(0, max) : undefined;
|
|
835
|
+
}
|
|
836
|
+
function isRecord(value) {
|
|
837
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
838
|
+
}
|
|
839
|
+
function sleep(ms) {
|
|
840
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
841
|
+
}
|
|
842
|
+
function stringField(obj, key) {
|
|
843
|
+
const v = obj[key];
|
|
844
|
+
return typeof v === 'string' ? v : undefined;
|
|
845
|
+
}
|
|
846
|
+
/** Same reading of the wire error shape as the v2 mapper's `errorText`
|
|
847
|
+
* (`opencode-ui-mapper.ts`): a bare string, `{message}`, `{data:{message}}`
|
|
848
|
+
* (the real server's `ProviderAuthError` shape), or `{name}` as a last resort. */
|
|
849
|
+
function sessionErrorText(error) {
|
|
850
|
+
if (typeof error === 'string')
|
|
851
|
+
return error;
|
|
852
|
+
if (typeof error !== 'object' || error === null)
|
|
853
|
+
return 'session error';
|
|
854
|
+
const rec = error;
|
|
855
|
+
const data = typeof rec.data === 'object' && rec.data !== null ? rec.data : undefined;
|
|
856
|
+
return (stringField(rec, 'message') ??
|
|
857
|
+
(data && stringField(data, 'message')) ??
|
|
858
|
+
stringField(rec, 'name') ??
|
|
859
|
+
'session error');
|
|
860
|
+
}
|
|
861
|
+
function numField(obj, key) {
|
|
862
|
+
const v = obj[key];
|
|
863
|
+
return typeof v === 'number' ? v : 0;
|
|
864
|
+
}
|
|
865
|
+
function safeStringify(value) {
|
|
866
|
+
if (typeof value === 'string')
|
|
867
|
+
return value;
|
|
868
|
+
try {
|
|
869
|
+
return JSON.stringify(value);
|
|
870
|
+
}
|
|
871
|
+
catch {
|
|
872
|
+
return String(value);
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
function wrapSpawnError(err, bin) {
|
|
876
|
+
const code = err?.code;
|
|
877
|
+
if (code === 'ENOENT') {
|
|
878
|
+
return new Error(`\`${bin}\` not found on PATH — install OpenCode (https://opencode.ai) and run \`opencode\` once to configure a provider`);
|
|
879
|
+
}
|
|
880
|
+
return err instanceof Error ? err : new Error(String(err));
|
|
881
|
+
}
|
|
882
|
+
//# sourceMappingURL=opencode-server-runner.js.map
|