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
|
@@ -8,14 +8,20 @@ import { isDeepStrictEqual } from "node:util";
|
|
|
8
8
|
import { isBundleSlug } from "../asset/asset-ref.js";
|
|
9
9
|
import { deriveBundleId } from "../bundle-id.js";
|
|
10
10
|
import { isRecord, resolveStashDir } from "../common.js";
|
|
11
|
-
import { ConfigError } from "../errors.js";
|
|
11
|
+
import { ConfigError, UsageError } from "../errors.js";
|
|
12
|
+
import { isRefToken, substituteRefTokens } from "../ref-token.js";
|
|
12
13
|
import { formatRegistryLabel, hasRegistryUrlCredentials } from "../registry-url.js";
|
|
13
14
|
import { acquireConfigLock, backupExistingConfig, parseConfigText, readConfigText, withConfigLock, writeConfigAtomic, } from "./config-io.js";
|
|
14
15
|
import { AkmConfigSchema, CURRENT_CONFIG_VERSION } from "./config-schema.js";
|
|
15
16
|
import { bundleComponentConfig, bundleContentRoot, bundleContentRoots, bundleSourceId, bundlesToSourceEntries, filesystemBundleSourceId, isBundleEnabled, } from "./config-sources.js";
|
|
16
|
-
import { resolveSchemaAt } from "./config-walker.js";
|
|
17
|
+
import { isOpaqueSchema, resolveSchemaAt } from "./config-walker.js";
|
|
18
|
+
import { convertEngineConfig } from "./convert-engines.js";
|
|
19
|
+
import { fileFlatCredentials, writeCredential } from "./credentials.js";
|
|
17
20
|
import { deepMergeConfig, isPlainObject } from "./deep-merge.js";
|
|
18
|
-
import {
|
|
21
|
+
import { documentEndpoint, planKeyMoves } from "./migrate-keys.js";
|
|
22
|
+
import { modelsFileExists, readModelsFile, retireModelsFile } from "./models-file.js";
|
|
23
|
+
import { rememberEngineTargets, rememberedEngineTargets } from "./retired-engines.js";
|
|
24
|
+
import { dropUnusableEntries, findUnusableEntries } from "./unusable-entries.js";
|
|
19
25
|
export { stripJsonComments } from "./config-io.js";
|
|
20
26
|
import { getConfigPath } from "../paths.js";
|
|
21
27
|
import { warn, warnOnce } from "../warn.js";
|
|
@@ -39,6 +45,24 @@ let cachedConfig;
|
|
|
39
45
|
export function resetConfigCache() {
|
|
40
46
|
cachedConfig = undefined;
|
|
41
47
|
}
|
|
48
|
+
const INHERITED_CONNECTIONS = Symbol("inheritedConnections");
|
|
49
|
+
/**
|
|
50
|
+
* The connections only an `extends` base defines in `config`: this host's file names none of them. A base
|
|
51
|
+
* chooses such a connection's endpoint, so it never uses a key stored on this host: keys are for the
|
|
52
|
+
* connections this host's own config file defines. The loader attaches the set to the config it returns
|
|
53
|
+
* (not enumerable, so a copy made with spread has none), and a config that did not come from the loader
|
|
54
|
+
* has none inherited.
|
|
55
|
+
*/
|
|
56
|
+
export function inheritedConnectionsOf(config) {
|
|
57
|
+
return config[INHERITED_CONNECTIONS] ?? new Set();
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* {@link inheritedConnectionsOf} the config as it is now on this host, loaded here when the cache holds
|
|
61
|
+
* nothing: for a caller with no config in hand.
|
|
62
|
+
*/
|
|
63
|
+
export function isInheritedConnection(name) {
|
|
64
|
+
return inheritedConnectionsOf(loadUserConfig()).has(name);
|
|
65
|
+
}
|
|
42
66
|
export function loadUserConfig() {
|
|
43
67
|
const configPath = getConfigPath();
|
|
44
68
|
let stat;
|
|
@@ -72,7 +96,10 @@ export function loadUserConfig() {
|
|
|
72
96
|
cachedConfig = undefined;
|
|
73
97
|
return { ...DEFAULT_CONFIG };
|
|
74
98
|
}
|
|
75
|
-
const
|
|
99
|
+
const lifted = runConfigFilePipeline(text, configPath);
|
|
100
|
+
const finalConfig = buildEffectiveConfig(lifted, configPath);
|
|
101
|
+
const written = isPlainConfigObject(lifted.connections) ? lifted.connections : {};
|
|
102
|
+
const inheritedConnections = new Set(Object.keys(finalConfig.connections ?? {}).filter((name) => !Object.hasOwn(written, name)));
|
|
76
103
|
// Re-stat after potential write-back so the cache key reflects the new mtime.
|
|
77
104
|
let finalStat = stat;
|
|
78
105
|
try {
|
|
@@ -81,6 +108,7 @@ export function loadUserConfig() {
|
|
|
81
108
|
catch {
|
|
82
109
|
// Stat failed — use original stat for cache; no harm done.
|
|
83
110
|
}
|
|
111
|
+
Object.defineProperty(finalConfig, INHERITED_CONNECTIONS, { value: inheritedConnections });
|
|
84
112
|
cachedConfig = {
|
|
85
113
|
config: finalConfig,
|
|
86
114
|
path: configPath,
|
|
@@ -92,8 +120,9 @@ export function loadUserConfig() {
|
|
|
92
120
|
/**
|
|
93
121
|
* Run the per-file config pipeline every raw config object goes through
|
|
94
122
|
* before it is either validated (the local/top-level file) or merged in as
|
|
95
|
-
* an `extends` base: JSONC parse already done by the caller, then the
|
|
96
|
-
* `
|
|
123
|
+
* an `extends` base: JSONC parse already done by the caller, then the retired
|
|
124
|
+
* `engines` form read as `connections`/`harnesses` ({@link readRetiredEngines})
|
|
125
|
+
* and the `configVersion` read ({@link readConfigVersion}). Unknown keys are not an
|
|
97
126
|
* error: the schema drops them in memory. A shape older than 0.9.15 is not
|
|
98
127
|
* converted here; it fails validation with a message naming `akm migrate
|
|
99
128
|
* apply` under akm 0.9.x. Shared by {@link parseAndValidateConfigText} (the
|
|
@@ -102,7 +131,113 @@ export function loadUserConfig() {
|
|
|
102
131
|
* the file that extends it.
|
|
103
132
|
*/
|
|
104
133
|
function runConfigFilePipeline(text, sourcePath) {
|
|
105
|
-
return
|
|
134
|
+
return readRetiredEngines(readConfigFile(text, sourcePath), sourcePath);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* A config file as it says it: parsed, with the retired `secret://` forms and
|
|
138
|
+
* `configVersion` read, and nothing else converted. An ordinary write edits this
|
|
139
|
+
* document, so what a read converts in memory (the retired `engines` form) is
|
|
140
|
+
* not rewritten until `akm migrate apply`.
|
|
141
|
+
*/
|
|
142
|
+
function readConfigFile(text, sourcePath) {
|
|
143
|
+
const raw = parseConfigText(text, sourcePath);
|
|
144
|
+
const lifted = readRetiredSecretRefs(raw, [], sourcePath);
|
|
145
|
+
return readConfigVersion(lifted, sourcePath);
|
|
146
|
+
}
|
|
147
|
+
/** The overlay every file read folds in, unless the migrator is reading an `extends` base without it. */
|
|
148
|
+
let modelsOverlayOverride;
|
|
149
|
+
function modelsOverlay() {
|
|
150
|
+
return modelsOverlayOverride ? modelsOverlayOverride.overlay : readModelsFile();
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* The connections and harnesses an `extends` base defines on its own, without the
|
|
154
|
+
* models.json overlay the loader folds into them: what the migrator folds into
|
|
155
|
+
* entries only the base defines, as partial local entries, once the file is gone.
|
|
156
|
+
*/
|
|
157
|
+
function inheritedEntries(document, configPath) {
|
|
158
|
+
if (document.extends === undefined)
|
|
159
|
+
return undefined;
|
|
160
|
+
modelsOverlayOverride = { overlay: undefined };
|
|
161
|
+
try {
|
|
162
|
+
const base = buildEffectiveConfig({ configVersion: document.configVersion, extends: document.extends }, configPath);
|
|
163
|
+
return { connections: { ...base.connections }, harnesses: { ...base.harnesses } };
|
|
164
|
+
}
|
|
165
|
+
finally {
|
|
166
|
+
modelsOverlayOverride = undefined;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* What each old engine name now is, so an `engine:` selector (a flag, or a key in an asset or a task source)
|
|
171
|
+
* keeps meaning the service it always named: the conversion renames the engine `defaults` named to `default`
|
|
172
|
+
* and moves an old `default` aside. While the file is still in the `engines` form the answer is the
|
|
173
|
+
* conversion's own; after `akm migrate apply` has rewritten it, the names the migrator remembered, until
|
|
174
|
+
* every reference it can rewrite is rewritten. Resolution and the migrator both ask here.
|
|
175
|
+
*/
|
|
176
|
+
export function retiredEngineTargets() {
|
|
177
|
+
const configPath = getConfigPath();
|
|
178
|
+
const text = readConfigText(configPath);
|
|
179
|
+
if (text === undefined)
|
|
180
|
+
return {};
|
|
181
|
+
const live = convertEngineConfig(readConfigFile(text, configPath), {
|
|
182
|
+
modelsOverlay: modelsOverlay(),
|
|
183
|
+
env: process.env,
|
|
184
|
+
}).targets;
|
|
185
|
+
return Object.keys(live).length > 0 ? live : rememberedEngineTargets();
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* `engines` / `defaults` are retired in favour of `connections` / `harnesses`.
|
|
189
|
+
* A config in the old form is read as the new one, in memory, with one warning;
|
|
190
|
+
* `akm migrate apply` writes it back (it runs {@link convertEngineConfig} too,
|
|
191
|
+
* through this pipeline, before it deletes the keys the schema does not know).
|
|
192
|
+
*/
|
|
193
|
+
function readRetiredEngines(raw, sourcePath) {
|
|
194
|
+
const { config, notes, changed, keys } = convertEngineConfig(raw, {
|
|
195
|
+
modelsOverlay: modelsOverlay(),
|
|
196
|
+
env: process.env,
|
|
197
|
+
});
|
|
198
|
+
if (!changed)
|
|
199
|
+
return raw;
|
|
200
|
+
const keyNotes = keys.map(({ engine, field }) => `engines.${engine}: its ${field} is not read from config any more; \`akm migrate apply\` moves it to credentials.json.`);
|
|
201
|
+
const retiredForm = raw.engines !== undefined || raw.defaults !== undefined;
|
|
202
|
+
warnOnce(`config:retired-engines:${sourcePath ?? "inline"}`, `${sourcePath ?? "config.json"} ${retiredForm
|
|
203
|
+
? "uses the retired engines/defaults form; reading it as connections/harnesses"
|
|
204
|
+
: "is read with the retired models.json folded into its models"}. \`akm migrate apply\` rewrites config.json${retiredForm ? "" : " and moves models.json aside"}.${[...notes, ...keyNotes].map((note) => `\n - ${note}`).join("")}`);
|
|
205
|
+
return config;
|
|
206
|
+
}
|
|
207
|
+
/** The retired `secret://<name>` credential form; it survives only in {@link readRetiredSecretRefs}. */
|
|
208
|
+
const SECRET_STORE_REFERENCE_PATTERN = /^secret:\/\/(.+)$/;
|
|
209
|
+
/**
|
|
210
|
+
* The `${ref}` a retired `secret://<x>` value reads as: `${<x>}` when `<x>` is
|
|
211
|
+
* already a full ref, else `${secrets/<x>}`. `undefined` for any other string.
|
|
212
|
+
* Shared by the config reader and the frozen workflow runner decoder.
|
|
213
|
+
*/
|
|
214
|
+
export function liftRetiredSecretRef(value) {
|
|
215
|
+
const name = SECRET_STORE_REFERENCE_PATTERN.exec(value)?.[1];
|
|
216
|
+
if (name === undefined)
|
|
217
|
+
return undefined;
|
|
218
|
+
return isRefToken(`\${${name}}`) ? `\${${name}}` : `\${secrets/${name}}`;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* `secret://<x>` is retired in favour of `${ref}`. A value in that form is read
|
|
222
|
+
* as its {@link liftRetiredSecretRef} with one warning naming the key; every
|
|
223
|
+
* ordinary config write and `akm migrate apply`'s `configFile` step then
|
|
224
|
+
* persist the new form.
|
|
225
|
+
*/
|
|
226
|
+
function readRetiredSecretRefs(node, keyPath, sourcePath) {
|
|
227
|
+
if (typeof node === "string") {
|
|
228
|
+
const lifted = liftRetiredSecretRef(node);
|
|
229
|
+
if (lifted === undefined)
|
|
230
|
+
return node;
|
|
231
|
+
const dotted = keyPath.join(".");
|
|
232
|
+
warnOnce(`config:secret-scheme:${sourcePath ?? "inline"}:${dotted}`, `${dotted}${sourcePath ? ` at ${sourcePath}` : ""} uses the retired ${JSON.stringify(node)} form; reading it as ${JSON.stringify(lifted)}. \`akm migrate apply\` rewrites config.json.`);
|
|
233
|
+
return lifted;
|
|
234
|
+
}
|
|
235
|
+
if (Array.isArray(node))
|
|
236
|
+
return node.map((child, i) => readRetiredSecretRefs(child, [...keyPath, String(i)], sourcePath));
|
|
237
|
+
if (isPlainConfigObject(node)) {
|
|
238
|
+
return Object.fromEntries(Object.entries(node).map(([key, child]) => [key, readRetiredSecretRefs(child, [...keyPath, key], sourcePath)]));
|
|
239
|
+
}
|
|
240
|
+
return node;
|
|
106
241
|
}
|
|
107
242
|
/**
|
|
108
243
|
* `configVersion` is read, never gated on. `"0.9.0"` is the only value akm
|
|
@@ -131,7 +266,11 @@ function readConfigVersion(raw, sourcePath) {
|
|
|
131
266
|
* keeps around, instead of only getting the final merged `AkmConfig` back.
|
|
132
267
|
*/
|
|
133
268
|
function buildEffectiveConfig(liftedLocalRaw, sourcePath) {
|
|
134
|
-
const
|
|
269
|
+
const chained = dropUnusableEntries(resolveExtendsChain(liftedLocalRaw, sourcePath), sourcePath);
|
|
270
|
+
// Writing an `embedding` block says semantic search is wanted: a config that names no mode reads as "auto".
|
|
271
|
+
const withExtends = chained.embedding !== undefined && chained.semanticSearchMode === undefined
|
|
272
|
+
? { ...chained, semanticSearchMode: "auto" }
|
|
273
|
+
: chained;
|
|
135
274
|
const where = sourcePath ? ` at ${sourcePath}` : "";
|
|
136
275
|
warnUnknownConfigKeys(liftedLocalRaw, sourcePath);
|
|
137
276
|
const parsed = AkmConfigSchema.safeParse(withExtends);
|
|
@@ -153,8 +292,8 @@ function buildEffectiveConfig(liftedLocalRaw, sourcePath) {
|
|
|
153
292
|
* per-pass catchall schema (`IndexPassConfigSchema`), so `resolveSchemaAt`
|
|
154
293
|
* below never returns `undefined` for them and the walk would otherwise
|
|
155
294
|
* treat them as a live, ordinary pass. Named here so the same three
|
|
156
|
-
* guarantees apply as any other retired config key
|
|
157
|
-
*
|
|
295
|
+
* guarantees apply as any other retired config key: a config setting one keeps
|
|
296
|
+
* loading, is named once by
|
|
158
297
|
* the unknown-key warning, and is dropped only by `akm migrate apply`.
|
|
159
298
|
* `index.graph` (the LLM entity-graph extraction pass) and
|
|
160
299
|
* `index.metadataEnhance` (LLM metadata enrichment) were retired in
|
|
@@ -172,10 +311,14 @@ export function unknownConfigKeyPaths(root, node = root, prefix = []) {
|
|
|
172
311
|
const found = [];
|
|
173
312
|
for (const key of Object.keys(node).sort()) {
|
|
174
313
|
const keyPath = [...prefix, key];
|
|
175
|
-
|
|
314
|
+
const schema = resolveSchemaAt(keyPath, root);
|
|
315
|
+
if (RETIRED_CATCHALL_KEY_PATHS.has(keyPath.join(".")) || schema === undefined) {
|
|
176
316
|
found.push(keyPath);
|
|
177
317
|
continue;
|
|
178
318
|
}
|
|
319
|
+
// A request field on a connection (`chat_template_kwargs`, ...) is the endpoint's, not akm's: its contents are not keys akm knows.
|
|
320
|
+
if (isOpaqueSchema(schema))
|
|
321
|
+
continue;
|
|
179
322
|
const value = node[key];
|
|
180
323
|
if (isPlainConfigObject(value))
|
|
181
324
|
found.push(...unknownConfigKeyPaths(root, value, keyPath));
|
|
@@ -291,7 +434,6 @@ const HOST_LOCAL_CONFIG_KEYS = new Set([
|
|
|
291
434
|
"defaultBundle",
|
|
292
435
|
"defaultWriteTarget",
|
|
293
436
|
"embedding",
|
|
294
|
-
"execution",
|
|
295
437
|
"experimental",
|
|
296
438
|
"registries",
|
|
297
439
|
"scheduler",
|
|
@@ -312,27 +454,30 @@ function sanitizeInheritedConfig(raw, label) {
|
|
|
312
454
|
delete inherited[key];
|
|
313
455
|
warnOnce(`config:inherited-host-local:${label}:${key}`, `Ignoring inherited config key ${JSON.stringify(key)} from ${label}; it is host-local and must be declared in the top-level config file.`);
|
|
314
456
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
457
|
+
let strippedAuthority = false;
|
|
458
|
+
for (const section of ["connections", "harnesses"]) {
|
|
459
|
+
const entries = inherited[section];
|
|
460
|
+
if (!isPlainObject(entries))
|
|
461
|
+
continue;
|
|
462
|
+
const portableEntries = {};
|
|
463
|
+
for (const [name, entry] of Object.entries(entries)) {
|
|
464
|
+
if (!isPlainObject(entry)) {
|
|
465
|
+
portableEntries[name] = entry;
|
|
321
466
|
continue;
|
|
322
467
|
}
|
|
323
|
-
const portable = { ...
|
|
324
|
-
for (const key of ["apiKey", "
|
|
468
|
+
const portable = { ...entry };
|
|
469
|
+
for (const key of ["apiKey", "bin", "args", "workspace"]) {
|
|
325
470
|
if (!Object.hasOwn(portable, key))
|
|
326
471
|
continue;
|
|
327
472
|
delete portable[key];
|
|
328
473
|
strippedAuthority = true;
|
|
329
474
|
}
|
|
330
|
-
|
|
331
|
-
}
|
|
332
|
-
inherited.engines = engines;
|
|
333
|
-
if (strippedAuthority) {
|
|
334
|
-
warnOnce(`config:inherited-host-local:${label}:engines-authority`, `Ignoring inherited engine credentials, executable arguments, or workspace from ${label}; those fields are host-local.`);
|
|
475
|
+
portableEntries[name] = portable;
|
|
335
476
|
}
|
|
477
|
+
inherited[section] = portableEntries;
|
|
478
|
+
}
|
|
479
|
+
if (strippedAuthority) {
|
|
480
|
+
warnOnce(`config:inherited-host-local:${label}:connections-authority`, `Ignoring inherited connection credentials, harness executable arguments, or workspace from ${label}; those fields are host-local.`);
|
|
336
481
|
}
|
|
337
482
|
if (isPlainObject(inherited.search) && Object.hasOwn(inherited.search, "curateRerank")) {
|
|
338
483
|
const { curateRerank: _curateRerank, ...portableSearch } = inherited.search;
|
|
@@ -359,7 +504,7 @@ function sanitizeInheritedConfig(raw, label) {
|
|
|
359
504
|
}
|
|
360
505
|
return inherited;
|
|
361
506
|
}
|
|
362
|
-
/** `~` expands to the home directory
|
|
507
|
+
/** `~` expands to the home directory. */
|
|
363
508
|
function expandExtendsHomePath(p) {
|
|
364
509
|
return p.startsWith("~") ? path.join(os.homedir(), p.slice(1)) : p;
|
|
365
510
|
}
|
|
@@ -524,6 +669,39 @@ export function getEffectiveRegistries(config) {
|
|
|
524
669
|
export function getImproveProcessConfig(processName, selected) {
|
|
525
670
|
return selected?.processes?.[processName];
|
|
526
671
|
}
|
|
672
|
+
/** The entry named `default`, else the lone entry, else none. */
|
|
673
|
+
function defaultEntry(entries) {
|
|
674
|
+
if (!entries)
|
|
675
|
+
return undefined;
|
|
676
|
+
const names = Object.keys(entries);
|
|
677
|
+
const name = Object.hasOwn(entries, "default") ? "default" : names.length === 1 ? names[0] : undefined;
|
|
678
|
+
return name === undefined ? undefined : { name, entry: entries[name] };
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* What is wrong when connections or harnesses are configured but none of a kind is the default: several, and
|
|
682
|
+
* none called `default`. The phrase (no final period) names the fix. Undefined when every kind that is configured has a default.
|
|
683
|
+
*/
|
|
684
|
+
export function ambiguousDefaultAdvice(config) {
|
|
685
|
+
const advice = ["connection", "harness"].flatMap((kind) => {
|
|
686
|
+
const names = Object.keys((kind === "connection" ? config.connections : config.harnesses) ?? {});
|
|
687
|
+
if (names.length < 2 || names.includes("default"))
|
|
688
|
+
return [];
|
|
689
|
+
return [
|
|
690
|
+
`Several ${kind}s are configured (${names.join(", ")}) and none is named "default": name one default in config.json, or pick one with --${kind}`,
|
|
691
|
+
];
|
|
692
|
+
});
|
|
693
|
+
return advice.length > 0 ? advice.join("; ") : undefined;
|
|
694
|
+
}
|
|
695
|
+
/** The default connection: the one named `default`, else the only one. */
|
|
696
|
+
export function defaultConnection(config) {
|
|
697
|
+
const found = defaultEntry(config.connections);
|
|
698
|
+
return found ? { name: found.name, connection: found.entry } : undefined;
|
|
699
|
+
}
|
|
700
|
+
/** The default harness: the one named `default`, else the only one. */
|
|
701
|
+
export function defaultHarness(config) {
|
|
702
|
+
const found = defaultEntry(config.harnesses);
|
|
703
|
+
return found ? { name: found.name, harness: found.entry } : undefined;
|
|
704
|
+
}
|
|
527
705
|
export function loadConfig() {
|
|
528
706
|
return loadUserConfig();
|
|
529
707
|
}
|
|
@@ -552,9 +730,9 @@ export function validateCompleteConfig(config) {
|
|
|
552
730
|
"If this looks like an akm bug, re-run with --debug to attach the traceback.");
|
|
553
731
|
}
|
|
554
732
|
/**
|
|
555
|
-
*
|
|
556
|
-
*
|
|
557
|
-
*
|
|
733
|
+
* `mutateConfig` must not write the entire extends-merged *effective* config
|
|
734
|
+
* (`next`) back to the local file on every `config set`/`unset`: that would
|
|
735
|
+
* duplicate every inherited `connections`/`improve.strategies`
|
|
558
736
|
* field into the local file on the very first ordinary write after adopting
|
|
559
737
|
* `extends` — defeating the feature (shared config, ≤20-line local files)
|
|
560
738
|
* and silently freezing the local copy against future upstream changes.
|
|
@@ -590,10 +768,102 @@ function pruneUnchangedInheritedFields(before, after, localRaw) {
|
|
|
590
768
|
return result;
|
|
591
769
|
}
|
|
592
770
|
/**
|
|
593
|
-
*
|
|
594
|
-
*
|
|
595
|
-
*
|
|
596
|
-
*
|
|
771
|
+
* Apply the edit `before` → `after` to `target` (the file's own document) and
|
|
772
|
+
* nothing else: a key that is the same on both sides is left as the file has it,
|
|
773
|
+
* whatever shape that is, so an unrelated write neither converts nor drops what
|
|
774
|
+
* the reader only reads (the retired `engines` form, an inherited field, a key
|
|
775
|
+
* the schema does not know). Only the paths that changed are written, at the same
|
|
776
|
+
* path in `target`; an object the file lacks gets only the changed part. The
|
|
777
|
+
* keys of a nested map follow `after`, which is how a mutation says where it
|
|
778
|
+
* inserted one (`bundles`); the top level keeps the file's authored order. A
|
|
779
|
+
* removal of a key the document does not hold is left undone and recorded in
|
|
780
|
+
* `unremoved`, with the path of the key.
|
|
781
|
+
*/
|
|
782
|
+
function applyEdit(target, before, after, unremoved = [], at = []) {
|
|
783
|
+
for (const key of Object.keys(before)) {
|
|
784
|
+
if (Object.hasOwn(after, key))
|
|
785
|
+
continue;
|
|
786
|
+
if (Object.hasOwn(target, key))
|
|
787
|
+
delete target[key];
|
|
788
|
+
else
|
|
789
|
+
unremoved.push([...at, key]);
|
|
790
|
+
}
|
|
791
|
+
for (const [key, value] of Object.entries(after)) {
|
|
792
|
+
const was = before[key];
|
|
793
|
+
if (isDeepStrictEqual(value, was))
|
|
794
|
+
continue;
|
|
795
|
+
const existing = target[key];
|
|
796
|
+
// An entry the reader dropped (a connection with no endpoint) is absent from
|
|
797
|
+
// `before` but present in the file: an edit to it adds to what the file has.
|
|
798
|
+
const merges = was === undefined ? isPlainObject(existing) : isPlainObject(was);
|
|
799
|
+
if (!isPlainObject(value) || !merges || (existing !== undefined && !isPlainObject(existing))) {
|
|
800
|
+
target[key] = value;
|
|
801
|
+
continue;
|
|
802
|
+
}
|
|
803
|
+
const part = existing ?? {};
|
|
804
|
+
applyEdit(part, isPlainObject(was) ? was : {}, value, unremoved, [...at, key]);
|
|
805
|
+
followKeyOrder(part, value);
|
|
806
|
+
if (existing !== undefined || Object.keys(part).length > 0)
|
|
807
|
+
target[key] = part;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
/** Put the keys of `target` in the order of `order`, those it does not name after. */
|
|
811
|
+
function followKeyOrder(target, order) {
|
|
812
|
+
const keys = [
|
|
813
|
+
...Object.keys(order).filter((key) => Object.hasOwn(target, key)),
|
|
814
|
+
...Object.keys(target).filter((key) => !Object.hasOwn(order, key)),
|
|
815
|
+
];
|
|
816
|
+
const entries = keys.map((key) => [key, target[key]]);
|
|
817
|
+
for (const key of Object.keys(target))
|
|
818
|
+
delete target[key];
|
|
819
|
+
for (const [key, value] of entries)
|
|
820
|
+
target[key] = value;
|
|
821
|
+
}
|
|
822
|
+
/**
|
|
823
|
+
* What an ordinary `mutateConfig`/`mutateConfigWithPrecommit` write persists:
|
|
824
|
+
* the file's own document with the edit `current` → `next` applied
|
|
825
|
+
* ({@link applyEdit}). Lifecycle mutations must not serialize every schema
|
|
826
|
+
* default merely because validation materialized it in memory (#972), nor the
|
|
827
|
+
* fields that only came along from `extends` (#945).
|
|
828
|
+
*/
|
|
829
|
+
function editedConfigFile(document, current, next, persistTopLevelKeys = [], unremoved = []) {
|
|
830
|
+
// A first write keeps the established full-default scaffold. Besides being
|
|
831
|
+
// useful to a new user, the add -> rejected-install -> remove lifecycle
|
|
832
|
+
// relies on that symmetry to return a pristine install to DEFAULT_CONFIG.
|
|
833
|
+
if (document === undefined)
|
|
834
|
+
return next;
|
|
835
|
+
// Keys stay in their authored order and new ones follow in `next` order: the
|
|
836
|
+
// add -> remove lifecycle writes the same logical object twice and must come
|
|
837
|
+
// back byte for byte.
|
|
838
|
+
const body = structuredClone(document);
|
|
839
|
+
applyEdit(body, current, next, unremoved);
|
|
840
|
+
// A caller may need to persist an explicit user choice even when it equals the
|
|
841
|
+
// schema default. Interactive setup uses this for semanticSearchMode: "off"
|
|
842
|
+
// means the user declined the opt-in, not an incidental default.
|
|
843
|
+
for (const key of persistTopLevelKeys) {
|
|
844
|
+
if (Object.hasOwn(next, key))
|
|
845
|
+
body[key] = next[key];
|
|
846
|
+
}
|
|
847
|
+
body.configVersion = CURRENT_CONFIG_VERSION;
|
|
848
|
+
return body;
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* A file still in the retired `engines` form holds `engines` and `defaults`, not
|
|
852
|
+
* the `connections`, `harnesses` and `improve` keys the reader makes of them, so
|
|
853
|
+
* removing one of those is a change the file cannot take: the next load would
|
|
854
|
+
* bring it back. Refuse it, once, until `akm migrate apply` has converted the file.
|
|
855
|
+
*/
|
|
856
|
+
function refuseRemovalFromRetiredForm(unremoved, document, converted) {
|
|
857
|
+
const fromRetiredForm = unremoved.filter((keyPath) => hasRawPath(converted, [...keyPath]) && !hasRawPath(document, [...keyPath]));
|
|
858
|
+
if (fromRetiredForm.length === 0)
|
|
859
|
+
return;
|
|
860
|
+
throw new UsageError(`Cannot remove ${fromRetiredForm.map((keyPath) => keyPath.join(".")).join(", ")}: it comes from the retired engines form or models.json, which this config.json still reads. ` +
|
|
861
|
+
"Run `akm migrate apply` to convert the file, then repeat the change.");
|
|
862
|
+
}
|
|
863
|
+
/**
|
|
864
|
+
* What `akm migrate apply` persists: the file read as the current shape
|
|
865
|
+
* (the retired `engines` form converted, the schema's own normalizations), only
|
|
866
|
+
* the changed-or-already-local fields.
|
|
597
867
|
*/
|
|
598
868
|
function configWriteBody(localRaw, current, next, persistTopLevelKeys = []) {
|
|
599
869
|
// A first write keeps the established full-default scaffold. Besides being
|
|
@@ -627,12 +897,52 @@ function configWriteBody(localRaw, current, next, persistTopLevelKeys = []) {
|
|
|
627
897
|
}
|
|
628
898
|
return ordered;
|
|
629
899
|
}
|
|
900
|
+
/**
|
|
901
|
+
* The connection or harness that `key` (`connections.<name>[.…]`,
|
|
902
|
+
* `harnesses.<name>[.…]`) is inside, when the reader drops it: this file holds it
|
|
903
|
+
* and nothing, an `extends` base included, completes it. `akm config` cannot edit
|
|
904
|
+
* an entry it cannot see. `inFile` says whether the file itself holds the entry
|
|
905
|
+
* under that name, or it comes from the retired `engines` form.
|
|
906
|
+
*/
|
|
907
|
+
export function unusableEntryAt(key) {
|
|
908
|
+
const [group, name] = key.split(".");
|
|
909
|
+
if (group !== "connections" && group !== "harnesses")
|
|
910
|
+
return undefined;
|
|
911
|
+
const configPath = getConfigPath();
|
|
912
|
+
const text = readConfigText(configPath);
|
|
913
|
+
if (text === undefined)
|
|
914
|
+
return undefined;
|
|
915
|
+
const document = readConfigFile(text, configPath);
|
|
916
|
+
const localRaw = readRetiredEngines(document, configPath);
|
|
917
|
+
const entry = findUnusableEntries(localRaw).find((found) => found.name === name && found.kind === (group === "connections" ? "connection" : "harness"));
|
|
918
|
+
if (!entry || Object.hasOwn(buildEffectiveConfig(localRaw, configPath)[group] ?? {}, entry.name))
|
|
919
|
+
return undefined;
|
|
920
|
+
return { ...entry, group, inFile: isRecord(document[group]) && Object.hasOwn(document[group], entry.name) };
|
|
921
|
+
}
|
|
922
|
+
/**
|
|
923
|
+
* A connection or harness the reader drops stays in the file exactly as written,
|
|
924
|
+
* with a note on what to fix. One this file only half says, which an `extends`
|
|
925
|
+
* base completes, is not dropped (it is in `current`) and gets no note.
|
|
926
|
+
*/
|
|
927
|
+
function keepUnusableEntries(body, localRaw, current, notes) {
|
|
928
|
+
for (const { kind, name, why } of findUnusableEntries(localRaw)) {
|
|
929
|
+
const holder = kind === "connection" ? "connections" : "harnesses";
|
|
930
|
+
if (Object.hasOwn(current[holder] ?? {}, name))
|
|
931
|
+
continue;
|
|
932
|
+
const entries = isRecord(body[holder]) ? body[holder] : {};
|
|
933
|
+
body[holder] = { ...entries, [name]: localRaw[holder][name] };
|
|
934
|
+
notes.push(`${holder}.${name}: kept as written, and ignored by akm because ${why}. Fix it, or remove it.`);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
630
937
|
/**
|
|
631
938
|
* The migrator's one config step. The reader already tolerates every shape
|
|
632
939
|
* 0.9.15 and later wrote (`configVersion` read as current, 0.9.16
|
|
633
940
|
* `{kind, ref, sourceId}` scheduler entries, unknown keys dropped); this
|
|
634
|
-
* writes
|
|
635
|
-
*
|
|
941
|
+
* writes the current shape back to `config.json` so the tolerance becomes
|
|
942
|
+
* durable: the retired `engines` form converted to `connections` / `harnesses`,
|
|
943
|
+
* every key akm no longer reads from config filed in `credentials.json`
|
|
944
|
+
* ({@link planKeyMoves}), and a connection or harness the reader drops kept as
|
|
945
|
+
* written. Reports without writing unless `apply` is set.
|
|
636
946
|
*/
|
|
637
947
|
export function normalizeConfigFile(configPath, options) {
|
|
638
948
|
return withConfigLock(() => {
|
|
@@ -640,7 +950,20 @@ export function normalizeConfigFile(configPath, options) {
|
|
|
640
950
|
if (text === undefined)
|
|
641
951
|
return { keys: [], changed: false, applied: false };
|
|
642
952
|
const raw = parseConfigText(text, configPath);
|
|
643
|
-
const
|
|
953
|
+
const document = readConfigFile(text, configPath);
|
|
954
|
+
// The conversion runs before the unknown-key sweep below deletes what the
|
|
955
|
+
// schema does not know (inventory surprise 8).
|
|
956
|
+
const overlay = readModelsFile();
|
|
957
|
+
const inherited = overlay === undefined ? undefined : inheritedEntries(document, configPath);
|
|
958
|
+
const converted = convertEngineConfig(document, {
|
|
959
|
+
modelsOverlay: overlay,
|
|
960
|
+
env: process.env,
|
|
961
|
+
...(inherited ? { inherited } : {}),
|
|
962
|
+
});
|
|
963
|
+
const plan = planKeyMoves(document, converted, {
|
|
964
|
+
resolveSecretRef: (ref) => resolveSecret(ref, options.resolveFromStore),
|
|
965
|
+
});
|
|
966
|
+
const localRaw = plan.config;
|
|
644
967
|
const current = buildEffectiveConfig(localRaw, configPath);
|
|
645
968
|
const next = validateCompleteConfig({ ...current, configVersion: CURRENT_CONFIG_VERSION });
|
|
646
969
|
const body = configWriteBody(localRaw, current, next);
|
|
@@ -656,15 +979,53 @@ export function normalizeConfigFile(configPath, options) {
|
|
|
656
979
|
const strategies = body.improve?.strategies;
|
|
657
980
|
if (strategies && Object.hasOwn(strategies, "graph-refresh"))
|
|
658
981
|
delete strategies["graph-refresh"];
|
|
982
|
+
// A key in the older flat form is filed only under an endpoint this file itself gives the block.
|
|
983
|
+
const flat = fileFlatCredentials((slot) => documentEndpoint(plan.config, slot), false, plan.moves);
|
|
984
|
+
const notes = [...converted.notes, ...plan.notes, ...flat.notes];
|
|
985
|
+
keepUnusableEntries(body, localRaw, current, notes);
|
|
986
|
+
const hasModelsFile = modelsFileExists();
|
|
987
|
+
if (hasModelsFile) {
|
|
988
|
+
notes.push("models.json: its aliases are folded in where they name a configured connection or harness (the ones that do not are listed above), then the file moves to the config-backups directory.");
|
|
989
|
+
}
|
|
659
990
|
const keys = [...new Set([...Object.keys(raw), ...Object.keys(body)])]
|
|
660
991
|
.filter((key) => JSON.stringify(raw[key]) !== JSON.stringify(body[key]))
|
|
661
992
|
.sort();
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
993
|
+
const reported = {
|
|
994
|
+
keys,
|
|
995
|
+
...(plan.moves.length > 0 || flat.filed.length > 0
|
|
996
|
+
? { credentials: [...plan.moves.map((move) => move.name), ...flat.filed] }
|
|
997
|
+
: {}),
|
|
998
|
+
...(notes.length > 0 ? { notes } : {}),
|
|
999
|
+
...(Object.keys(converted.targets).length > 0 ? { engineTargets: converted.targets } : {}),
|
|
1000
|
+
};
|
|
1001
|
+
const changed = keys.length > 0 || hasModelsFile || flat.filed.length > 0;
|
|
1002
|
+
if (!changed || !options.apply)
|
|
1003
|
+
return { ...reported, changed, applied: false };
|
|
1004
|
+
let backup;
|
|
1005
|
+
const credentialsBackupPath = flat.filed.length > 0
|
|
1006
|
+
? fileFlatCredentials((slot) => documentEndpoint(plan.config, slot), true, plan.moves).backupPath
|
|
1007
|
+
: undefined;
|
|
1008
|
+
if (keys.length > 0) {
|
|
1009
|
+
// The keys are filed before the file that holds them is rewritten; a store
|
|
1010
|
+
// that cannot take them (malformed, unwritable) throws here and loses none.
|
|
1011
|
+
for (const move of plan.moves)
|
|
1012
|
+
writeCredential(move.slot, move.value, move.endpoint);
|
|
1013
|
+
// The old engine names outlive the file that had them, until the references to them are migrated.
|
|
1014
|
+
if (Object.keys(converted.targets).length > 0)
|
|
1015
|
+
rememberEngineTargets(converted.targets);
|
|
1016
|
+
backup = backupExistingConfig(configPath);
|
|
1017
|
+
writeConfigAtomic(configPath, body);
|
|
1018
|
+
cachedConfig = undefined;
|
|
1019
|
+
}
|
|
1020
|
+
const modelsFileBackupPath = hasModelsFile ? retireModelsFile() : undefined;
|
|
1021
|
+
return {
|
|
1022
|
+
...reported,
|
|
1023
|
+
changed: true,
|
|
1024
|
+
applied: true,
|
|
1025
|
+
...(backup ? { backupPath: backup.timestamped } : {}),
|
|
1026
|
+
...(credentialsBackupPath ? { credentialsBackupPath } : {}),
|
|
1027
|
+
...(modelsFileBackupPath ? { modelsFileBackupPath } : {}),
|
|
1028
|
+
};
|
|
668
1029
|
});
|
|
669
1030
|
}
|
|
670
1031
|
/**
|
|
@@ -679,16 +1040,20 @@ export function mutateConfig(mutate, options) {
|
|
|
679
1040
|
if (text === undefined && options?.absentNoop) {
|
|
680
1041
|
return { config: { ...DEFAULT_CONFIG }, written: false };
|
|
681
1042
|
}
|
|
682
|
-
const
|
|
1043
|
+
const document = text === undefined ? undefined : readConfigFile(text, configPath);
|
|
1044
|
+
const localRaw = document === undefined ? undefined : readRetiredEngines(document, configPath);
|
|
683
1045
|
const current = localRaw === undefined ? { ...DEFAULT_CONFIG } : buildEffectiveConfig(localRaw, configPath);
|
|
684
1046
|
const mutated = mutate(current);
|
|
685
1047
|
if (mutated === current)
|
|
686
1048
|
return { config: current, written: false };
|
|
687
1049
|
const next = validateCompleteConfig({ ...mutated, configVersion: CURRENT_CONFIG_VERSION });
|
|
1050
|
+
const unremoved = [];
|
|
1051
|
+
const body = editedConfigFile(document, current, next, [], unremoved);
|
|
1052
|
+
refuseRemovalFromRetiredForm(unremoved, document, localRaw);
|
|
688
1053
|
if (text !== undefined)
|
|
689
1054
|
backupExistingConfig(configPath);
|
|
690
1055
|
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
691
|
-
writeConfigAtomic(configPath, sanitizeConfigForWrite(
|
|
1056
|
+
writeConfigAtomic(configPath, sanitizeConfigForWrite(body));
|
|
692
1057
|
return { config: next, written: true };
|
|
693
1058
|
});
|
|
694
1059
|
}
|
|
@@ -703,17 +1068,22 @@ export async function mutateConfigWithPrecommit(mutate, precommit, options) {
|
|
|
703
1068
|
const release = acquireConfigLock();
|
|
704
1069
|
try {
|
|
705
1070
|
const text = readConfigText(configPath);
|
|
706
|
-
const
|
|
1071
|
+
const document = text === undefined ? undefined : readConfigFile(text, configPath);
|
|
1072
|
+
const localRaw = document === undefined ? undefined : readRetiredEngines(document, configPath);
|
|
707
1073
|
const current = localRaw === undefined ? { ...DEFAULT_CONFIG } : buildEffectiveConfig(localRaw, configPath);
|
|
708
1074
|
const mutated = mutate(current);
|
|
709
1075
|
const next = validateCompleteConfig({ ...mutated, configVersion: CURRENT_CONFIG_VERSION });
|
|
1076
|
+
const unremoved = [];
|
|
1077
|
+
const body = editedConfigFile(document, current, next, options?.persistTopLevelKeys, unremoved);
|
|
1078
|
+
if (mutated !== current)
|
|
1079
|
+
refuseRemovalFromRetiredForm(unremoved, document, localRaw);
|
|
710
1080
|
if (text !== undefined)
|
|
711
1081
|
backupExistingConfig(configPath);
|
|
712
1082
|
const precommitResult = await precommit(next);
|
|
713
1083
|
if (mutated === current)
|
|
714
1084
|
return { config: current, written: false, precommit: precommitResult };
|
|
715
1085
|
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
716
|
-
writeConfigAtomic(configPath, sanitizeConfigForWrite(
|
|
1086
|
+
writeConfigAtomic(configPath, sanitizeConfigForWrite(body));
|
|
717
1087
|
return { config: next, written: true, precommit: precommitResult };
|
|
718
1088
|
}
|
|
719
1089
|
finally {
|
|
@@ -740,57 +1110,16 @@ export function schedulerSourceIdFor(config, bundleId) {
|
|
|
740
1110
|
}
|
|
741
1111
|
}
|
|
742
1112
|
/**
|
|
743
|
-
*
|
|
744
|
-
* API keys are expected to come from environment variables
|
|
745
|
-
* (AKM_EMBED_API_KEY, AKM_LLM_API_KEY, AKM_ENGINE_<NAME>_API_KEY).
|
|
746
|
-
*
|
|
747
|
-
* `${VAR}` / `$VAR` references are preserved — they are not secrets, they
|
|
748
|
-
* are deferred lookups resolved at consumption by `resolveSecret`. Dropping
|
|
749
|
-
* them would break the documented config-on-disk pattern.
|
|
1113
|
+
* Drop what must not reach `config.json` before it is written.
|
|
750
1114
|
*
|
|
751
|
-
*
|
|
752
|
-
*
|
|
753
|
-
*
|
|
754
|
-
*
|
|
755
|
-
*
|
|
756
|
-
* whether a write would drop anything and writes nothing itself.
|
|
1115
|
+
* A literal `apiKey` is not dropped: akm never reads or sends it (see
|
|
1116
|
+
* `envReferenceApiKey`), but it is the user's, and `akm migrate apply` is what
|
|
1117
|
+
* moves it. Only registry URLs carrying credentials are removed here.
|
|
1118
|
+
* `quiet` skips the warnings for a caller that only asks whether a write would
|
|
1119
|
+
* drop anything and writes nothing itself.
|
|
757
1120
|
*/
|
|
758
1121
|
export function sanitizeConfigForWrite(config, options) {
|
|
759
1122
|
const sanitized = { ...config };
|
|
760
|
-
const stripped = [];
|
|
761
|
-
if (config.embedding?.apiKey !== undefined) {
|
|
762
|
-
const apiKey = config.embedding.apiKey;
|
|
763
|
-
if (isApiKeyReference(apiKey)) {
|
|
764
|
-
// Preserve reference verbatim — not a secret.
|
|
765
|
-
sanitized.embedding = { ...config.embedding };
|
|
766
|
-
}
|
|
767
|
-
else {
|
|
768
|
-
const { apiKey: _drop, ...rest } = config.embedding;
|
|
769
|
-
sanitized.embedding = rest;
|
|
770
|
-
if (apiKey)
|
|
771
|
-
stripped.push("embedding.apiKey (set AKM_EMBED_API_KEY to provide at runtime)");
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
else if (config.embedding) {
|
|
775
|
-
sanitized.embedding = { ...config.embedding };
|
|
776
|
-
}
|
|
777
|
-
if (config.engines) {
|
|
778
|
-
const engines = {};
|
|
779
|
-
for (const [name, engine] of Object.entries(config.engines)) {
|
|
780
|
-
if (engine.kind !== "llm" || engine.apiKey === undefined || isApiKeyReference(engine.apiKey)) {
|
|
781
|
-
engines[name] = { ...engine };
|
|
782
|
-
continue;
|
|
783
|
-
}
|
|
784
|
-
const { apiKey: _drop, ...rest } = engine;
|
|
785
|
-
engines[name] = rest;
|
|
786
|
-
if (engine.apiKey)
|
|
787
|
-
stripped.push(`engines.${name}.apiKey`);
|
|
788
|
-
}
|
|
789
|
-
sanitized.engines = engines;
|
|
790
|
-
}
|
|
791
|
-
if (stripped.length > 0 && !options?.quiet) {
|
|
792
|
-
warn(`Config sanitizer dropped API key(s) before writing to disk:\n - ${stripped.join("\n - ")}\n\nakm does not persist API keys to config.json. Set the listed environment variables to provide them at runtime, or use \`\${VAR}\` references in your config to defer lookup. See docs/reference/data-and-telemetry.md.`);
|
|
793
|
-
}
|
|
794
1123
|
if (config.registries) {
|
|
795
1124
|
const droppedRegistries = [];
|
|
796
1125
|
const registries = config.registries.filter((entry) => {
|
|
@@ -812,10 +1141,10 @@ export function updateConfig(partial) {
|
|
|
812
1141
|
}
|
|
813
1142
|
/**
|
|
814
1143
|
* Resolve a single secret value: expand `${VAR}` / `$VAR` against
|
|
815
|
-
* `process.env`,
|
|
816
|
-
* at apiKey / authorization-header consumption
|
|
817
|
-
* agent SDK runner) — NOT on the load path.
|
|
818
|
-
* unchanged.
|
|
1144
|
+
* `process.env`, then substitute `${ref}` tokens (`${secrets/name}`) through
|
|
1145
|
+
* `resolveFromStore`. Use this at apiKey / authorization-header consumption
|
|
1146
|
+
* sites (LLM client, embedder, agent SDK runner) — NOT on the load path.
|
|
1147
|
+
* Non-string inputs pass through unchanged.
|
|
819
1148
|
*
|
|
820
1149
|
* Returns the input unchanged when no substitution markers are present, so
|
|
821
1150
|
* literal API key strings (already-resolved secrets) are zero-cost.
|
|
@@ -824,26 +1153,18 @@ export function updateConfig(partial) {
|
|
|
824
1153
|
* preserved verbatim on read — only fields explicitly routed through this
|
|
825
1154
|
* helper are expanded.
|
|
826
1155
|
*
|
|
827
|
-
* A `
|
|
828
|
-
*
|
|
829
|
-
* credential.
|
|
1156
|
+
* A `${ref}` that fails to resolve throws `ConfigError` (naming the ref, never
|
|
1157
|
+
* the secret) rather than silently sending an unusable credential.
|
|
830
1158
|
*/
|
|
831
1159
|
export function resolveSecret(value, resolveFromStore) {
|
|
832
1160
|
if (value === undefined)
|
|
833
1161
|
return undefined;
|
|
834
1162
|
if (typeof value !== "string")
|
|
835
1163
|
return value;
|
|
836
|
-
const storeRef = SECRET_STORE_REFERENCE_PATTERN.exec(value)?.[1];
|
|
837
|
-
if (storeRef !== undefined) {
|
|
838
|
-
const resolved = resolveFromStore?.(storeRef) ?? null;
|
|
839
|
-
if (resolved === null) {
|
|
840
|
-
throw new ConfigError(`Secret store reference "${value}" did not resolve to a stored value.`, "SECRET_REFERENCE_UNRESOLVED");
|
|
841
|
-
}
|
|
842
|
-
return resolved;
|
|
843
|
-
}
|
|
844
1164
|
if (!value.includes("$"))
|
|
845
1165
|
return value;
|
|
846
|
-
|
|
1166
|
+
// Env expansion runs first so a secret value containing `$` is never re-expanded.
|
|
1167
|
+
const expanded = value.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g, (_match, braced, bare) => {
|
|
847
1168
|
const name = (braced ?? bare);
|
|
848
1169
|
const resolved = process.env[name];
|
|
849
1170
|
if (!resolved) {
|
|
@@ -854,6 +1175,14 @@ export function resolveSecret(value, resolveFromStore) {
|
|
|
854
1175
|
}
|
|
855
1176
|
return resolved ?? "";
|
|
856
1177
|
});
|
|
1178
|
+
const { values, missing } = substituteRefTokens({ value: expanded }, (ref) => {
|
|
1179
|
+
const stored = resolveFromStore?.(ref) ?? null;
|
|
1180
|
+
return stored === null ? undefined : { kind: "secret", value: stored };
|
|
1181
|
+
});
|
|
1182
|
+
if (missing.length > 0) {
|
|
1183
|
+
throw new ConfigError(`Secret store reference "\${${missing[0]}}" did not resolve to a stored value.`, "SECRET_REFERENCE_UNRESOLVED");
|
|
1184
|
+
}
|
|
1185
|
+
return values.value;
|
|
857
1186
|
}
|
|
858
1187
|
// Re-export source runtime helpers — implementation lives in config-sources.ts.
|
|
859
1188
|
export { bundleComponentConfig, bundleContentRoot, bundleContentRoots, bundleEntryToSourceEntry, bundleKeyForContentRoot, bundlePhysicalContentRoot, bundleSourceId, bundlesToSourceEntries, installedSourceDescriptor, isBundleEnabled, parseSourceSpec, primaryBundlePath, resolveActiveConfiguredSources, resolveConfiguredSources, } from "./config-sources.js";
|