akm-cli 0.10.261012-beta → 0.10.261014-beta
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/CHANGELOG.md +710 -3
- package/README.md +15 -0
- package/SECURITY.md +11 -10
- package/STABILITY.md +47 -51
- package/dist/assets/hints/cli-hints-full.md +21 -19
- package/dist/assets/hints/cli-hints-short.md +3 -4
- package/dist/assets/opencode/akm-plugin.js +177 -0
- package/dist/assets/stash-skeleton/README.md +1 -1
- package/dist/assets/tasks/core/improve.yml +1 -1
- package/dist/assets/tasks/improve/akm-improve-consolidate.yml +1 -1
- package/dist/assets/ui/config.js +27 -10
- package/dist/assets/workflows/workflow-template.md +2 -2
- package/dist/cli/clack.js +3 -1
- package/dist/cli/selection.js +33 -0
- package/dist/cli/shared.js +14 -1
- package/dist/cli/unknown-flags.js +6 -37
- package/dist/cli.js +69 -48
- package/dist/commands/agent/agent-dispatch.js +17 -25
- package/dist/commands/agent/contribute-cli.js +9 -8
- package/dist/commands/command/command-cli.js +4 -8
- package/dist/commands/command/command-execution.js +25 -39
- package/dist/commands/command/execution-source-loader.js +10 -14
- package/dist/commands/completions.js +1 -1
- package/dist/commands/config-cli.js +14 -5
- package/dist/commands/env/child-env.js +12 -14
- package/dist/commands/env/env-binding.js +47 -58
- package/dist/commands/env/env-cli.js +22 -24
- package/dist/commands/env/env.js +10 -58
- package/dist/commands/env/secret-cli.js +11 -29
- package/dist/commands/env/secret.js +3 -10
- package/dist/commands/health/checks.js +10 -849
- package/dist/commands/health/connections-and-harnesses-check.js +109 -0
- package/dist/commands/health/egress.js +3 -3
- package/dist/commands/health/html-report.js +9 -10
- package/dist/commands/health/plugin-staleness.js +18 -11
- package/dist/commands/health/report-view-model.js +16 -12
- package/dist/commands/health/scheduler-binary.js +1 -1
- package/dist/commands/health/semantic-search-check.js +85 -0
- package/dist/commands/health/type-directory-check.js +0 -3
- package/dist/commands/health.js +30 -42
- package/dist/commands/improve/consolidate/pair-pass.js +10 -25
- package/dist/commands/improve/consolidate.js +10 -15
- package/dist/commands/improve/distill.js +5 -17
- package/dist/commands/improve/eligibility.js +1 -3
- package/dist/commands/improve/execution.js +37 -40
- package/dist/commands/improve/extract-cli.js +6 -11
- package/dist/commands/improve/extract-prompt.js +2 -2
- package/dist/commands/improve/extract.js +67 -97
- package/dist/commands/improve/improve-cli.js +22 -128
- package/dist/commands/improve/improve-report.js +1 -1
- package/dist/commands/improve/improve-strategies.js +25 -108
- package/dist/commands/improve/improve.js +10 -14
- package/dist/commands/improve/loop-stages.js +3 -0
- package/dist/commands/improve/memory/memory-improve.js +1 -1
- package/dist/commands/improve/preparation.js +8 -13
- package/dist/commands/improve/reflect.js +203 -332
- package/dist/commands/improve/retrieval-scope.js +1 -1
- package/dist/commands/improve/session-asset.js +3 -2
- package/dist/commands/improve/stage.js +87 -115
- package/dist/commands/lint/base-linter.js +3 -3
- package/dist/commands/lint/env-key-rules.js +7 -6
- package/dist/commands/lint/index.js +3 -3
- package/dist/commands/models-cli.js +103 -63
- package/dist/commands/proposal/drain.js +26 -39
- package/dist/commands/proposal/proposal-cli.js +14 -18
- package/dist/commands/proposal/propose-cli.js +4 -2
- package/dist/commands/proposal/propose.js +37 -64
- package/dist/commands/proposal/repository.js +17 -17
- package/dist/commands/proposal/validators/proposal-validators.js +3 -6
- package/dist/commands/read/curate.js +10 -6
- package/dist/commands/read/registry-search.js +2 -48
- package/dist/commands/read/search-cli.js +1 -8
- package/dist/commands/read/search.js +5 -6
- package/dist/commands/read/show.js +6 -29
- package/dist/commands/remember.js +25 -40
- package/dist/commands/sources/add-cli.js +8 -63
- package/dist/commands/sources/bundle-cli.js +8 -9
- package/dist/commands/sources/bundle-config-ops.js +2 -2
- package/dist/commands/sources/info.js +12 -1
- package/dist/commands/sources/init.js +1 -1
- package/dist/commands/sources/installed-stashes.js +119 -63
- package/dist/commands/sources/migration-help.js +1 -1
- package/dist/commands/sources/plugin-upgrade.js +8 -15
- package/dist/commands/sources/schema-repair.js +4 -10
- package/dist/commands/sources/source-add.js +2 -2
- package/dist/commands/sources/source-clone.js +31 -7
- package/dist/commands/sources/source-manage.js +4 -4
- package/dist/commands/targets.js +175 -0
- package/dist/commands/tasks/explain.js +104 -69
- package/dist/commands/tasks/tasks-cli.js +9 -11
- package/dist/commands/tasks/tasks.js +75 -97
- package/dist/commands/ui/api.js +11 -3
- package/dist/commands/ui/redact.js +5 -31
- package/dist/commands/ui/workspace-actions.js +1 -1
- package/dist/commands/workflow/plan.js +48 -9
- package/dist/commands/workflow-cli.js +40 -45
- package/dist/core/adapter/adapters/agent-skills-adapter.js +10 -11
- package/dist/core/adapter/adapters/akm-adapter.js +36 -41
- package/dist/core/adapter/adapters/akm-lint.js +10 -12
- package/dist/core/adapter/adapters/akm-metadata.js +2 -2
- package/dist/core/adapter/adapters/akm-task-adapter.js +8 -9
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +5 -5
- package/dist/core/adapter/adapters/claude-adapter.js +1 -1
- package/dist/core/adapter/adapters/dotenv-adapter.js +7 -6
- package/dist/core/adapter/adapters/generic-files-adapter.js +5 -5
- package/dist/core/adapter/adapters/index.js +3 -4
- package/dist/core/adapter/adapters/llm-wiki-adapter.js +12 -14
- package/dist/core/adapter/adapters/okf-adapter.js +8 -10
- package/dist/core/adapter/adapters/opencode-adapter.js +1 -1
- package/dist/core/adapter/adapters/shared.js +3 -3
- package/dist/core/adapter/adapters/tool-dir-shared.js +7 -25
- package/dist/core/adapter/adapters/website-snapshot-adapter.js +3 -3
- package/dist/core/adapter/detect-adapter.js +1 -1
- package/dist/core/adapter/execution-source.js +13 -35
- package/dist/core/adapter/registry.js +1 -1
- package/dist/core/adapter/validate-context.js +2 -4
- package/dist/core/asset/asset-placement.js +1 -1
- package/dist/core/asset/asset-ref.js +3 -3
- package/dist/core/asset/resolve-ref.js +5 -0
- package/dist/core/bundle-rename.js +12 -4
- package/dist/core/closest-match.js +37 -0
- package/dist/core/common.js +37 -57
- package/dist/core/config/config-io.js +26 -18
- package/dist/core/config/config-schema.js +19 -106
- package/dist/core/config/config-sources.js +3 -3
- package/dist/core/config/config-types.js +4 -4
- package/dist/core/config/config-walker.js +50 -77
- package/dist/core/config/config.js +438 -109
- package/dist/core/config/convert-engine-references.js +114 -0
- package/dist/core/config/convert-engines-shared.js +40 -0
- package/dist/core/config/convert-engines.js +300 -0
- package/dist/core/config/convert-models-overlay.js +137 -0
- package/dist/core/config/credentials.js +304 -0
- package/dist/core/config/engine-semantics.js +3 -3
- package/dist/core/config/migrate-keys.js +222 -0
- package/dist/core/config/model-aliases.js +37 -0
- package/dist/core/config/models-file.js +34 -0
- package/dist/core/config/retired-engines.js +53 -0
- package/dist/core/config/schema/connections.js +41 -0
- package/dist/core/config/schema/embedding.js +2 -2
- package/dist/core/config/schema/harnesses.js +41 -0
- package/dist/core/config/schema/improve-processes.js +9 -28
- package/dist/core/config/schema/improve.js +36 -1
- package/dist/core/config/schema/index-config.js +18 -20
- package/dist/core/config/schema/primitives.js +42 -17
- package/dist/core/config/schema/search.js +6 -11
- package/dist/core/config/schema/sources-bundles.js +9 -9
- package/dist/core/config/schema/workflow.js +2 -6
- package/dist/core/config/unusable-entries.js +66 -0
- package/dist/core/content-safety.js +3 -6
- package/dist/core/endpoint-url.js +47 -0
- package/dist/core/env-secret-ref.js +84 -73
- package/dist/core/errors.js +10 -16
- package/dist/core/extra-params.js +3 -3
- package/dist/core/improve-result.js +8 -9
- package/dist/core/json-schema.js +1 -1
- package/dist/core/lesson-lint.js +1 -1
- package/dist/core/llm-inference.js +80 -0
- package/dist/core/parse.js +2 -2
- package/dist/core/paths.js +33 -10
- package/dist/core/recognition-util.js +8 -10
- package/dist/core/redaction.js +94 -55
- package/dist/core/ref-token.js +82 -0
- package/dist/core/secret-value.js +14 -0
- package/dist/core/state/migrations.js +17 -22
- package/dist/core/structured.js +2 -68
- package/dist/core/subprocess.js +63 -22
- package/dist/core/type-presentation.js +10 -14
- package/dist/core/write-source.js +20 -4
- package/dist/execution/input-contract.js +3 -6
- package/dist/execution/notices.js +49 -0
- package/dist/execution/resolved-request.js +24 -40
- package/dist/execution/source.js +21 -45
- package/dist/execution/target-ref.js +2 -2
- package/dist/indexer/bundle-identity-guard.js +6 -5
- package/dist/indexer/ensure-index.js +1 -1
- package/dist/indexer/index-writer-lock.js +1 -1
- package/dist/indexer/indexer.js +46 -42
- package/dist/indexer/installations.js +12 -22
- package/dist/indexer/lookup/adapter-concept-owner.js +40 -14
- package/dist/indexer/materialize-embeddings.js +20 -16
- package/dist/indexer/passes/memory-inference.js +21 -50
- package/dist/indexer/passes/metadata.js +5 -5
- package/dist/indexer/scan/doc-to-entry.js +1 -1
- package/dist/indexer/scan/drain-dir.js +9 -11
- package/dist/indexer/search/db-search.js +9 -7
- package/dist/indexer/search/search-source.js +42 -22
- package/dist/indexer/usage/usage-events.js +2 -3
- package/dist/indexer/walk/index-context.js +2 -1
- package/dist/indexer/walk/matchers.js +3 -11
- package/dist/indexer/walk/path-resolver.js +13 -68
- package/dist/indexer/walk/walker.js +32 -26
- package/dist/integrations/agent/akm-read-tools.js +81 -0
- package/dist/integrations/agent/config.js +1 -1
- package/dist/integrations/agent/detect.js +32 -36
- package/dist/integrations/agent/execution.js +130 -428
- package/dist/integrations/agent/index.js +1 -1
- package/dist/integrations/agent/profiles.js +24 -28
- package/dist/integrations/agent/prompts.js +17 -30
- package/dist/integrations/agent/request-lowering.js +67 -101
- package/dist/integrations/agent/resolution.js +332 -0
- package/dist/integrations/agent/runner-dispatch.js +156 -181
- package/dist/integrations/agent/runner.js +20 -70
- package/dist/integrations/agent/runtime-port.js +4 -0
- package/dist/integrations/agent/runtime.js +10 -0
- package/dist/integrations/agent/spawn.js +65 -190
- package/dist/integrations/harnesses/aider/index.js +19 -39
- package/dist/integrations/harnesses/amazonq/index.js +17 -32
- package/dist/integrations/harnesses/claude/index.js +59 -28
- package/dist/integrations/harnesses/claude/session-log.js +7 -5
- package/dist/integrations/harnesses/codex/index.js +19 -31
- package/dist/integrations/harnesses/copilot/index.js +17 -35
- package/dist/integrations/harnesses/gemini/index.js +18 -34
- package/dist/integrations/harnesses/harness.js +36 -0
- package/dist/integrations/harnesses/index.js +20 -51
- package/dist/integrations/harnesses/opencode/agent-builder.js +132 -100
- package/dist/integrations/harnesses/opencode/index.js +11 -33
- package/dist/integrations/harnesses/opencode/roots.js +50 -0
- package/dist/integrations/harnesses/opencode/runtime/binary.js +206 -0
- package/dist/integrations/harnesses/opencode/runtime/command.js +152 -0
- package/dist/integrations/harnesses/opencode/runtime/index.js +177 -0
- package/dist/integrations/harnesses/opencode/runtime/tar.js +107 -0
- package/dist/integrations/harnesses/opencode/session-log-v1.js +15 -7
- package/dist/integrations/harnesses/opencode/session-log-v2.js +72 -48
- package/dist/integrations/harnesses/opencode/session-log.js +58 -137
- package/dist/integrations/harnesses/opencode/version.js +34 -53
- package/dist/integrations/harnesses/openhands/index.js +13 -37
- package/dist/integrations/harnesses/pi/index.js +21 -32
- package/dist/integrations/lockfile.js +1 -1
- package/dist/integrations/session-logs/index.js +11 -31
- package/dist/llm/client.js +93 -111
- package/dist/llm/embedder.js +19 -34
- package/dist/llm/embedders/connection.js +31 -0
- package/dist/llm/embedders/local-model-files.js +56 -0
- package/dist/llm/embedders/local.js +146 -115
- package/dist/llm/embedders/remote.js +26 -23
- package/dist/llm/embedders/types.js +1 -1
- package/dist/llm/index-passes.js +33 -39
- package/dist/llm/memory-infer.js +67 -77
- package/dist/llm/model-list.js +35 -0
- package/dist/llm/rerank-client.js +8 -14
- package/dist/llm/structured-call.js +45 -99
- package/dist/llm/structured.js +60 -0
- package/dist/output/renderers.js +2 -8
- package/dist/output/shapes/helpers.js +18 -44
- package/dist/output/shapes/passthrough.js +1 -0
- package/dist/output/shapes.js +1 -7
- package/dist/output/text/command-format.js +41 -39
- package/dist/output/text/health-format.js +8 -2
- package/dist/output/text/helpers.js +1 -1
- package/dist/output/text/migrate.js +12 -0
- package/dist/output/text/models.js +3 -2
- package/dist/output/text/registry-commands.js +1 -2
- package/dist/output/text/show-directives.js +16 -28
- package/dist/output/text/show-format.js +0 -2
- package/dist/output/text/targets-table.js +70 -0
- package/dist/output/text/workflow-format.js +44 -15
- package/dist/output/text.js +1 -1
- package/dist/registry/providers/skills-sh.js +1 -28
- package/dist/registry/providers/static-index.js +1 -71
- package/dist/runtime.js +6 -2
- package/dist/scripts/akm-migrate-node.js +47095 -44905
- package/dist/scripts/akm-migrate.js +46946 -44755
- package/dist/setup/detect.js +28 -273
- package/dist/setup/endpoint.js +36 -0
- package/dist/setup/semantic-assets.js +13 -19
- package/dist/setup/setup-flags.js +77 -0
- package/dist/setup/setup-plan.js +144 -0
- package/dist/setup/setup.js +134 -358
- package/dist/setup/steps/connection-shared.js +49 -101
- package/dist/setup/steps/connection.js +121 -524
- package/dist/setup/steps/embeddings.js +151 -0
- package/dist/setup/steps/harnesses.js +118 -0
- package/dist/setup/steps/platforms.js +2 -81
- package/dist/setup/steps/tasks.js +1 -2
- package/dist/setup/steps.js +10 -0
- package/dist/setup/summary.js +67 -0
- package/dist/sources/freshness.js +16 -21
- package/dist/sources/providers/git-provider.js +2 -9
- package/dist/sources/providers/provider-utils.js +13 -5
- package/dist/sources/resolve.js +12 -16
- package/dist/sources/snapshot-fetchers/robots.js +11 -12
- package/dist/sources/snapshot-fetchers/secret-seam.js +2 -4
- package/dist/sources/snapshot-fetchers/website-ingest.js +8 -15
- package/dist/sources/snapshot-fetchers/x.js +1 -1
- package/dist/standalone/onnxruntime-library.js +52 -0
- package/dist/storage/repositories/improve-ledger-repository.js +1 -1
- package/dist/storage/repositories/improve-runs-repository.js +0 -13
- package/dist/storage/repositories/index-entries-repository.js +2 -2
- package/dist/storage/repositories/proposals-repository.js +2 -2
- package/dist/storage/repositories/task-history-repository.js +2 -2
- package/dist/storage/repositories/workflow-runs-repository.js +44 -129
- package/dist/storage/sqlite-read-snapshot.js +33 -29
- package/dist/tasks/embedded.js +1 -1
- package/dist/tasks/log-redaction.js +7 -119
- package/dist/tasks/prepare/prepare-support.js +8 -7
- package/dist/tasks/prepare/prepare.js +8 -9
- package/dist/tasks/prepare/script-capture.js +9 -25
- package/dist/tasks/run/attempt-lifecycle.js +17 -35
- package/dist/tasks/run/load-task.js +54 -27
- package/dist/tasks/run/run-command-task.js +15 -10
- package/dist/tasks/run/run-native-task.js +8 -21
- package/dist/tasks/run/run-task.js +5 -8
- package/dist/tasks/run/run-workflow-task.js +14 -21
- package/dist/tasks/run/task-history.js +9 -17
- package/dist/tasks/run/task-log.js +1 -5
- package/dist/tasks/scheduler-invocation.js +13 -18
- package/dist/tasks/scheduler-sync.js +0 -1
- package/dist/tasks/source/bounded-document.js +31 -117
- package/dist/tasks/source/parse-task-source.js +0 -1
- package/dist/tasks/source/project-v4.js +5 -3
- package/dist/tasks/source/task-input-diagnostics.js +3 -4
- package/dist/tasks/source/task-source-v4.js +48 -72
- package/dist/tasks/source-v3.js +2 -5
- package/dist/tasks/task-run-reserved-flags.js +1 -2
- package/dist/workflows/authoring/authoring.js +2 -7
- package/dist/workflows/compile.js +1 -15
- package/dist/workflows/concurrency-policy.js +4 -4
- package/dist/workflows/exec/child-workflow.js +1 -4
- package/dist/workflows/exec/dispatch-redaction.js +35 -86
- package/dist/workflows/exec/environment.js +13 -77
- package/dist/workflows/exec/exec-unit.js +14 -55
- package/dist/workflows/exec/frozen-judge.js +26 -28
- package/dist/workflows/exec/native-executor.js +126 -71
- package/dist/workflows/exec/run-workflow.js +38 -42
- package/dist/workflows/exec/step-work.js +13 -17
- package/dist/workflows/exec/unit-dispatch.js +7 -21
- package/dist/workflows/freeze/environment.js +14 -119
- package/dist/workflows/freeze/freeze.js +14 -33
- package/dist/workflows/freeze/step-values.js +6 -13
- package/dist/workflows/freeze/targets/child-workflow.js +3 -5
- package/dist/workflows/freeze/targets/command.js +9 -15
- package/dist/workflows/freeze/targets/shell.js +0 -3
- package/dist/workflows/freeze/task-bindings.js +0 -1
- package/dist/workflows/parser.js +50 -59
- package/dist/workflows/plan.js +8 -2
- package/dist/workflows/renderer.js +83 -9
- package/dist/workflows/resource-limits.js +2 -2
- package/dist/workflows/runtime/run-plan.js +38 -23
- package/dist/workflows/runtime/runs.js +55 -93
- package/dist/workflows/runtime/workflow-asset-loader.js +55 -86
- package/dist/workflows/source-files.js +7 -19
- package/docs/README.md +10 -16
- package/docs/architecture/README.md +2 -37
- package/docs/architecture/adapters.md +2 -2
- package/docs/architecture/akm-core-principles.md +256 -118
- package/docs/architecture/architecture.md +283 -99
- package/docs/architecture/improvement.md +8 -2
- package/docs/architecture/internals/classification.md +1 -3
- package/docs/architecture/internals/fresh-host-rebuild-runbook.md +7 -6
- package/docs/architecture/internals/health-advisories.md +35 -74
- package/docs/architecture/internals/improve-workflow.md +27 -26
- package/docs/architecture/internals/indexing.md +3 -3
- package/docs/architecture/internals/storage-locations.md +48 -15
- package/docs/architecture/persisted-data-compat.md +10 -6
- package/docs/architecture/specs/okf-support.md +9 -11
- package/docs/architecture/specs/ref.md +7 -9
- package/docs/architecture/testing/testing-workflow.md +172 -52
- package/docs/architecture/workflow-engine.md +51 -34
- package/docs/guides/README.md +0 -1
- package/docs/guides/author-bundles.md +15 -20
- package/docs/guides/author-workflows.md +93 -71
- package/docs/guides/bundles.md +20 -16
- package/docs/guides/discover-and-load.md +1 -1
- package/docs/guides/getting-started.md +49 -3
- package/docs/guides/improve-the-library.md +10 -4
- package/docs/guides/recipes/headless-install.md +11 -16
- package/docs/guides/run-workflows.md +29 -20
- package/docs/guides/scheduling.md +2 -2
- package/docs/guides/use-with-any-agent.md +20 -22
- package/docs/integration/bundling-akm.md +19 -9
- package/docs/maintainers/README.md +2 -6
- package/docs/maintainers/release-checklist.md +73 -126
- package/docs/migration/README.md +10 -3
- package/docs/migration/release-notes/0.10.0.md +81 -16
- package/docs/migration/release-notes/0.6.0.md +1 -1
- package/docs/migration/release-notes/0.7.0.md +1 -1
- package/docs/migration/release-notes/0.9.0.md +1 -1
- package/docs/migration/release-notes/README.md +1 -1
- package/docs/migration/v0.9-to-v0.10.md +300 -60
- package/docs/migration/v0.9.1-to-v0.9.2.md +6 -5
- package/docs/posts/config-v2-lessons-16.md +1 -1
- package/docs/posts/llm-mode-reflect-13.md +1 -1
- package/docs/posts/release-0.6.0.md +11 -11
- package/docs/posts/release-0.8.0.md +4 -4
- package/docs/posts/workflows-vaults-09.md +1 -1
- package/docs/reference/README.md +1 -1
- package/docs/reference/asset-types.md +2 -2
- package/docs/reference/cli.md +477 -388
- package/docs/reference/configuration.md +569 -550
- package/docs/reference/data-and-telemetry.md +9 -6
- package/docs/reference/env-and-secrets.md +72 -15
- package/docs/reference/refs.md +3 -20
- package/docs/reference/registry.md +12 -38
- package/docs/reference/supported-formats.md +1 -1
- package/docs/reference/tasks.md +22 -11
- package/docs/reference/ui-api.md +14 -9
- package/docs/reference/workflow-schema.md +199 -154
- package/docs/reference/workflows.md +4 -6
- package/package.json +9 -11
- package/schemas/akm-config.json +1696 -2922
- package/schemas/akm-task.json +4 -4
- package/schemas/akm-workflow.json +36 -9
- package/ROADMAP.md +0 -112
- package/dist/assets/models.json +0 -35
- package/dist/commands/health/engine-usage.js +0 -45
- package/dist/commands/sources/dangerous-env-audit.js +0 -134
- package/dist/core/activation-policy.js +0 -67
- package/dist/core/config/schema/engines.js +0 -160
- package/dist/core/config/schema/execution.js +0 -23
- package/dist/core/spawn-env.js +0 -268
- package/dist/execution/directory-identity.js +0 -78
- package/dist/integrations/agent/builder-shared.js +0 -33
- package/dist/integrations/agent/builders.js +0 -43
- package/dist/integrations/agent/engine-fallback.js +0 -59
- package/dist/integrations/agent/engine-resolution.js +0 -333
- package/dist/integrations/agent/model-map.js +0 -475
- package/dist/integrations/harnesses/aider/agent-builder.js +0 -107
- package/dist/integrations/harnesses/aider/result-extractor.js +0 -53
- package/dist/integrations/harnesses/amazonq/agent-builder.js +0 -138
- package/dist/integrations/harnesses/amazonq/result-extractor.js +0 -48
- package/dist/integrations/harnesses/claude/agent-builder.js +0 -106
- package/dist/integrations/harnesses/claude/config-import.js +0 -67
- package/dist/integrations/harnesses/claude/result-extractor.js +0 -67
- package/dist/integrations/harnesses/codex/agent-builder.js +0 -150
- package/dist/integrations/harnesses/codex/result-extractor.js +0 -110
- package/dist/integrations/harnesses/copilot/agent-builder.js +0 -117
- package/dist/integrations/harnesses/copilot/result-extractor.js +0 -178
- package/dist/integrations/harnesses/gemini/agent-builder.js +0 -114
- package/dist/integrations/harnesses/gemini/result-extractor.js +0 -149
- package/dist/integrations/harnesses/opencode/config-import.js +0 -149
- package/dist/integrations/harnesses/opencode/model-config.js +0 -33
- package/dist/integrations/harnesses/opencode/model-work-agent.js +0 -115
- package/dist/integrations/harnesses/opencode/session-log-types.js +0 -12
- package/dist/integrations/harnesses/opencode-sdk/index.js +0 -44
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +0 -1052
- package/dist/integrations/harnesses/opencode-sdk/v1-adapter.js +0 -199
- package/dist/integrations/harnesses/opencode-sdk/v2-adapter.js +0 -254
- package/dist/integrations/harnesses/opencode-sdk/wire.js +0 -66
- package/dist/integrations/harnesses/openhands/agent-builder.js +0 -120
- package/dist/integrations/harnesses/openhands/result-extractor.js +0 -135
- package/dist/integrations/harnesses/pi/agent-builder.js +0 -89
- package/dist/integrations/harnesses/pi/result-extractor.js +0 -168
- package/dist/integrations/harnesses/shared.js +0 -18
- package/dist/integrations/harnesses/types.js +0 -55
- package/dist/llm/feature-gate.js +0 -115
- package/dist/output/shapes/models-list.js +0 -16
- package/dist/output/shapes/registry-search.js +0 -10
- package/dist/setup/detected-engines.js +0 -136
- package/dist/setup/engine-config.js +0 -97
- package/dist/setup/harness-config-import.js +0 -47
- package/dist/setup/providers.js +0 -14
- package/dist/setup/steps/semantic.js +0 -39
- package/dist/workflows/authoring/scope-key.js +0 -79
- package/dist/workflows/exec/lowering-notices.js +0 -23
- package/dist/workflows/source-semantics.js +0 -110
- package/docs/architecture/0.10-process-rules.md +0 -832
- package/docs/architecture/akm-architecture-decision-history.md +0 -2055
- package/docs/architecture/comparisons/claude-code-vs-akm-workflows-full.md +0 -550
- package/docs/architecture/decisions/0001-no-interpolation-attached-structured-context.md +0 -72
- package/docs/architecture/decisions/0002-unit-reuse-and-input-hash-scope.md +0 -142
- package/docs/architecture/decisions/0003-child-env-allowlist-and-provenance.md +0 -189
- package/docs/architecture/decisions/0004-task-input-contract-and-flag-coercion.md +0 -97
- package/docs/architecture/decisions/0005-task-result-vocabulary-and-legacy-read-mapping.md +0 -108
- package/docs/architecture/decisions/0006-task-source-version-routing.md +0 -124
- package/docs/architecture/decisions/0007-workflow-composition-bounds.md +0 -83
- package/docs/architecture/decisions/0008-task-binding-normalization.md +0 -88
- package/docs/architecture/decisions/0009-child-run-publication-column-parity.md +0 -80
- package/docs/architecture/decisions/0010-driver-protocol-cut.md +0 -36
- package/docs/architecture/decisions/0011-engine-run-loop-invariants.md +0 -125
- package/docs/architecture/decisions/README.md +0 -62
- package/docs/architecture/internals/functional-contract-patterns.md +0 -340
- package/docs/architecture/internals/registry-network-boundary.md +0 -57
- package/docs/architecture/internals/search.md +0 -248
- package/docs/architecture/reviews/env-secret-access.md +0 -319
- package/docs/architecture/runtime-boundary-design.md +0 -153
- package/docs/architecture/specs/0.9.0-close-out-plan.md +0 -361
- package/docs/architecture/specs/0.9.0-decisions.md +0 -532
- package/docs/architecture/specs/0.9.0-docs-code-drift-register.md +0 -745
- package/docs/architecture/specs/0.9.0-open-items-register.md +0 -135
- package/docs/architecture/specs/0.9.0-public-api-issue-backlog.md +0 -740
- package/docs/architecture/specs/0.9.0-release-surface-review.md +0 -636
- package/docs/architecture/specs/agent-command-engine-model-design.md +0 -513
- package/docs/architecture/specs/akm-0.9.0-bundle-adapter-spec.md +0 -501
- package/docs/architecture/specs/akm-0.9.0-ref-grammar-decision.md +0 -291
- package/docs/architecture/specs/akm-format-neutral-bundle-workspace-spec.md +0 -2059
- package/docs/architecture/specs/di-seams-plan.md +0 -539
- package/docs/architecture/specs/driver-protocol-keep-or-cut.md +0 -275
- package/docs/architecture/specs/fact-asset-type.md +0 -142
- package/docs/architecture/specs/improve-collapse-churn-detector-design.md +0 -634
- package/docs/architecture/specs/reviews/task-workflow-unification/README.md +0 -72
- package/docs/architecture/specs/reviews/task-workflow-unification/findings-consolidated.md +0 -152
- package/docs/architecture/specs/reviews/task-workflow-unification/library-eval-alternatives.md +0 -352
- package/docs/architecture/specs/reviews/task-workflow-unification/library-eval-bunqueue.md +0 -300
- package/docs/architecture/specs/reviews/task-workflow-unification/library-eval-openworkflow.md +0 -402
- package/docs/architecture/specs/reviews/task-workflow-unification/opus-review-1.md +0 -758
- package/docs/architecture/specs/reviews/task-workflow-unification/opus-review-2.md +0 -645
- package/docs/architecture/specs/reviews/task-workflow-unification/opus-review-3.md +0 -602
- package/docs/architecture/specs/reviews/task-workflow-unification/panel-r1-adversarial.md +0 -27
- package/docs/architecture/specs/reviews/task-workflow-unification/panel-r1-architecture.md +0 -27
- package/docs/architecture/specs/reviews/task-workflow-unification/panel-r1-coverage.md +0 -74
- package/docs/architecture/specs/reviews/task-workflow-unification/panel-r1-security.md +0 -15
- package/docs/architecture/specs/reviews/task-workflow-unification/panel-r2-adversarial.md +0 -132
- package/docs/architecture/specs/reviews/task-workflow-unification/panel-r2-verification.md +0 -147
- package/docs/architecture/specs/stash-conventions-code-spec.md +0 -275
- package/docs/architecture/specs/stash-organization-conventions.md +0 -377
- package/docs/architecture/specs/task-workflow-format-unification.md +0 -721
- package/docs/architecture/specs/workflow-engine-buy-vs-build.md +0 -231
- package/docs/architecture/specs/workflow-format-unification.md +0 -432
- package/docs/architecture/testing/manual-testing-checklist.md +0 -3411
- package/docs/architecture/testing/okf-v0.2-conformance-runbook.md +0 -924
- package/docs/architecture/testing/task-workflow-manual-tests.md +0 -1720
- package/docs/guides/claude-code-vs-akm-workflows.md +0 -29
- package/docs/guides/knowledge-management.md +0 -18
- package/docs/maintainers/curate-workmap.md +0 -335
- package/docs/migration/v0.5-to-v0.6.md +0 -759
- package/docs/migration/v0.7-to-v0.8.md +0 -1378
- package/docs/plans/0.9.2-agent-command-workflow-plan.md +0 -423
|
@@ -1,1052 +0,0 @@
|
|
|
1
|
-
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
-
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
-
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
-
/**
|
|
5
|
-
* OpenCode SDK agent runner (migrated from `agent/sdk-runner.ts`, #564).
|
|
6
|
-
*
|
|
7
|
-
* Uses the embedded `@opencode-ai/sdk` instead of `Bun.spawn` for dispatch, so
|
|
8
|
-
* no agent CLI argv is ever constructed — but the SDK is not self-contained:
|
|
9
|
-
* {@link createManagedOpencode} spawns `opencode serve` and talks HTTP to it,
|
|
10
|
-
* so the `opencode` binary must be on PATH (which is why setup's
|
|
11
|
-
* `detectHarness` gates this harness on that probe). The user provides an
|
|
12
|
-
* OpenAI-compatible endpoint (or inherits from the selected fallback LLM
|
|
13
|
-
* engine) for the SDK.
|
|
14
|
-
*
|
|
15
|
-
* This is the runtime surface of the {@link OpencodeSdkHarness} (`id =
|
|
16
|
-
* 'opencode-sdk'`). It is the dispatch path for SDK runner specs; it exposes
|
|
17
|
-
* no native session logs of its own (`capabilities.sessionLogs = false`).
|
|
18
|
-
*
|
|
19
|
-
* ## Two OpenCode majors, one runner (#1049)
|
|
20
|
-
*
|
|
21
|
-
* This file owns what both majors share: the managed `opencode serve` child
|
|
22
|
-
* (spawn, port, registry, cleanup, {@link closeServer}), the child environment,
|
|
23
|
-
* per-workspace isolation, deadlines, abort handling and result shaping. The
|
|
24
|
-
* per-major wire protocol is an adapter behind `./wire.ts`, chosen once per
|
|
25
|
-
* dispatch from the major the engine's binary reports
|
|
26
|
-
* (`../opencode/version.ts`); the major is part of the server registry key.
|
|
27
|
-
* Where the sections below name an OpenCode 1 wire shape (`query.directory`,
|
|
28
|
-
* the prompt body), `v2-adapter.ts` has the OpenCode 2 equivalent.
|
|
29
|
-
*
|
|
30
|
-
* ## Sessions are kept (#1100)
|
|
31
|
-
*
|
|
32
|
-
* One session is created per dispatch and is NEVER deleted: it stays in
|
|
33
|
-
* OpenCode's history (transcript for debugging, input for session capture),
|
|
34
|
-
* as it does for `opencode run`, and its id is returned as
|
|
35
|
-
* `AgentRunResult.sessionId`. A dispatch akm gives up on (timeout or caller
|
|
36
|
-
* abort) is aborted server-side so the server stops calling the model, but the
|
|
37
|
-
* session is still kept. The server process itself is closed independently
|
|
38
|
-
* (see the registry notes below).
|
|
39
|
-
*
|
|
40
|
-
* ## Per-call cwd and env (redesign addendum R2, open seam decision 1)
|
|
41
|
-
*
|
|
42
|
-
* The plan left one decision open: per-call cwd/env forwarding vs a server
|
|
43
|
-
* keyed by `(cwd, envKeysHash)`. Reading the SDK settled it as a SPLIT — the
|
|
44
|
-
* two halves have different API realities (verified against
|
|
45
|
-
* `@opencode-ai/sdk` 1.2.20):
|
|
46
|
-
*
|
|
47
|
-
* - **cwd is PER-CALL.** `session.create` / `session.prompt` /
|
|
48
|
-
* `session.abort` all accept a `query.directory` parameter that scopes
|
|
49
|
-
* the session's working directory, so a single server can host sessions
|
|
50
|
-
* in any number of working directories. {@link RunAgentOptions.cwd} is
|
|
51
|
-
* forwarded as `query: { directory }` on every session call — no
|
|
52
|
-
* per-cwd server processes, no server-key explosion for worktree
|
|
53
|
-
* isolation (which mints a fresh directory per unit attempt).
|
|
54
|
-
*
|
|
55
|
-
* - **env is PER-SERVER (keyed registry).** The SDK exposes NO per-call or
|
|
56
|
-
* per-session env surface; the only way env reaches tool child processes
|
|
57
|
-
* is the `opencode serve` process environment. {@link getOrStartServer}
|
|
58
|
-
* snapshots and materializes the complete child environment for each
|
|
59
|
-
* dispatch, passes it directly to the managed spawn (never mutating
|
|
60
|
-
* `process.env`), and hashes that exact canonical environment for registry
|
|
61
|
-
* identity. Identical child material shares one server; any material
|
|
62
|
-
* difference receives a separate server.
|
|
63
|
-
*
|
|
64
|
-
* This is what removed the workflow engine's `env_unsupported` hard-fail for
|
|
65
|
-
* the sdk runner: injection genuinely reaches the child, because tool
|
|
66
|
-
* subprocesses (bash etc.) inherit the server process environment.
|
|
67
|
-
*
|
|
68
|
-
* Registry hygiene (peer-review fixes):
|
|
69
|
-
*
|
|
70
|
-
* - **Ports.** `createOpencodeServer` binds a FIXED default port (4096),
|
|
71
|
-
* so coexisting registry entries would contend for the same bind. Every
|
|
72
|
-
* entry is started on its own reserved OS-assigned port (see
|
|
73
|
-
* {@link startServer}).
|
|
74
|
-
* - **Shutdown.** {@link closeServer} closes resolved servers
|
|
75
|
-
* SYNCHRONOUSLY — it runs from `process.once('exit')`, where Bun never
|
|
76
|
-
* drains microtasks, so a `.then()`-based close would orphan every
|
|
77
|
-
* `opencode serve` child.
|
|
78
|
-
*
|
|
79
|
-
* Process-lifecycle note (owner finding 4): a cached `opencode serve` child is
|
|
80
|
-
* a live OS handle that keeps Bun's event loop OPEN, so a one-shot CLI never
|
|
81
|
-
* becomes idle and `process.once('exit')` never fires — the exit hook alone
|
|
82
|
-
* cannot free a process the child is keeping alive (a deadlock that hangs the
|
|
83
|
-
* caller after an otherwise-successful run). The registry is therefore drained
|
|
84
|
-
* PROACTIVELY at the end of a dispatching command: the workflow engine calls
|
|
85
|
-
* `disposeDispatchResources()` (→ {@link closeServer}) in composition-root and
|
|
86
|
-
* workflow `finally` blocks.
|
|
87
|
-
* The `process.once('exit')` hook stays as the last-resort backstop for paths
|
|
88
|
-
* that never reach that drain.
|
|
89
|
-
*
|
|
90
|
-
* ## Managed server spawn (owner finding 4, live-harness follow-up)
|
|
91
|
-
*
|
|
92
|
-
* Draining the registry is necessary but NOT sufficient with the SDK's own
|
|
93
|
-
* `createOpencodeServer`: its `close()` merely sends SIGTERM and it never
|
|
94
|
-
* `unref()`s the child or its stdio pipes, so akm's event loop stays pinned
|
|
95
|
-
* until the child ACTUALLY exits — and a real `opencode serve` (a live HTTP
|
|
96
|
-
* server with provider children) can outlive SIGTERM long enough to hang the
|
|
97
|
-
* caller indefinitely. {@link createManagedOpencode} therefore owns the spawn
|
|
98
|
-
* (the SDK package is used only for its client).
|
|
99
|
-
*
|
|
100
|
-
* Owning the spawn changes the LIFECYCLE, not the REQUIREMENT: the SDK's
|
|
101
|
-
* `createOpencodeServer` is itself a `spawn("opencode", ["serve", ...])`, and
|
|
102
|
-
* the package declares `"dependencies": {}`, so `opencode` must be on PATH
|
|
103
|
-
* either way. akm's version differs only in how it supervises the child:
|
|
104
|
-
*
|
|
105
|
-
* - after the URL handshake, the child and its stdio are `unref()`ed /
|
|
106
|
-
* destroyed, so the handle can never hold akm open;
|
|
107
|
-
* - `close()` sends SIGTERM and arms a bounded grace timer
|
|
108
|
-
* ({@link SERVER_KILL_GRACE_MS}) that escalates to SIGKILL and is cleared
|
|
109
|
-
* on cooperative exit, so stubborn children cannot survive parent exit and
|
|
110
|
-
* stale timers cannot signal a reused PID;
|
|
111
|
-
* - the spawn receives the immutable per-dispatch environment directly, so
|
|
112
|
-
* asynchronous factory setup cannot race a temporary process-wide overlay.
|
|
113
|
-
*/
|
|
114
|
-
import { spawn } from "node:child_process";
|
|
115
|
-
import { createHash } from "node:crypto";
|
|
116
|
-
import { COMMON_SPAWN_ENV_PASSTHROUGH, OPENCODE_ENV_PASSTHROUGH, spawnEnvNamesFor } from "../../../core/spawn-env.js";
|
|
117
|
-
import { DEFAULT_AGENT_TIMEOUT_MS } from "../../agent/config.js";
|
|
118
|
-
import { opencodeInferenceConfig } from "../opencode/model-config.js";
|
|
119
|
-
import { MODEL_WORK_OPENCODE_AGENT, modelWorkOpencodeConfig, modelWorkPluginEnv } from "../opencode/model-work-agent.js";
|
|
120
|
-
import { detectOpencodeMajor } from "../opencode/version.js";
|
|
121
|
-
import { V1_ADAPTER } from "./v1-adapter.js";
|
|
122
|
-
import { V2_ADAPTER } from "./v2-adapter.js";
|
|
123
|
-
import { parseReadiness, } from "./wire.js";
|
|
124
|
-
// Server registry — one server per complete server-material signature. Caller
|
|
125
|
-
// deadlines race the shared promise independently; they never become startup
|
|
126
|
-
// configuration inherited by later callers.
|
|
127
|
-
const _servers = new Map();
|
|
128
|
-
// Resolved servers by registry key, mirrored from `_servers` as each start
|
|
129
|
-
// promise settles. This exists so closeServer() can close started servers
|
|
130
|
-
// SYNCHRONOUSLY: it is wired to `process.once('exit')`, and Bun does not
|
|
131
|
-
// drain microtasks scheduled inside 'exit' handlers, so a `.then()`-based
|
|
132
|
-
// close never runs there and would orphan every `opencode serve` child.
|
|
133
|
-
const _resolvedServers = new Map();
|
|
134
|
-
// Listen ports handed to non-default registry entries (see startServer) —
|
|
135
|
-
// tracked so two coexisting servers in this process can never be assigned
|
|
136
|
-
// the same port.
|
|
137
|
-
const _serverPorts = new Map();
|
|
138
|
-
// Serializes probe-and-reserve so concurrent starts cannot claim one port.
|
|
139
|
-
let _portAllocationTail = Promise.resolve();
|
|
140
|
-
/** The port `createOpencodeServer` binds when none is passed (SDK 1.2.20). */
|
|
141
|
-
const DEFAULT_SDK_PORT = 4096;
|
|
142
|
-
// Test override: when set, every call uses this server (all keys) and no real
|
|
143
|
-
// server is ever started.
|
|
144
|
-
let _testServer = null;
|
|
145
|
-
// Test seam replacing the real `createOpencode` import (see __setServerFactory).
|
|
146
|
-
let _serverFactory = null;
|
|
147
|
-
let _exitHookInstalled = false;
|
|
148
|
-
/**
|
|
149
|
-
* True once `process.on("exit")` has fired. Close paths consult it because an
|
|
150
|
-
* exit handler runs after the event loop has stopped: no timer scheduled there
|
|
151
|
-
* will ever fire, so any cleanup that needs to happen must happen inline.
|
|
152
|
-
*/
|
|
153
|
-
let _processExiting = false;
|
|
154
|
-
/**
|
|
155
|
-
* Test-only seam: inject a fake {@link SdkServer} so `runOpencodeSdk` can be
|
|
156
|
-
* exercised without the real `@opencode-ai/sdk` (which would spin up a server).
|
|
157
|
-
* Pass `null` to clear. NOT part of the public runtime API — used only to
|
|
158
|
-
* assert the #564 bug fixes (systemPrompt/tools forwarding + timeout). The
|
|
159
|
-
* leading underscores mark it as internal.
|
|
160
|
-
*/
|
|
161
|
-
export function __setTestServer(server) {
|
|
162
|
-
_testServer = server;
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* Test-only seam: replace the `createOpencode` factory so the env-keyed
|
|
166
|
-
* server registry (module doc, *Per-call cwd and env*) can be exercised
|
|
167
|
-
* without the real SDK. The complete child environment is supplied as
|
|
168
|
-
* `options.env`, so async factories observe the same immutable values as the
|
|
169
|
-
* production spawn. Pass `null` to clear.
|
|
170
|
-
*/
|
|
171
|
-
export function __setServerFactory(factory) {
|
|
172
|
-
_serverFactory = factory;
|
|
173
|
-
}
|
|
174
|
-
/**
|
|
175
|
-
* Close every started OpenCode SDK server and reset the registry (and any
|
|
176
|
-
* injected test server). Used by tests for clean teardown between runs and
|
|
177
|
-
* wired to `process.once('exit')` — which is why resolved servers MUST be
|
|
178
|
-
* closed synchronously here: Bun never drains microtasks scheduled inside
|
|
179
|
-
* 'exit' handlers, so a promise-based close would silently orphan the
|
|
180
|
-
* `opencode serve` children (leaking processes AND keeping their ports
|
|
181
|
-
* bound for the next invocation).
|
|
182
|
-
*/
|
|
183
|
-
export async function closeServer() {
|
|
184
|
-
const closes = [];
|
|
185
|
-
for (const [key, entry] of _servers) {
|
|
186
|
-
const resolved = _resolvedServers.get(key);
|
|
187
|
-
if (resolved) {
|
|
188
|
-
// Synchronous close — safe from the 'exit' hook.
|
|
189
|
-
try {
|
|
190
|
-
closes.push(Promise.resolve(resolved.server.close()));
|
|
191
|
-
}
|
|
192
|
-
catch {
|
|
193
|
-
/* ignore */
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
else {
|
|
197
|
-
// Still starting: cancel the real managed spawn immediately. A custom
|
|
198
|
-
// factory may ignore the signal; the registry settlement handler closes
|
|
199
|
-
// any late result without awaiting it and pinning shutdown indefinitely.
|
|
200
|
-
entry.controller.abort();
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
_servers.clear();
|
|
204
|
-
_resolvedServers.clear();
|
|
205
|
-
_serverPorts.clear();
|
|
206
|
-
try {
|
|
207
|
-
if (_testServer)
|
|
208
|
-
closes.push(Promise.resolve(_testServer.server.close()));
|
|
209
|
-
}
|
|
210
|
-
catch {
|
|
211
|
-
/* ignore */
|
|
212
|
-
}
|
|
213
|
-
_testServer = null;
|
|
214
|
-
await Promise.allSettled(closes);
|
|
215
|
-
}
|
|
216
|
-
/**
|
|
217
|
-
* Convert an `AgentDispatchRequest.tools` policy into the SDK's tool-allowlist
|
|
218
|
-
* shape (`{ [toolName]: boolean }`).
|
|
219
|
-
*
|
|
220
|
-
* #564 bug fix (2): the tool list was previously dropped entirely. The CLI
|
|
221
|
-
* builder passes tools as a comma-separated `--allowedTools` flag; the SDK
|
|
222
|
-
* instead wants a per-tool boolean map. A list/comma-string of tool names is
|
|
223
|
-
* treated as an allowlist (each name → true). A structured policy object whose
|
|
224
|
-
* values are already booleans is forwarded as-is.
|
|
225
|
-
*
|
|
226
|
-
* Returns `undefined` when there is nothing to forward, so an absent policy
|
|
227
|
-
* leaves the SDK's own defaults untouched (behaviour-preserving for callers
|
|
228
|
-
* that pass no tools).
|
|
229
|
-
*/
|
|
230
|
-
function toolsToSdkAllowlist(tools) {
|
|
231
|
-
if (tools === undefined || tools === null)
|
|
232
|
-
return undefined;
|
|
233
|
-
const names = [];
|
|
234
|
-
if (typeof tools === "string") {
|
|
235
|
-
names.push(...tools
|
|
236
|
-
.split(",")
|
|
237
|
-
.map((t) => t.trim())
|
|
238
|
-
.filter(Boolean));
|
|
239
|
-
}
|
|
240
|
-
else if (Array.isArray(tools)) {
|
|
241
|
-
for (const t of tools) {
|
|
242
|
-
if (typeof t === "string" && t.trim())
|
|
243
|
-
names.push(t.trim());
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
else if (typeof tools === "object") {
|
|
247
|
-
// Structured policy: forward boolean entries directly.
|
|
248
|
-
const out = {};
|
|
249
|
-
for (const [k, v] of Object.entries(tools)) {
|
|
250
|
-
if (typeof v === "boolean")
|
|
251
|
-
out[k] = v;
|
|
252
|
-
}
|
|
253
|
-
return Object.keys(out).length > 0 ? out : undefined;
|
|
254
|
-
}
|
|
255
|
-
if (names.length === 0)
|
|
256
|
-
return undefined;
|
|
257
|
-
const out = {};
|
|
258
|
-
for (const n of names)
|
|
259
|
-
out[n] = true;
|
|
260
|
-
return out;
|
|
261
|
-
}
|
|
262
|
-
/** The inference a fallback LLM connection carries, which opencode can take. */
|
|
263
|
-
function fallbackInference(llmConfig) {
|
|
264
|
-
const out = {};
|
|
265
|
-
for (const key of ["temperature", "maxTokens", "contextLength", "enableThinking", "reasoningEffort"]) {
|
|
266
|
-
if (llmConfig?.[key] !== undefined)
|
|
267
|
-
out[key] = llmConfig[key];
|
|
268
|
-
}
|
|
269
|
-
return out;
|
|
270
|
-
}
|
|
271
|
-
/**
|
|
272
|
-
* Assemble the OpenCode SDK server config from the profile + LLM fallback.
|
|
273
|
-
* Pure and exported for tests. `profile.model` is already exact because model
|
|
274
|
-
* aliases resolve once before harness lowering. A server for model work also
|
|
275
|
-
* defines the confined model-work agent (`../opencode/model-work-agent`).
|
|
276
|
-
*
|
|
277
|
-
* The model the config routes through `akm-custom` is declared in full, with
|
|
278
|
-
* the dispatch's inference (`../opencode/model-config`): the fallback LLM
|
|
279
|
-
* engine's, under `requestInference`, the request's own. For model work the
|
|
280
|
-
* options go on the confined agent instead of the model, which needs no model
|
|
281
|
-
* named: it runs whichever model opencode picks. A model the user's own opencode
|
|
282
|
-
* config provides carries none: set inference there.
|
|
283
|
-
*/
|
|
284
|
-
export function buildSdkConfig(profile, llmConfig, modelWork = false, requestInference) {
|
|
285
|
-
const endpoint = llmConfig?.endpoint;
|
|
286
|
-
const apiKey = llmConfig?.apiKey;
|
|
287
|
-
const profileModel = profile.model;
|
|
288
|
-
const model = profileModel ?? llmConfig?.model;
|
|
289
|
-
const inference = requestInference === null ? undefined : { ...fallbackInference(llmConfig), ...requestInference };
|
|
290
|
-
const { entry, agentOptions } = opencodeInferenceConfig(inference, modelWork);
|
|
291
|
-
const sdkConfig = {};
|
|
292
|
-
if (model)
|
|
293
|
-
sdkConfig.model = model;
|
|
294
|
-
if (endpoint || apiKey) {
|
|
295
|
-
// The first path segment selects the OpenCode provider. Model IDs may
|
|
296
|
-
// themselves contain slashes, but still belong to this custom endpoint.
|
|
297
|
-
const modelId = model?.startsWith("akm-custom/") ? model.slice("akm-custom/".length) : model;
|
|
298
|
-
// Configure a custom OpenAI-compatible provider. OpenCode registers only
|
|
299
|
-
// the models a custom provider lists, so the routed model is declared
|
|
300
|
-
// (#1015: without it every dispatch failed with ProviderModelNotFoundError).
|
|
301
|
-
sdkConfig.provider = {
|
|
302
|
-
"akm-custom": {
|
|
303
|
-
npm: "@ai-sdk/openai-compatible",
|
|
304
|
-
options: {
|
|
305
|
-
baseURL: canonicalProviderBase(endpoint) ?? undefined,
|
|
306
|
-
...(apiKey ? { apiKey } : {}),
|
|
307
|
-
},
|
|
308
|
-
...(modelId ? { models: { [modelId]: entry } } : {}),
|
|
309
|
-
},
|
|
310
|
-
};
|
|
311
|
-
if (modelId)
|
|
312
|
-
sdkConfig.model = `akm-custom/${modelId}`;
|
|
313
|
-
}
|
|
314
|
-
return modelWork ? { ...sdkConfig, ...modelWorkOpencodeConfig(agentOptions) } : sdkConfig;
|
|
315
|
-
}
|
|
316
|
-
/** Digest the OpenCode major, the executable and the exact environment received by the child. */
|
|
317
|
-
function serverRegistryKey(adapter, profile, env) {
|
|
318
|
-
// The major is part of the identity: a v1 and a v2 server never share an entry.
|
|
319
|
-
const material = { major: adapter.major, bin: profile.bin, env };
|
|
320
|
-
return createHash("sha256")
|
|
321
|
-
.update(JSON.stringify(canonicalize(material)))
|
|
322
|
-
.digest("hex");
|
|
323
|
-
}
|
|
324
|
-
/**
|
|
325
|
-
* @internal Exact environment allowlist used to start the OpenCode SDK server:
|
|
326
|
-
* the common baseline, the OpenCode names the `opencode` CLI profile passes
|
|
327
|
-
* (`OPENCODE_ENV_PASSTHROUGH`: API key, config path, XDG base directories), and
|
|
328
|
-
* the profile's own names. They are the server's, not the profile's: there is no
|
|
329
|
-
* built-in `opencode-sdk` profile, so its `envPassthrough` is empty.
|
|
330
|
-
*/
|
|
331
|
-
export function opencodeSdkServerEnvironmentNames(profile) {
|
|
332
|
-
return [
|
|
333
|
-
...new Set([
|
|
334
|
-
...spawnEnvNamesFor(COMMON_SPAWN_ENV_PASSTHROUGH),
|
|
335
|
-
...OPENCODE_ENV_PASSTHROUGH,
|
|
336
|
-
...(profile.envPassthrough ?? []),
|
|
337
|
-
]),
|
|
338
|
-
];
|
|
339
|
-
}
|
|
340
|
-
function buildServerEnv(profile, config, bindings, envSource, modelWork) {
|
|
341
|
-
const env = {};
|
|
342
|
-
for (const key of opencodeSdkServerEnvironmentNames(profile)) {
|
|
343
|
-
const value = envSource[key];
|
|
344
|
-
if (value !== undefined)
|
|
345
|
-
env[key] = value;
|
|
346
|
-
}
|
|
347
|
-
for (const [key, value] of Object.entries(bindings ?? {}))
|
|
348
|
-
env[key] = value;
|
|
349
|
-
if (modelWork)
|
|
350
|
-
Object.assign(env, modelWorkPluginEnv());
|
|
351
|
-
env.OPENCODE_CONFIG_CONTENT = JSON.stringify(config);
|
|
352
|
-
return env;
|
|
353
|
-
}
|
|
354
|
-
function canonicalProviderBase(endpoint) {
|
|
355
|
-
if (!endpoint)
|
|
356
|
-
return null;
|
|
357
|
-
try {
|
|
358
|
-
const url = new URL(endpoint);
|
|
359
|
-
url.pathname = url.pathname.replace(/\/chat\/completions$/, "").replace(/\/$/, "");
|
|
360
|
-
return url.toString().replace(/\/$/, "");
|
|
361
|
-
}
|
|
362
|
-
catch {
|
|
363
|
-
return endpoint.replace(/\/chat\/completions$/, "").replace(/\/$/, "");
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
function canonicalize(value) {
|
|
367
|
-
if (Array.isArray(value))
|
|
368
|
-
return value.map(canonicalize);
|
|
369
|
-
if (value && typeof value === "object") {
|
|
370
|
-
return Object.fromEntries(Object.entries(value)
|
|
371
|
-
.sort(([a], [b]) => a.localeCompare(b))
|
|
372
|
-
.map(([key, child]) => [key, canonicalize(child)]));
|
|
373
|
-
}
|
|
374
|
-
return value;
|
|
375
|
-
}
|
|
376
|
-
/**
|
|
377
|
-
* Ask the OS for a currently-free localhost port (bind :0, read the assigned
|
|
378
|
-
* port, release it). Skips the SDK's fixed default port and any port already
|
|
379
|
-
* handed to another registry entry in this process, so coexisting servers
|
|
380
|
-
* never contend. The probe-then-use gap is the standard free-port race —
|
|
381
|
-
* acceptable here because the failure mode is a clean `spawn_failed` on the
|
|
382
|
-
* next dispatch, not corruption.
|
|
383
|
-
*/
|
|
384
|
-
async function allocateFreePort(registryKey) {
|
|
385
|
-
let release;
|
|
386
|
-
const previous = _portAllocationTail;
|
|
387
|
-
_portAllocationTail = new Promise((resolve) => {
|
|
388
|
-
release = resolve;
|
|
389
|
-
});
|
|
390
|
-
await previous;
|
|
391
|
-
try {
|
|
392
|
-
const { createServer } = await import("node:net");
|
|
393
|
-
const taken = new Set(_serverPorts.values());
|
|
394
|
-
for (let attempt = 0; attempt < 10; attempt++) {
|
|
395
|
-
const port = await new Promise((resolve, reject) => {
|
|
396
|
-
const probe = createServer();
|
|
397
|
-
probe.unref();
|
|
398
|
-
probe.on("error", reject);
|
|
399
|
-
probe.listen(0, "127.0.0.1", () => {
|
|
400
|
-
const address = probe.address();
|
|
401
|
-
probe.close(() => {
|
|
402
|
-
if (address && typeof address === "object")
|
|
403
|
-
resolve(address.port);
|
|
404
|
-
else
|
|
405
|
-
reject(new Error("could not read the probe socket's port"));
|
|
406
|
-
});
|
|
407
|
-
});
|
|
408
|
-
});
|
|
409
|
-
if (port !== DEFAULT_SDK_PORT && !taken.has(port)) {
|
|
410
|
-
_serverPorts.set(registryKey, port);
|
|
411
|
-
return port;
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
throw new Error("could not allocate a free port for the OpenCode SDK server");
|
|
415
|
-
}
|
|
416
|
-
finally {
|
|
417
|
-
release();
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
/** Grace between SIGTERM and SIGKILL when closing a managed server child. */
|
|
421
|
-
const SERVER_KILL_GRACE_MS = 2_000;
|
|
422
|
-
// Test seam: override the argv used to spawn the server child ("opencode"
|
|
423
|
-
// plus serve flags by default) so the managed-spawn lifecycle (handshake,
|
|
424
|
-
// unref, SIGTERM→SIGKILL escalation) is testable without the real binary.
|
|
425
|
-
let _serveCommand = null;
|
|
426
|
-
/** Test-only seam: replace the `opencode serve` argv. Pass `null` to clear. */
|
|
427
|
-
export function __setServeCommand(argv) {
|
|
428
|
-
_serveCommand = argv;
|
|
429
|
-
}
|
|
430
|
-
/**
|
|
431
|
-
* Spawn-owning replacement for the SDK's `createOpencode` (module doc,
|
|
432
|
-
* *Managed server spawn*). Mirrors `createOpencodeServer`'s contract — the
|
|
433
|
-
* `spawn` receives the factory's immutable environment directly,
|
|
434
|
-
* `OPENCODE_CONFIG_CONTENT` carries the config, the handshake parses the
|
|
435
|
-
* "opencode server listening on <url>" line — but manages the child so its
|
|
436
|
-
* handle can never pin akm's event loop:
|
|
437
|
-
*
|
|
438
|
-
* - handshake success → stdio destroyed, listeners dropped, `proc.unref()`;
|
|
439
|
-
* - `close()` → SIGTERM now, SIGKILL after an unref'ed grace timer;
|
|
440
|
-
* - handshake failure → the child is killed and unref'ed before rejecting.
|
|
441
|
-
*/
|
|
442
|
-
async function createManagedOpencode(options) {
|
|
443
|
-
const { adapter } = options;
|
|
444
|
-
const port = options.port ?? DEFAULT_SDK_PORT;
|
|
445
|
-
const argv = _serveCommand ?? adapter.serveArgv(options.bin ?? "opencode", port);
|
|
446
|
-
// The adapter may add to the child's environment (V2: the server password akm chose).
|
|
447
|
-
const authorization = adapter.authorize();
|
|
448
|
-
const proc = spawn(argv[0], argv.slice(1), {
|
|
449
|
-
env: { ...options.env, ...authorization.env },
|
|
450
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
451
|
-
});
|
|
452
|
-
let closeStarted = false;
|
|
453
|
-
let closeEscalation;
|
|
454
|
-
const childExited = () => proc.exitCode !== null || proc.signalCode !== null;
|
|
455
|
-
const clearCloseEscalation = () => {
|
|
456
|
-
if (closeEscalation !== undefined) {
|
|
457
|
-
clearTimeout(closeEscalation);
|
|
458
|
-
closeEscalation = undefined;
|
|
459
|
-
}
|
|
460
|
-
};
|
|
461
|
-
const closeManaged = () => {
|
|
462
|
-
if (closeStarted)
|
|
463
|
-
return;
|
|
464
|
-
closeStarted = true;
|
|
465
|
-
if (childExited())
|
|
466
|
-
return;
|
|
467
|
-
proc.once("exit", clearCloseEscalation);
|
|
468
|
-
try {
|
|
469
|
-
proc.kill("SIGTERM");
|
|
470
|
-
}
|
|
471
|
-
catch {
|
|
472
|
-
proc.off("exit", clearCloseEscalation);
|
|
473
|
-
return;
|
|
474
|
-
}
|
|
475
|
-
if (childExited())
|
|
476
|
-
return;
|
|
477
|
-
if (_processExiting) {
|
|
478
|
-
// During `process.on("exit")` the event loop is finished, so a timer
|
|
479
|
-
// scheduled here would never fire and the SIGKILL escalation could not
|
|
480
|
-
// happen at all — a server child that ignores SIGTERM outlived akm.
|
|
481
|
-
// Exit handlers must be synchronous, so escalate immediately instead of
|
|
482
|
-
// waiting out a grace period we have no way to wait for.
|
|
483
|
-
try {
|
|
484
|
-
proc.kill("SIGKILL");
|
|
485
|
-
}
|
|
486
|
-
catch {
|
|
487
|
-
/* already dead */
|
|
488
|
-
}
|
|
489
|
-
return;
|
|
490
|
-
}
|
|
491
|
-
closeEscalation = setTimeout(() => {
|
|
492
|
-
closeEscalation = undefined;
|
|
493
|
-
if (childExited())
|
|
494
|
-
return;
|
|
495
|
-
try {
|
|
496
|
-
proc.kill("SIGKILL");
|
|
497
|
-
}
|
|
498
|
-
catch {
|
|
499
|
-
/* already dead */
|
|
500
|
-
}
|
|
501
|
-
}, SERVER_KILL_GRACE_MS);
|
|
502
|
-
};
|
|
503
|
-
const url = await new Promise((resolve, reject) => {
|
|
504
|
-
let output = "";
|
|
505
|
-
let settled = false;
|
|
506
|
-
const cleanupStartup = () => {
|
|
507
|
-
proc.stdout?.off("data", onStdoutData);
|
|
508
|
-
proc.stderr?.off("data", onStderrData);
|
|
509
|
-
proc.off("exit", onExit);
|
|
510
|
-
proc.off("error", onError);
|
|
511
|
-
options.startupSignal.removeEventListener("abort", onAbort);
|
|
512
|
-
};
|
|
513
|
-
const fail = (err) => {
|
|
514
|
-
if (settled)
|
|
515
|
-
return;
|
|
516
|
-
settled = true;
|
|
517
|
-
cleanupStartup();
|
|
518
|
-
closeManaged();
|
|
519
|
-
proc.stdout?.destroy();
|
|
520
|
-
proc.stderr?.destroy();
|
|
521
|
-
proc.unref();
|
|
522
|
-
reject(err);
|
|
523
|
-
};
|
|
524
|
-
const succeed = (serverUrl) => {
|
|
525
|
-
if (settled)
|
|
526
|
-
return;
|
|
527
|
-
settled = true;
|
|
528
|
-
cleanupStartup();
|
|
529
|
-
resolve(serverUrl);
|
|
530
|
-
};
|
|
531
|
-
const onStdoutData = (chunk) => {
|
|
532
|
-
output += chunk.toString();
|
|
533
|
-
for (const line of output.split("\n")) {
|
|
534
|
-
const verdict = parseReadiness(line, adapter.major);
|
|
535
|
-
if (!verdict)
|
|
536
|
-
continue;
|
|
537
|
-
if ("error" in verdict)
|
|
538
|
-
fail(new Error(verdict.error));
|
|
539
|
-
else
|
|
540
|
-
succeed(verdict.url);
|
|
541
|
-
return;
|
|
542
|
-
}
|
|
543
|
-
};
|
|
544
|
-
const onStderrData = (chunk) => {
|
|
545
|
-
output += chunk.toString();
|
|
546
|
-
};
|
|
547
|
-
const onExit = (code, signal) => {
|
|
548
|
-
const status = code !== null ? `code ${code}` : `signal ${signal ?? "unknown"}`;
|
|
549
|
-
fail(new Error(`OpenCode server exited with ${status}${output.trim() ? `\nServer output: ${output}` : ""}`));
|
|
550
|
-
};
|
|
551
|
-
const onError = (err) => {
|
|
552
|
-
fail(err instanceof Error ? err : new Error(String(err)));
|
|
553
|
-
};
|
|
554
|
-
const onAbort = () => fail(new Error("OpenCode server startup cancelled because no callers are waiting"));
|
|
555
|
-
proc.stdout?.on("data", onStdoutData);
|
|
556
|
-
proc.stderr?.on("data", onStderrData);
|
|
557
|
-
proc.on("exit", onExit);
|
|
558
|
-
proc.on("error", onError);
|
|
559
|
-
if (options.startupSignal.aborted)
|
|
560
|
-
onAbort();
|
|
561
|
-
else
|
|
562
|
-
options.startupSignal.addEventListener("abort", onAbort, { once: true });
|
|
563
|
-
});
|
|
564
|
-
// Handshake done: from here on the child must never hold akm open. Its
|
|
565
|
-
// lifetime is managed explicitly (closeServer → closeManaged), not by the
|
|
566
|
-
// event loop. Destroying the pipes also releases their loop handles.
|
|
567
|
-
proc.stdout?.destroy();
|
|
568
|
-
proc.stderr?.destroy();
|
|
569
|
-
proc.unref();
|
|
570
|
-
try {
|
|
571
|
-
return { client: await adapter.connect(url, authorization.credentials), server: { close: closeManaged } };
|
|
572
|
-
}
|
|
573
|
-
catch (err) {
|
|
574
|
-
closeManaged();
|
|
575
|
-
throw err;
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
async function startServer(adapter, profile, sdkConfig, env, registryKey, startupSignal) {
|
|
579
|
-
const factory = _serverFactory ?? createManagedOpencode;
|
|
580
|
-
const options = {
|
|
581
|
-
adapter,
|
|
582
|
-
bin: profile.bin,
|
|
583
|
-
...(Object.keys(sdkConfig).length > 0 ? { config: sdkConfig } : {}),
|
|
584
|
-
env,
|
|
585
|
-
startupSignal,
|
|
586
|
-
};
|
|
587
|
-
// Every cached server receives a separately reserved port. This avoids both
|
|
588
|
-
// inter-entry contention and collisions with an unrelated process on 4096.
|
|
589
|
-
options.port = await allocateFreePort(registryKey);
|
|
590
|
-
if (startupSignal.aborted)
|
|
591
|
-
throw new Error("OpenCode server startup cancelled because no callers are waiting");
|
|
592
|
-
// Installed before the spawn, so a child that is still starting up is covered too.
|
|
593
|
-
if (!_exitHookInstalled) {
|
|
594
|
-
_exitHookInstalled = true;
|
|
595
|
-
process.once("exit", () => {
|
|
596
|
-
_processExiting = true;
|
|
597
|
-
void closeServer();
|
|
598
|
-
});
|
|
599
|
-
// A command with no SIGINT/SIGTERM handler of its own dies on the signal without running `exit` hooks, which
|
|
600
|
-
// orphaned every cached server. Close them and re-raise, so the default termination still happens. A command that
|
|
601
|
-
// handles the signal itself (`workflow run`, `improve`, `index`, task runs) unwinds through its own cleanup and is
|
|
602
|
-
// left alone.
|
|
603
|
-
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
604
|
-
if (process.listenerCount(signal) > 0)
|
|
605
|
-
continue;
|
|
606
|
-
process.once(signal, () => {
|
|
607
|
-
_processExiting = true;
|
|
608
|
-
void closeServer();
|
|
609
|
-
process.kill(process.pid, signal);
|
|
610
|
-
});
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
const server = await factory(options);
|
|
614
|
-
if (!server)
|
|
615
|
-
throw new Error("Failed to initialise OpenCode SDK server.");
|
|
616
|
-
return server;
|
|
617
|
-
}
|
|
618
|
-
/**
|
|
619
|
-
* Get (or lazily start) the server for this call's complete server material.
|
|
620
|
-
* Concurrent callers of the same key share one
|
|
621
|
-
* start (the registry stores the in-flight promise). A failed start is
|
|
622
|
-
* evicted so the next call can retry instead of caching the error forever.
|
|
623
|
-
*/
|
|
624
|
-
function getOrStartServer(adapter, profile, llmConfig, env, envSource = process.env, modelWork = false, inference) {
|
|
625
|
-
if (_testServer)
|
|
626
|
-
return { promise: Promise.resolve(_testServer), release() { } };
|
|
627
|
-
const sdkConfig = buildSdkConfig(profile, llmConfig, modelWork, inference);
|
|
628
|
-
const serverEnv = buildServerEnv(profile, sdkConfig, env, envSource, modelWork);
|
|
629
|
-
const key = serverRegistryKey(adapter, profile, serverEnv);
|
|
630
|
-
let entry = _servers.get(key);
|
|
631
|
-
if (!entry) {
|
|
632
|
-
const controller = new AbortController();
|
|
633
|
-
entry = {
|
|
634
|
-
promise: startServer(adapter, profile, sdkConfig, serverEnv, key, controller.signal),
|
|
635
|
-
controller,
|
|
636
|
-
waiters: 0,
|
|
637
|
-
};
|
|
638
|
-
const started = entry;
|
|
639
|
-
_servers.set(key, started);
|
|
640
|
-
started.promise.then((server) => {
|
|
641
|
-
if (_servers.get(key) === started) {
|
|
642
|
-
started.server = server;
|
|
643
|
-
_resolvedServers.set(key, server);
|
|
644
|
-
}
|
|
645
|
-
else {
|
|
646
|
-
void server.server.close();
|
|
647
|
-
}
|
|
648
|
-
}, () => {
|
|
649
|
-
if (_servers.get(key) === started) {
|
|
650
|
-
_servers.delete(key);
|
|
651
|
-
_serverPorts.delete(key);
|
|
652
|
-
}
|
|
653
|
-
});
|
|
654
|
-
}
|
|
655
|
-
entry.waiters++;
|
|
656
|
-
let released = false;
|
|
657
|
-
return {
|
|
658
|
-
promise: entry.promise,
|
|
659
|
-
release() {
|
|
660
|
-
if (released)
|
|
661
|
-
return;
|
|
662
|
-
released = true;
|
|
663
|
-
entry.waiters--;
|
|
664
|
-
if (entry.waiters === 0 && !entry.server && _servers.get(key) === entry) {
|
|
665
|
-
_servers.delete(key);
|
|
666
|
-
_serverPorts.delete(key);
|
|
667
|
-
entry.controller.abort();
|
|
668
|
-
}
|
|
669
|
-
},
|
|
670
|
-
};
|
|
671
|
-
}
|
|
672
|
-
const SDK_OPERATION_TIMED_OUT = Symbol("opencode-sdk-operation-timeout");
|
|
673
|
-
const SDK_OPERATION_ABORTED = Symbol("opencode-sdk-operation-aborted");
|
|
674
|
-
async function raceSdkOperation(operation, opts) {
|
|
675
|
-
let timer;
|
|
676
|
-
let onAbort;
|
|
677
|
-
const racers = [operation];
|
|
678
|
-
if (opts.timeoutMs !== null) {
|
|
679
|
-
racers.push(new Promise((resolve) => {
|
|
680
|
-
timer = opts.setTimeoutFn(() => resolve(SDK_OPERATION_TIMED_OUT), opts.timeoutMs ?? 0);
|
|
681
|
-
if (typeof timer !== "number")
|
|
682
|
-
timer.unref?.();
|
|
683
|
-
}));
|
|
684
|
-
}
|
|
685
|
-
if (opts.signal) {
|
|
686
|
-
racers.push(new Promise((resolve) => {
|
|
687
|
-
onAbort = () => resolve(SDK_OPERATION_ABORTED);
|
|
688
|
-
if (opts.signal?.aborted)
|
|
689
|
-
onAbort();
|
|
690
|
-
else
|
|
691
|
-
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
692
|
-
}));
|
|
693
|
-
}
|
|
694
|
-
try {
|
|
695
|
-
return racers.length === 1 ? await operation : await Promise.race(racers);
|
|
696
|
-
}
|
|
697
|
-
finally {
|
|
698
|
-
if (timer !== undefined)
|
|
699
|
-
opts.clearTimeoutFn(timer);
|
|
700
|
-
if (opts.signal && onAbort)
|
|
701
|
-
opts.signal.removeEventListener("abort", onAbort);
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
function errorText(err) {
|
|
705
|
-
return err instanceof Error ? err.message : String(err);
|
|
706
|
-
}
|
|
707
|
-
/**
|
|
708
|
-
* Answer every OpenCode permission request of the dispatch's session (and of
|
|
709
|
-
* the sub-sessions it spawns) with "reject", once each. This is what a
|
|
710
|
-
* non-interactive `opencode run` does ("auto-rejecting"); left unanswered, a
|
|
711
|
-
* tool call that needs permission (OpenCode's default `external_directory:
|
|
712
|
-
* ask`) stays `running` and the prompt never settles. A rejection reaches the
|
|
713
|
-
* model as a tool error it can work around. Each request and answer is
|
|
714
|
-
* appended to `notes`. The same stream reports OpenCode's retries (#1108):
|
|
715
|
-
* each is noted, and one caused by a usage limit, or due after `deadline`,
|
|
716
|
-
* ends the run through `stopForRetry` instead of a silent wait. `ready`
|
|
717
|
-
* settles once the subscription is open and never rejects; `stop` closes it.
|
|
718
|
-
* The wire format (event shapes, the reject call) is the adapter's.
|
|
719
|
-
*/
|
|
720
|
-
function rejectPermissionRequests(adapter, client, sessionId, spec, notes, deadline, stopForRetry) {
|
|
721
|
-
const controller = new AbortController();
|
|
722
|
-
const stop = () => controller.abort();
|
|
723
|
-
if (adapter.canWatch?.(client) === false)
|
|
724
|
-
return { stop };
|
|
725
|
-
const sessions = new Set([sessionId]);
|
|
726
|
-
const answered = new Set();
|
|
727
|
-
const consume = async (stream) => {
|
|
728
|
-
for await (const raw of stream) {
|
|
729
|
-
const event = adapter.decodeEvent(raw);
|
|
730
|
-
if (!event)
|
|
731
|
-
continue;
|
|
732
|
-
if (event.kind === "session") {
|
|
733
|
-
if (event.parentId !== undefined && sessions.has(event.parentId))
|
|
734
|
-
sessions.add(event.id);
|
|
735
|
-
continue;
|
|
736
|
-
}
|
|
737
|
-
if (!sessions.has(event.sessionId))
|
|
738
|
-
continue;
|
|
739
|
-
if (event.kind === "retry") {
|
|
740
|
-
const retryAt = event.next === undefined ? "unknown time" : new Date(event.next).toISOString();
|
|
741
|
-
notes.push(`OpenCode retry attempt ${event.attempt ?? "unknown"}${event.provider ? ` (${event.provider})` : ""}: ${event.message}; retrying at ${retryAt}`);
|
|
742
|
-
if (event.accountLimit) {
|
|
743
|
-
stopForRetry(`OpenCode provider limit stopped this run${event.provider ? ` (${event.provider})` : ""}: ${event.message}; reset at ${retryAt}`);
|
|
744
|
-
}
|
|
745
|
-
else if (event.next !== undefined && deadline !== null && event.next > deadline) {
|
|
746
|
-
stopForRetry(`OpenCode's next retry (${retryAt}) is after this run's deadline: ${event.message}`);
|
|
747
|
-
}
|
|
748
|
-
continue;
|
|
749
|
-
}
|
|
750
|
-
if (answered.has(event.requestId))
|
|
751
|
-
continue;
|
|
752
|
-
answered.add(event.requestId);
|
|
753
|
-
notes.push(`permission requested: ${event.description}; auto-rejecting`);
|
|
754
|
-
adapter.rejectPermission(client, event, spec).catch((err) => {
|
|
755
|
-
notes.push(`permission reply for ${event.requestId} failed: ${errorText(err)}`);
|
|
756
|
-
});
|
|
757
|
-
}
|
|
758
|
-
};
|
|
759
|
-
const ready = adapter
|
|
760
|
-
.subscribe(client, spec, controller.signal)
|
|
761
|
-
.then((stream) => {
|
|
762
|
-
void consume(stream).catch((err) => {
|
|
763
|
-
if (!controller.signal.aborted)
|
|
764
|
-
notes.push(`permission event stream ended: ${errorText(err)}`);
|
|
765
|
-
});
|
|
766
|
-
})
|
|
767
|
-
.catch((err) => {
|
|
768
|
-
notes.push(`permission requests will not be answered: event subscription failed: ${errorText(err)}`);
|
|
769
|
-
});
|
|
770
|
-
return { ready, stop };
|
|
771
|
-
}
|
|
772
|
-
const ADAPTERS = { 1: V1_ADAPTER, 2: V2_ADAPTER };
|
|
773
|
-
/** The wire adapter for the OpenCode major the engine's binary reports. */
|
|
774
|
-
function adapterFor(profile) {
|
|
775
|
-
return ADAPTERS[detectOpencodeMajor(profile.bin).major];
|
|
776
|
-
}
|
|
777
|
-
function abortedBeforeSdkStart(profile) {
|
|
778
|
-
return {
|
|
779
|
-
ok: false,
|
|
780
|
-
stdout: "",
|
|
781
|
-
stderr: "",
|
|
782
|
-
durationMs: 0,
|
|
783
|
-
exitCode: null,
|
|
784
|
-
reason: "aborted",
|
|
785
|
-
error: `opencode-sdk agent "${profile.name}" not started: caller signal already aborted`,
|
|
786
|
-
};
|
|
787
|
-
}
|
|
788
|
-
export async function runOpencodeSdk(profile, prompt, opts = {}, llmConfig) {
|
|
789
|
-
const start = Date.now();
|
|
790
|
-
const timeoutMs = opts.timeoutMs !== undefined ? opts.timeoutMs : DEFAULT_AGENT_TIMEOUT_MS;
|
|
791
|
-
const deadline = timeoutMs === null ? null : start + timeoutMs;
|
|
792
|
-
const remainingTimeoutMs = () => (deadline === null ? null : Math.max(0, deadline - Date.now()));
|
|
793
|
-
const setTimeoutImpl = opts.setTimeoutFn ?? setTimeout;
|
|
794
|
-
const clearTimeoutImpl = opts.clearTimeoutFn ?? clearTimeout;
|
|
795
|
-
if (opts.signal?.aborted)
|
|
796
|
-
return abortedBeforeSdkStart(profile);
|
|
797
|
-
const modelWork = opts.dispatch?.modelWork === true;
|
|
798
|
-
const adapter = adapterFor(profile);
|
|
799
|
-
let client;
|
|
800
|
-
if (_testServer) {
|
|
801
|
-
client = _testServer.client;
|
|
802
|
-
}
|
|
803
|
-
else {
|
|
804
|
-
const startupHandle = getOrStartServer(adapter, profile, llmConfig, opts.env, opts.envSource, modelWork, opts.dispatch?.inference);
|
|
805
|
-
try {
|
|
806
|
-
const startup = await raceSdkOperation(startupHandle.promise, {
|
|
807
|
-
timeoutMs: remainingTimeoutMs(),
|
|
808
|
-
setTimeoutFn: setTimeoutImpl,
|
|
809
|
-
clearTimeoutFn: clearTimeoutImpl,
|
|
810
|
-
signal: opts.signal,
|
|
811
|
-
});
|
|
812
|
-
if (startup === SDK_OPERATION_ABORTED) {
|
|
813
|
-
return {
|
|
814
|
-
ok: false,
|
|
815
|
-
stdout: "",
|
|
816
|
-
stderr: "",
|
|
817
|
-
durationMs: Date.now() - start,
|
|
818
|
-
exitCode: null,
|
|
819
|
-
reason: "aborted",
|
|
820
|
-
error: `opencode-sdk agent "${profile.name}" aborted by caller signal during server startup`,
|
|
821
|
-
};
|
|
822
|
-
}
|
|
823
|
-
if (startup === SDK_OPERATION_TIMED_OUT) {
|
|
824
|
-
return {
|
|
825
|
-
ok: false,
|
|
826
|
-
stdout: "",
|
|
827
|
-
stderr: "",
|
|
828
|
-
durationMs: Date.now() - start,
|
|
829
|
-
exitCode: null,
|
|
830
|
-
reason: "timeout",
|
|
831
|
-
error: `opencode-sdk agent "${profile.name}" timed out during server startup after ${timeoutMs}ms`,
|
|
832
|
-
};
|
|
833
|
-
}
|
|
834
|
-
client = startup.client;
|
|
835
|
-
}
|
|
836
|
-
catch (e) {
|
|
837
|
-
return {
|
|
838
|
-
ok: false,
|
|
839
|
-
stdout: "",
|
|
840
|
-
stderr: String(e),
|
|
841
|
-
durationMs: Date.now() - start,
|
|
842
|
-
exitCode: 1,
|
|
843
|
-
reason: "spawn_failed",
|
|
844
|
-
error: String(e),
|
|
845
|
-
};
|
|
846
|
-
}
|
|
847
|
-
finally {
|
|
848
|
-
startupHandle.release();
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
// #564 bug fix (3): enforce a hard timeout like the CLI path (runAgent).
|
|
852
|
-
// Previously runOpencodeSdk() awaited SDK calls with no timeout, so a stalled
|
|
853
|
-
// local-model endpoint or wedged server could block the caller indefinitely.
|
|
854
|
-
// The same absolute deadline covers server startup, session creation, and
|
|
855
|
-
// prompting. null disables every dispatch timer.
|
|
856
|
-
// Per-call working directory (module doc): the adapter scopes the session to
|
|
857
|
-
// it, so worktree-isolated units run in their own checkout without a
|
|
858
|
-
// per-cwd server.
|
|
859
|
-
// Forward the exact native agent selector, systemPrompt, and tools from the abstract
|
|
860
|
-
// dispatch request. Both were once accepted on AgentDispatchRequest but silently dropped
|
|
861
|
-
// on the SDK path (#564). Model work runs the confined agent its server config defines;
|
|
862
|
-
// its tools are that agent's.
|
|
863
|
-
const dispatch = opts.dispatch;
|
|
864
|
-
const agent = modelWork ? MODEL_WORK_OPENCODE_AGENT : dispatch?.agent;
|
|
865
|
-
const system = dispatch?.systemPrompt;
|
|
866
|
-
const tools = toolsToSdkAllowlist(dispatch?.tools);
|
|
867
|
-
const spec = {
|
|
868
|
-
...(opts.cwd ? { directory: opts.cwd } : {}),
|
|
869
|
-
...(agent ? { agent } : {}),
|
|
870
|
-
...(system ? { system } : {}),
|
|
871
|
-
...(tools ? { tools } : {}),
|
|
872
|
-
};
|
|
873
|
-
// One session per call — do NOT reuse (history accumulates, token costs grow).
|
|
874
|
-
// Session creation is startup plumbing, so failures map to spawn_failed rather
|
|
875
|
-
// than bubbling out as a generic workflow dispatch exception.
|
|
876
|
-
const callerSignal = opts.signal;
|
|
877
|
-
const runController = new AbortController();
|
|
878
|
-
const abortForCaller = () => runController.abort();
|
|
879
|
-
if (callerSignal?.aborted)
|
|
880
|
-
abortForCaller();
|
|
881
|
-
else
|
|
882
|
-
callerSignal?.addEventListener("abort", abortForCaller, { once: true });
|
|
883
|
-
const abortSignal = runController.signal;
|
|
884
|
-
let sessionId;
|
|
885
|
-
try {
|
|
886
|
-
const created = await raceSdkOperation(adapter.createSession(client, spec), {
|
|
887
|
-
timeoutMs: remainingTimeoutMs(),
|
|
888
|
-
setTimeoutFn: setTimeoutImpl,
|
|
889
|
-
clearTimeoutFn: clearTimeoutImpl,
|
|
890
|
-
signal: abortSignal,
|
|
891
|
-
});
|
|
892
|
-
if (created === SDK_OPERATION_ABORTED) {
|
|
893
|
-
return {
|
|
894
|
-
ok: false,
|
|
895
|
-
stdout: "",
|
|
896
|
-
stderr: "",
|
|
897
|
-
durationMs: Date.now() - start,
|
|
898
|
-
exitCode: null,
|
|
899
|
-
reason: "aborted",
|
|
900
|
-
error: `opencode-sdk agent "${profile.name}" aborted by caller signal`,
|
|
901
|
-
};
|
|
902
|
-
}
|
|
903
|
-
if (created === SDK_OPERATION_TIMED_OUT) {
|
|
904
|
-
return {
|
|
905
|
-
ok: false,
|
|
906
|
-
stdout: "",
|
|
907
|
-
stderr: "",
|
|
908
|
-
durationMs: Date.now() - start,
|
|
909
|
-
exitCode: null,
|
|
910
|
-
reason: "timeout",
|
|
911
|
-
error: `opencode-sdk agent "${profile.name}" timed out creating a session after ${timeoutMs}ms`,
|
|
912
|
-
};
|
|
913
|
-
}
|
|
914
|
-
sessionId = created;
|
|
915
|
-
}
|
|
916
|
-
catch (err) {
|
|
917
|
-
return {
|
|
918
|
-
ok: false,
|
|
919
|
-
stdout: "",
|
|
920
|
-
stderr: errorText(err),
|
|
921
|
-
durationMs: Date.now() - start,
|
|
922
|
-
exitCode: 1,
|
|
923
|
-
reason: "spawn_failed",
|
|
924
|
-
error: errorText(err),
|
|
925
|
-
};
|
|
926
|
-
}
|
|
927
|
-
if (!sessionId) {
|
|
928
|
-
return {
|
|
929
|
-
ok: false,
|
|
930
|
-
stdout: "",
|
|
931
|
-
stderr: "Failed to create session",
|
|
932
|
-
durationMs: Date.now() - start,
|
|
933
|
-
exitCode: 1,
|
|
934
|
-
reason: "spawn_failed",
|
|
935
|
-
error: "Failed to create OpenCode session",
|
|
936
|
-
};
|
|
937
|
-
}
|
|
938
|
-
let result;
|
|
939
|
-
// Subscribed before the prompt so no request can be missed; closed in `finally`.
|
|
940
|
-
const permissionNotes = [];
|
|
941
|
-
let retryFailure;
|
|
942
|
-
const permissions = rejectPermissionRequests(adapter, client, sessionId, spec, permissionNotes, deadline, (failure) => {
|
|
943
|
-
retryFailure ??= failure;
|
|
944
|
-
runController.abort();
|
|
945
|
-
});
|
|
946
|
-
try {
|
|
947
|
-
// Bounded like every other step; if it times out the prompt race below does too.
|
|
948
|
-
if (permissions.ready) {
|
|
949
|
-
await raceSdkOperation(permissions.ready, {
|
|
950
|
-
timeoutMs: remainingTimeoutMs(),
|
|
951
|
-
setTimeoutFn: setTimeoutImpl,
|
|
952
|
-
clearTimeoutFn: clearTimeoutImpl,
|
|
953
|
-
signal: abortSignal,
|
|
954
|
-
});
|
|
955
|
-
}
|
|
956
|
-
const prompted = await raceSdkOperation(adapter.prompt(client, sessionId, prompt, spec, abortSignal), {
|
|
957
|
-
timeoutMs: remainingTimeoutMs(),
|
|
958
|
-
setTimeoutFn: setTimeoutImpl,
|
|
959
|
-
clearTimeoutFn: clearTimeoutImpl,
|
|
960
|
-
signal: abortSignal,
|
|
961
|
-
});
|
|
962
|
-
// A session the dispatch stops early is aborted on the server too.
|
|
963
|
-
if (prompted === SDK_OPERATION_ABORTED || prompted === SDK_OPERATION_TIMED_OUT) {
|
|
964
|
-
adapter.abort(client, sessionId, spec);
|
|
965
|
-
}
|
|
966
|
-
if (prompted === SDK_OPERATION_ABORTED && retryFailure) {
|
|
967
|
-
result = {
|
|
968
|
-
ok: false,
|
|
969
|
-
stdout: "",
|
|
970
|
-
stderr: retryFailure,
|
|
971
|
-
durationMs: Date.now() - start,
|
|
972
|
-
exitCode: 1,
|
|
973
|
-
reason: "llm_rate_limit",
|
|
974
|
-
error: retryFailure,
|
|
975
|
-
sessionId,
|
|
976
|
-
};
|
|
977
|
-
}
|
|
978
|
-
else if (prompted === SDK_OPERATION_ABORTED) {
|
|
979
|
-
result = {
|
|
980
|
-
ok: false,
|
|
981
|
-
stdout: "",
|
|
982
|
-
stderr: "",
|
|
983
|
-
durationMs: Date.now() - start,
|
|
984
|
-
exitCode: null,
|
|
985
|
-
reason: "aborted",
|
|
986
|
-
error: `opencode-sdk agent "${profile.name}" aborted by caller signal`,
|
|
987
|
-
sessionId,
|
|
988
|
-
};
|
|
989
|
-
}
|
|
990
|
-
else if (prompted === SDK_OPERATION_TIMED_OUT) {
|
|
991
|
-
result = {
|
|
992
|
-
ok: false,
|
|
993
|
-
stdout: "",
|
|
994
|
-
stderr: "",
|
|
995
|
-
durationMs: Date.now() - start,
|
|
996
|
-
exitCode: null,
|
|
997
|
-
reason: "timeout",
|
|
998
|
-
error: `opencode-sdk agent "${profile.name}" timed out after ${timeoutMs}ms`,
|
|
999
|
-
sessionId,
|
|
1000
|
-
};
|
|
1001
|
-
}
|
|
1002
|
-
else {
|
|
1003
|
-
const { text: stdout, usage, failure } = prompted;
|
|
1004
|
-
if (failure) {
|
|
1005
|
-
result = {
|
|
1006
|
-
ok: false,
|
|
1007
|
-
stdout,
|
|
1008
|
-
stderr: failure.message,
|
|
1009
|
-
durationMs: Date.now() - start,
|
|
1010
|
-
exitCode: failure.reason === "aborted" ? null : 1,
|
|
1011
|
-
reason: failure.reason,
|
|
1012
|
-
error: failure.message,
|
|
1013
|
-
sessionId,
|
|
1014
|
-
...(usage ? { usage } : {}),
|
|
1015
|
-
};
|
|
1016
|
-
}
|
|
1017
|
-
else {
|
|
1018
|
-
result = {
|
|
1019
|
-
ok: true,
|
|
1020
|
-
stdout,
|
|
1021
|
-
stderr: "",
|
|
1022
|
-
durationMs: Date.now() - start,
|
|
1023
|
-
exitCode: 0,
|
|
1024
|
-
sessionId,
|
|
1025
|
-
...(usage ? { usage } : {}),
|
|
1026
|
-
};
|
|
1027
|
-
}
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
catch (err) {
|
|
1031
|
-
result = {
|
|
1032
|
-
ok: false,
|
|
1033
|
-
stdout: "",
|
|
1034
|
-
stderr: errorText(err),
|
|
1035
|
-
durationMs: Date.now() - start,
|
|
1036
|
-
exitCode: 1,
|
|
1037
|
-
reason: "non_zero_exit",
|
|
1038
|
-
error: errorText(err),
|
|
1039
|
-
sessionId,
|
|
1040
|
-
};
|
|
1041
|
-
}
|
|
1042
|
-
finally {
|
|
1043
|
-
permissions.stop();
|
|
1044
|
-
callerSignal?.removeEventListener("abort", abortForCaller);
|
|
1045
|
-
// The dispatch is over: cancel any request still in flight.
|
|
1046
|
-
runController.abort();
|
|
1047
|
-
}
|
|
1048
|
-
if (permissionNotes.length > 0) {
|
|
1049
|
-
result.stderr = [result.stderr, ...permissionNotes].filter(Boolean).join("\n");
|
|
1050
|
-
}
|
|
1051
|
-
return result;
|
|
1052
|
-
}
|