akm-cli 0.9.0-rc.9 → 0.9.1-beta.1
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 +1770 -44
- package/README.md +38 -47
- package/SECURITY.md +14 -1
- package/STABILITY.md +499 -0
- package/dist/akm +148 -35
- package/dist/{akm-migrate-storage → akm-migrate} +6 -9
- package/dist/assets/hints/cli-hints-full.md +223 -95
- package/dist/assets/hints/cli-hints-short.md +85 -22
- package/dist/assets/improve-strategies/default.json +1 -1
- package/dist/assets/improve-strategies/reflect-distill.json +1 -1
- package/dist/assets/prompts/memory-infer-user.md +2 -3
- package/dist/assets/stash-skeleton/README.md +6 -5
- package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/domains.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/organization.md +20 -9
- package/dist/assets/tasks/core/extract.yml +1 -1
- package/dist/assets/tasks/core/version-check.yml +1 -1
- package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +5 -0
- package/dist/assets/tasks/improve/akm-improve-catchup.yml +8 -0
- package/dist/assets/tasks/improve/akm-improve-consolidate.yml +5 -0
- package/dist/assets/tasks/improve/akm-improve-frequent.yml +5 -0
- package/dist/assets/tasks/improve/akm-improve-nightly.yml +5 -0
- package/dist/assets/templates/html/health.html +1 -3
- package/dist/assets/workflows/workflow-template.md +32 -15
- package/dist/cli/invocation.js +40 -15
- package/dist/cli/parse-args.js +0 -22
- package/dist/cli/retired-commands.js +121 -0
- package/dist/cli/shared.js +154 -22
- package/dist/cli/unknown-flags.js +236 -0
- package/dist/cli-node.mjs +2 -1
- package/dist/cli.js +696 -258
- package/dist/commands/agent/agent-dispatch.js +14 -3
- package/dist/commands/agent/contribute-cli.js +74 -89
- package/dist/commands/completions.js +79 -22
- package/dist/commands/config-cli.js +17 -150
- package/dist/commands/env/env-cli.js +59 -143
- package/dist/commands/env/env.js +12 -163
- package/dist/commands/env/marker-path.js +6 -0
- package/dist/commands/env/secret-cli.js +36 -66
- package/dist/commands/env/secret.js +24 -57
- package/dist/commands/feedback-cli.js +148 -88
- package/dist/commands/health/accept-rate.js +58 -0
- package/dist/commands/health/advisories.js +3 -4
- package/dist/commands/health/checks.js +85 -23
- package/dist/commands/health/html-report.js +7 -10
- package/dist/commands/health/improve-metrics.js +25 -83
- package/dist/commands/health/llm-usage.js +2 -1
- package/dist/commands/health/md-report.js +5 -9
- package/dist/commands/health/metrics.js +62 -20
- package/dist/commands/health/renderers.js +47 -0
- package/dist/commands/health/report-view-model.js +4 -5
- package/dist/commands/health/stash-exposure.js +1 -1
- package/dist/commands/health/surfaces.js +4 -122
- package/dist/commands/health/task-runs.js +3 -67
- package/dist/commands/health/types-improve.js +7 -0
- package/dist/commands/health.js +160 -35
- package/dist/commands/improve/anti-collapse.js +2 -2
- package/dist/commands/improve/autonomy-gate.js +68 -0
- package/dist/commands/improve/collapse-detector.js +41 -40
- package/dist/commands/improve/consolidate/eligibility.js +1 -23
- package/dist/commands/improve/consolidate/merge.js +4 -0
- package/dist/commands/improve/consolidate.js +140 -1000
- package/dist/commands/improve/distill/promote-memory.js +12 -12
- package/dist/commands/improve/distill/quality-gate.js +11 -6
- package/dist/commands/improve/distill.js +58 -69
- package/dist/commands/improve/eligibility.js +101 -55
- package/dist/commands/improve/extract-cli.js +14 -133
- package/dist/commands/improve/improve-cli.js +98 -114
- package/dist/commands/improve/improve-result-file.js +1 -28
- package/dist/commands/improve/improve-strategies.js +8 -5
- package/dist/commands/improve/improve.js +254 -101
- package/dist/commands/improve/locks.js +7 -0
- package/dist/commands/improve/loop-stages.js +182 -20
- package/dist/commands/improve/memory/derived-ref.js +45 -43
- package/dist/commands/improve/memory/memory-belief.js +1 -1
- package/dist/commands/improve/memory/memory-contradiction-detect.js +4 -12
- package/dist/commands/improve/memory/memory-improve.js +14 -5
- package/dist/commands/improve/outcome-loop.js +22 -65
- package/dist/commands/improve/preparation.js +114 -123
- package/dist/commands/improve/proactive-maintenance.js +2 -5
- package/dist/commands/improve/reflect.js +56 -160
- package/dist/commands/improve/run-context.js +5 -0
- package/dist/commands/improve/salience.js +11 -122
- package/dist/commands/improve/session-asset.js +4 -0
- package/dist/commands/improve/source-identity.js +10 -38
- package/dist/commands/lint/base-linter.js +51 -131
- package/dist/commands/lint/env-key-rules.js +31 -47
- package/dist/commands/lint/index.js +444 -88
- package/dist/commands/lint/types.js +22 -1
- package/dist/commands/{events.js → log.js} +33 -38
- package/dist/commands/migrate-cli.js +92 -12
- package/dist/commands/migration-tool.js +46 -0
- package/dist/commands/observability-cli.js +70 -209
- package/dist/commands/proposal/drain.js +101 -29
- package/dist/commands/proposal/proposal-cli.js +76 -48
- package/dist/commands/proposal/proposal.js +54 -18
- package/dist/commands/proposal/propose-cli.js +88 -0
- package/dist/commands/proposal/propose.js +23 -15
- package/dist/commands/proposal/repository.js +718 -279
- package/dist/commands/proposal/validators/proposal-quality-validators.js +2 -8
- package/dist/commands/proposal/validators/proposal-validators.js +55 -7
- package/dist/commands/proposal/validators/proposals.js +4 -7
- package/dist/commands/read/curate.js +34 -53
- package/dist/commands/read/knowledge.js +150 -95
- package/dist/commands/read/registry-search.js +2 -2
- package/dist/commands/read/remember-cli.js +42 -15
- package/dist/commands/read/search-cli.js +180 -78
- package/dist/commands/read/search.js +58 -43
- package/dist/commands/read/show.js +197 -141
- package/dist/commands/registry-cli.js +12 -51
- package/dist/commands/remember.js +14 -57
- package/dist/commands/sources/add-cli.js +100 -31
- package/dist/commands/sources/bundle-cli.js +166 -0
- package/dist/commands/sources/bundle-config-ops.js +7 -2
- package/dist/commands/sources/info.js +30 -7
- package/dist/commands/sources/init.js +12 -12
- package/dist/commands/sources/installed-stashes.js +387 -98
- package/dist/commands/sources/schema-repair.js +3 -2
- package/dist/commands/sources/self-update.js +131 -38
- package/dist/commands/sources/source-add.js +72 -17
- package/dist/commands/sources/source-clone.js +129 -45
- package/dist/commands/sources/source-manage.js +43 -23
- package/dist/commands/sources/sources-cli.js +57 -208
- package/dist/commands/sources/stash-cli.js +46 -53
- package/dist/commands/tasks/tasks-cli.js +91 -97
- package/dist/commands/tasks/tasks.js +284 -423
- package/dist/commands/workflow-cli.js +181 -450
- package/dist/core/abort-deadline.js +28 -0
- package/dist/core/adapter/adapters/agent-skills-adapter.js +67 -5
- package/dist/core/adapter/adapters/akm-adapter.js +60 -38
- package/dist/core/adapter/adapters/akm-lint.js +109 -42
- package/dist/core/adapter/adapters/akm-metadata.js +15 -44
- package/dist/core/adapter/adapters/akm-task-adapter.js +54 -31
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +55 -71
- package/dist/core/adapter/adapters/dotenv-adapter.js +1 -1
- package/dist/core/adapter/adapters/generic-files-adapter.js +2 -0
- package/dist/core/adapter/adapters/index.js +6 -6
- package/dist/core/adapter/adapters/llm-wiki-adapter.js +14 -8
- package/dist/core/adapter/adapters/okf-adapter.js +187 -19
- package/dist/core/adapter/adapters/shared.js +3 -19
- package/dist/core/adapter/adapters/tool-dir-shared.js +13 -6
- package/dist/core/adapter/adapters/website-snapshot-adapter.js +1 -0
- package/dist/core/adapter/detect-adapter.js +17 -0
- package/dist/core/adapter/recognize-match.js +6 -4
- package/dist/core/adapter/validate-context.js +214 -0
- package/dist/core/asset/akm-markdown.js +63 -0
- package/dist/core/asset/asset-placement.js +20 -6
- package/dist/core/asset/asset-ref.js +11 -9
- package/dist/core/asset/frontmatter-lint.js +30 -0
- package/dist/core/asset/frontmatter.js +29 -9
- package/dist/core/asset/markdown.js +40 -51
- package/dist/core/asset/resolve-ref.js +89 -18
- package/dist/core/asset/stash-meta.js +1 -1
- package/dist/core/bundle-id.js +51 -0
- package/dist/core/common.js +218 -44
- package/dist/core/concurrent.js +32 -0
- package/dist/core/config/config-io.js +12 -1
- package/dist/core/config/config-schema.js +35 -8
- package/dist/core/config/config-sources.js +55 -11
- package/dist/core/config/config-walker.js +25 -9
- package/dist/core/config/config.js +9 -48
- package/dist/core/config/experimental.js +21 -0
- package/dist/core/config/schema/embedding.js +5 -1
- package/dist/core/config/schema/experimental.js +30 -0
- package/dist/core/config/schema/improve-processes.js +0 -6
- package/dist/core/config/schema/improve.js +21 -3
- package/dist/core/config/schema/index-config.js +8 -15
- package/dist/core/config/schema/output.js +4 -1
- package/dist/core/config/schema/setup.js +9 -18
- package/dist/core/config/schema/sources-bundles.js +49 -33
- package/dist/core/config/schema/workflow.js +14 -3
- package/dist/core/env-secret-ref.js +76 -46
- package/dist/core/errors.js +43 -12
- package/dist/core/events.js +76 -152
- package/dist/core/file-change.js +6 -5
- package/dist/core/file-lock.js +7 -1
- package/dist/core/fs-txn.js +83 -7
- package/dist/core/git-message.js +2 -2
- package/dist/core/improve-result.js +6 -100
- package/dist/core/json-schema.js +327 -9
- package/dist/core/lesson-lint.js +1 -17
- package/dist/core/logs-db.js +2 -1
- package/dist/core/loopback.js +89 -0
- package/dist/core/migration-operation.js +33 -2
- package/dist/core/mutation-target.js +78 -0
- package/dist/core/parse.js +4 -1
- package/dist/core/path-access.js +107 -0
- package/dist/core/paths.js +17 -20
- package/dist/core/recognition-util.js +12 -14
- package/dist/core/redaction.js +98 -17
- package/dist/core/spawn-env.js +234 -0
- package/dist/core/standards/resolve-standards-context.js +2 -14
- package/dist/core/standards/resolve-stash-standards.js +2 -2
- package/dist/core/standards/resolve-type-conventions.js +2 -2
- package/dist/core/state/migrations.js +41 -18
- package/dist/core/state-db-scope.js +134 -0
- package/dist/core/state-db.js +5 -14
- package/dist/core/structured.js +1 -1
- package/dist/core/subprocess.js +182 -36
- package/dist/core/text-truncation.js +9 -5
- package/dist/core/type-presentation.js +3 -3
- package/dist/core/warn.js +0 -3
- package/dist/core/write-provenance.js +85 -0
- package/dist/core/write-source.js +778 -95
- package/dist/indexer/bundle-identity-guard.js +3 -2
- package/dist/indexer/db/graph-db.js +17 -30
- package/dist/indexer/ensure-index.js +11 -3
- package/dist/indexer/graph/graph-boost.js +9 -34
- package/dist/indexer/graph/graph-extraction.js +8 -5
- package/dist/indexer/index-writer-lock.js +53 -17
- package/dist/indexer/index-written-assets.js +33 -24
- package/dist/indexer/indexer.js +551 -253
- package/dist/indexer/installations.js +14 -96
- package/dist/indexer/passes/dir-staleness.js +16 -9
- package/dist/indexer/passes/memory-inference.js +15 -9
- package/dist/indexer/passes/metadata.js +113 -47
- package/dist/indexer/scan/doc-to-entry.js +38 -1
- package/dist/indexer/scan/drain-dir.js +13 -23
- package/dist/indexer/search/db-search.js +107 -69
- package/dist/indexer/search/fts-query.js +47 -24
- package/dist/indexer/search/ranking-contributors.js +42 -20
- package/dist/indexer/search/ranking.js +18 -99
- package/dist/indexer/search/search-fields.js +7 -2
- package/dist/indexer/search/search-source.js +82 -93
- package/dist/indexer/usage/usage-events.js +0 -89
- package/dist/indexer/walk/file-context.js +2 -1
- package/dist/indexer/walk/matchers.js +30 -43
- package/dist/indexer/walk/path-resolver.js +7 -2
- package/dist/indexer/walk/walker.js +38 -12
- package/dist/integrations/agent/builders.js +0 -6
- package/dist/integrations/agent/config.js +2 -2
- package/dist/integrations/agent/detect.js +49 -19
- package/dist/integrations/agent/engine-fallback.js +76 -0
- package/dist/integrations/agent/engine-resolution.js +24 -11
- package/dist/integrations/agent/model-aliases.js +1 -1
- package/dist/integrations/agent/profiles.js +23 -1
- package/dist/integrations/agent/prompts.js +12 -8
- package/dist/integrations/agent/runner-dispatch.js +4 -2
- package/dist/integrations/agent/runner.js +0 -1
- package/dist/integrations/agent/spawn.js +20 -93
- package/dist/integrations/github.js +1 -1
- package/dist/integrations/harnesses/aider/agent-builder.js +6 -4
- package/dist/integrations/harnesses/amazonq/agent-builder.js +7 -4
- package/dist/integrations/harnesses/claude/session-log.js +0 -10
- package/dist/integrations/harnesses/codex/agent-builder.js +5 -2
- package/dist/integrations/harnesses/copilot/agent-builder.js +5 -3
- package/dist/integrations/harnesses/gemini/agent-builder.js +5 -3
- package/dist/integrations/harnesses/index.js +3 -7
- package/dist/integrations/harnesses/opencode/agent-builder.js +21 -2
- package/dist/integrations/harnesses/opencode/session-log.js +0 -15
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +13 -4
- package/dist/integrations/harnesses/openhands/agent-builder.js +9 -6
- package/dist/integrations/harnesses/pi/agent-builder.js +6 -4
- package/dist/integrations/lockfile.js +132 -8
- package/dist/integrations/session-logs/index.js +3 -28
- package/dist/llm/client.js +136 -100
- package/dist/llm/embedders/remote.js +13 -5
- package/dist/llm/feature-gate.js +4 -12
- package/dist/llm/graph-extract.js +5 -11
- package/dist/llm/memory-infer.js +144 -1
- package/dist/llm/metadata-enhance.js +5 -7
- package/dist/llm/structured-call.js +1 -1
- package/dist/llm/usage-persist.js +26 -5
- package/dist/llm/usage-telemetry.js +25 -2
- package/dist/output/cli-hints.js +1 -2
- package/dist/output/context.js +22 -7
- package/dist/output/format-exempt.js +80 -0
- package/dist/output/generic-render.js +259 -0
- package/dist/output/render-registry.js +57 -0
- package/dist/output/renderers.js +14 -36
- package/dist/output/shapes/curate.js +10 -1
- package/dist/output/shapes/events.js +12 -7
- package/dist/output/shapes/helpers.js +56 -83
- package/dist/output/shapes/migrate.js +8 -0
- package/dist/output/shapes/passthrough.js +7 -41
- package/dist/output/shapes/proposal/producer.js +15 -7
- package/dist/output/shapes.js +2 -9
- package/dist/output/text/{init.js → bundle-create.js} +3 -1
- package/dist/output/text/bundle-show.js +7 -0
- package/dist/output/text/command-format.js +164 -96
- package/dist/output/text/env.js +1 -3
- package/dist/output/text/events.js +8 -7
- package/dist/output/text/health-format.js +103 -0
- package/dist/output/text/health.js +7 -0
- package/dist/output/text/helpers.js +10 -8
- package/dist/output/text/lint-format.js +56 -0
- package/dist/{migrate-storage-node.mjs → output/text/lint.js} +2 -5
- package/dist/output/text/migrate.js +88 -0
- package/dist/output/text/proposal/producer.js +4 -2
- package/dist/output/text/proposal-format.js +44 -72
- package/dist/output/text/registry-commands.js +1 -2
- package/dist/output/text/show-directives.js +15 -7
- package/dist/output/text/status-list.js +32 -0
- package/dist/output/text/{save.js → sync.js} +2 -2
- package/dist/output/text/workflow-format.js +24 -203
- package/dist/output/text/workflow.js +1 -7
- package/dist/output/text.js +16 -17
- package/dist/registry/factory.js +4 -6
- package/dist/registry/origin-resolve.js +16 -27
- package/dist/registry/providers/skills-sh.js +3 -3
- package/dist/registry/providers/static-index.js +13 -23
- package/dist/registry/resolve.js +42 -7
- package/dist/registry/semver.js +34 -84
- package/dist/runtime.js +2 -23
- package/dist/scripts/akm-migrate-node.js +60972 -0
- package/dist/scripts/akm-migrate.js +60310 -0
- package/dist/setup/detect.js +42 -15
- package/dist/setup/registry-stash-loader.js +2 -2
- package/dist/setup/setup.js +236 -136
- package/dist/setup/steps/connection.js +7 -9
- package/dist/setup/steps/platforms.js +9 -9
- package/dist/setup/steps/semantic.js +15 -3
- package/dist/setup/steps/sources.js +12 -13
- package/dist/setup/steps/stashdir.js +2 -3
- package/dist/setup/steps/tasks.js +237 -120
- package/dist/sources/freshness.js +1 -1
- package/dist/sources/provider-factory.js +11 -17
- package/dist/sources/providers/filesystem.js +2 -3
- package/dist/sources/providers/git-install.js +278 -34
- package/dist/sources/providers/git-provider.js +25 -23
- package/dist/sources/providers/git-stash.js +414 -106
- package/dist/sources/providers/git.js +2 -2
- package/dist/sources/providers/npm.js +16 -19
- package/dist/sources/providers/provider-utils.js +7 -4
- package/dist/sources/providers/sync-from-ref.js +3 -9
- package/dist/sources/providers/website.js +6 -1
- package/dist/sources/resolve.js +6 -5
- package/dist/sources/snapshot-fetchers/bluesky.js +146 -0
- package/dist/sources/snapshot-fetchers/content-extract.js +566 -0
- package/dist/sources/snapshot-fetchers/fetcher-util.js +41 -0
- package/dist/sources/snapshot-fetchers/github.js +100 -0
- package/dist/sources/snapshot-fetchers/host-guard.js +291 -0
- package/dist/sources/snapshot-fetchers/registry.js +17 -1
- package/dist/sources/snapshot-fetchers/robots.js +348 -0
- package/dist/sources/snapshot-fetchers/rss.js +282 -0
- package/dist/sources/snapshot-fetchers/secret-seam.js +42 -0
- package/dist/sources/snapshot-fetchers/website-ingest.js +686 -282
- package/dist/sources/snapshot-fetchers/x.js +910 -0
- package/dist/storage/database.js +13 -1
- package/dist/storage/engines/sqlite-migrations.js +23 -111
- package/dist/storage/managed-db.js +21 -2
- package/dist/storage/repositories/canaries-repository.js +1 -1
- package/dist/storage/repositories/events-repository.js +27 -11
- package/dist/storage/repositories/improve-runs-repository.js +6 -12
- package/dist/storage/repositories/index-connection.js +44 -8
- package/dist/storage/repositories/index-entries-repository.js +157 -241
- package/dist/storage/repositories/index-entry-mapper.js +15 -11
- package/dist/storage/repositories/index-fts-repository.js +5 -2
- package/dist/storage/repositories/index-llm-cache-repository.js +0 -1
- package/dist/storage/repositories/index-meta-repository.js +2 -3
- package/dist/storage/repositories/index-schema.js +10 -25
- package/dist/storage/repositories/index-utility-repository.js +15 -28
- package/dist/storage/repositories/index-vec-repository.js +6 -1
- package/dist/storage/repositories/outcome-repository.js +119 -0
- package/dist/storage/repositories/proposals-repository.js +296 -59
- package/dist/storage/repositories/registry-cache.js +19 -0
- package/dist/storage/repositories/salience-repository.js +172 -0
- package/dist/storage/repositories/task-history-repository.js +15 -13
- package/dist/storage/repositories/workflow-runs-repository.js +118 -53
- package/dist/tasks/backends/cron.js +105 -15
- package/dist/tasks/backends/index.js +1 -1
- package/dist/tasks/backends/launchd.js +85 -38
- package/dist/tasks/backends/schtasks.js +135 -15
- package/dist/tasks/embedded.js +56 -40
- package/dist/tasks/log-redaction.js +156 -0
- package/dist/tasks/parser.js +87 -160
- package/dist/tasks/resolve-akm-bin.js +137 -59
- package/dist/tasks/runner.js +209 -44
- package/dist/tasks/scheduler-invocation.js +220 -10
- package/dist/tasks/schema.js +89 -1
- package/dist/tasks/task-id.js +1 -3
- package/dist/tasks/validator.js +20 -6
- package/dist/workflows/authoring/authoring.js +94 -143
- package/dist/workflows/authoring/scope-key.js +1 -1
- package/dist/workflows/concurrency-policy.js +95 -1
- package/dist/workflows/exec/dispatch-redaction.js +114 -0
- package/dist/workflows/exec/exec-unit.js +542 -0
- package/dist/workflows/exec/frozen-judge.js +130 -32
- package/dist/workflows/exec/native-executor.js +513 -274
- package/dist/workflows/exec/param-secrets.js +12 -11
- package/dist/workflows/exec/run-workflow.js +496 -237
- package/dist/workflows/exec/step-work.js +616 -412
- package/dist/workflows/exec/unit-dispatch.js +34 -0
- package/dist/workflows/exec/unit-writer.js +53 -13
- package/dist/workflows/exec/worktree.js +454 -41
- package/dist/workflows/ir/compile.js +166 -271
- package/dist/workflows/ir/freeze.js +119 -42
- package/dist/workflows/ir/params.js +135 -11
- package/dist/workflows/ir/plan-hash.js +1 -1
- package/dist/workflows/ir/schema.js +129 -45
- package/dist/workflows/parser.js +1070 -307
- package/dist/workflows/program/expressions.js +20 -208
- package/dist/workflows/program/schema.js +31 -10
- package/dist/workflows/renderer.js +123 -68
- package/dist/workflows/resource-limits.js +184 -0
- package/dist/workflows/runtime/checkin.js +3 -3
- package/dist/workflows/runtime/plan-classifier.js +16 -75
- package/dist/workflows/runtime/runs.js +331 -132
- package/dist/workflows/runtime/unit-checkin.js +1 -1
- package/dist/workflows/runtime/unit-phases.js +2 -2
- package/dist/workflows/runtime/workflow-asset-loader.js +232 -83
- package/dist/workflows/schema.js +1 -11
- package/dist/workflows/validate-summary.js +47 -38
- package/dist/workflows/validator.js +21 -62
- package/docs/README.md +110 -0
- package/docs/migration/README.md +8 -0
- package/docs/migration/release-notes/0.7.0.md +11 -11
- package/docs/migration/release-notes/0.9.0.md +209 -27
- package/docs/migration/v0.7-to-v0.8.md +48 -48
- package/docs/migration/v0.8-to-v0.9.md +567 -209
- package/docs/migration/v0.9.0-troubleshooting.md +561 -0
- package/docs/reference/README.md +19 -0
- package/docs/reference/bundle-types.md +19 -0
- package/docs/reference/cli.md +2342 -0
- package/docs/reference/configuration.md +371 -0
- package/docs/reference/data-and-telemetry.md +129 -46
- package/docs/reference/supported-formats.md +50 -0
- package/docs/reference/workflow-schema.md +1014 -0
- package/docs/reference/workflows.md +51 -0
- package/package.json +31 -13
- package/schemas/akm-asset-envelope.json +93 -0
- package/schemas/akm-config.json +89 -128
- package/schemas/akm-task.json +27 -5
- package/schemas/akm-workflow.json +157 -81
- package/dist/assets/tasks/core/backup.yml +0 -5
- package/dist/assets/tasks/graph-refresh-weekly.yml +0 -10
- package/dist/cli/config-migrate.js +0 -1878
- package/dist/cli/config-validate.js +0 -41
- package/dist/commands/backup-cli.js +0 -56
- package/dist/commands/bundle/bundle-cli.js +0 -68
- package/dist/commands/bundle/bundle.js +0 -219
- package/dist/commands/graph/graph-cli.js +0 -124
- package/dist/commands/graph/graph.js +0 -489
- package/dist/commands/improve/extract-watch.js +0 -140
- package/dist/commands/mv-cli.js +0 -1221
- package/dist/commands/sources/history.js +0 -201
- package/dist/commands/tasks/default-tasks.js +0 -186
- package/dist/core/migration-backup.js +0 -1231
- package/dist/indexer/usage/unmigrated-vaults-guard.js +0 -95
- package/dist/llm/memory-infer-impl.js +0 -138
- package/dist/migrate/legacy/config-source-migration.js +0 -257
- package/dist/migrate/legacy/content-migration.js +0 -350
- package/dist/migrate/legacy/legacy-layout.js +0 -779
- package/dist/migrate/legacy/legacy-paths.js +0 -25
- package/dist/migrate/legacy/legacy-stash-json.js +0 -80
- package/dist/migrate/legacy/proposal-fs-import.js +0 -168
- package/dist/migrate/legacy/task-target-ref-migration.js +0 -278
- package/dist/migrate/legacy/three-db-cutover.js +0 -845
- package/dist/migrate/legacy/workflow-migrations-bodies.js +0 -52
- package/dist/migrate/legacy/workflow-migrations-frozen.js +0 -21
- package/dist/migrate/legacy-ref-grammar.js +0 -214
- package/dist/output/shapes/distill.js +0 -14
- package/dist/output/shapes/history.js +0 -11
- package/dist/output/text/distill.js +0 -6
- package/dist/output/text/enable-disable.js +0 -8
- package/dist/output/text/history.js +0 -6
- package/dist/registry/build-index.js +0 -382
- package/dist/schemas/akm-config.json +0 -4704
- package/dist/schemas/akm-task.json +0 -87
- package/dist/schemas/akm-workflow.json +0 -372
- package/dist/scripts/migrate-storage.js +0 -3816
- package/dist/workflows/authoring/workflow-program-template.yaml +0 -31
- package/dist/workflows/cli.js +0 -53
- package/dist/workflows/exec/brief.js +0 -481
- package/dist/workflows/exec/report.js +0 -1460
- package/dist/workflows/exec/watch.js +0 -116
- package/dist/workflows/program/parser.js +0 -813
- package/dist/workflows/program/project.js +0 -104
|
@@ -7,6 +7,7 @@ import { writeFileAtomic } from "../core/common.js";
|
|
|
7
7
|
import { ConfigError, rethrowIfTestIsolationError } from "../core/errors.js";
|
|
8
8
|
import { createLockPayload, probeLock, reclaimStaleLock, releaseLock, tryAcquireLockSync } from "../core/file-lock.js";
|
|
9
9
|
import { acquireMaintenanceBarrier } from "../core/maintenance-barrier.js";
|
|
10
|
+
import { classifyPathAccess, describeInaccessiblePath } from "../core/path-access.js";
|
|
10
11
|
import { getDataDir, getLockfileLockPath, getLockfilePath } from "../core/paths.js";
|
|
11
12
|
// ── Lock sentinel ────────────────────────────────────────────────────────────
|
|
12
13
|
const LOCK_MAX_RETRIES = 3;
|
|
@@ -54,6 +55,79 @@ export function readLockfile() {
|
|
|
54
55
|
return [];
|
|
55
56
|
}
|
|
56
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Refuse to treat an UNREADABLE lockfile (or data dir) as an absent one (#791).
|
|
60
|
+
*
|
|
61
|
+
* Every write path here is read-modify-WRITE: it loads the current entries and
|
|
62
|
+
* writes the whole array back. An unreadable `akm.lock` that reads as `[]`
|
|
63
|
+
* therefore does not merely lose information — the very next
|
|
64
|
+
* `writeFileAtomic` replaces the operator's entire lock record with the single
|
|
65
|
+
* entry this call happened to be adding. That is the same catastrophe R-012
|
|
66
|
+
* guards against for a *corrupt* file, reached instead through a permission
|
|
67
|
+
* fault, and `fs.existsSync`/a swallowed `readFileSync` could not tell the two
|
|
68
|
+
* apart from "the file was never created".
|
|
69
|
+
*
|
|
70
|
+
* No-op when the path is genuinely absent — that case really does have nothing
|
|
71
|
+
* to preserve.
|
|
72
|
+
*/
|
|
73
|
+
function assertLockfilePathReadable(target) {
|
|
74
|
+
const { access, code } = classifyPathAccess(target);
|
|
75
|
+
if (access !== "inaccessible")
|
|
76
|
+
return;
|
|
77
|
+
throw new ConfigError(`Refusing to modify the lockfile: ${describeInaccessiblePath(target, code)}. akm cannot read the existing lock ` +
|
|
78
|
+
"records, and writing over them would destroy every bundle they track. Fix the ownership or mode of that " +
|
|
79
|
+
"path (or point AKM_DATA_DIR / XDG_DATA_HOME somewhere this user owns) and retry.", "DATA_DIR_UNREADABLE");
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Like {@link readLockfile}, but THROWS instead of silently degrading to `[]`
|
|
83
|
+
* when the on-disk lockfile exists yet is not parseable JSON or not a JSON
|
|
84
|
+
* array (R-012).
|
|
85
|
+
*
|
|
86
|
+
* `readLockfile`'s fail-open contract is intentional for READ paths — a
|
|
87
|
+
* corrupt lock degrades a managed bundle to "unmanaged" rather than erroring
|
|
88
|
+
* every read-only command (`list`, `installed-stashes`, …). But
|
|
89
|
+
* {@link upsertLockEntry} and {@link removeLockEntry} read the current
|
|
90
|
+
* entries and then WRITE `[...entries, change]` back out; if that read
|
|
91
|
+
* silently returned `[]` for a corrupt file, the write would silently
|
|
92
|
+
* replace the corrupt file with one containing only the single new/changed
|
|
93
|
+
* entry — permanently destroying every other surviving lock record. Write
|
|
94
|
+
* paths use this strict variant so a corrupt lockfile fails the operation
|
|
95
|
+
* loudly (matching the guard `mergeLockEntriesSync` already applies) instead
|
|
96
|
+
* of quietly deleting user state. A missing file is NOT corruption — there
|
|
97
|
+
* is nothing to preserve, so that case still returns `[]`. Entries that fail
|
|
98
|
+
* per-entry validation are still tolerated (filtered out), matching
|
|
99
|
+
* `readLockfile`'s existing shape-tolerant behavior.
|
|
100
|
+
*/
|
|
101
|
+
function readLockfileOrThrow() {
|
|
102
|
+
const lockfilePath = getLockfilePath();
|
|
103
|
+
let raw;
|
|
104
|
+
try {
|
|
105
|
+
raw = fs.readFileSync(lockfilePath, "utf8");
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
rethrowIfTestIsolationError(err);
|
|
109
|
+
// "Missing file" is the only failure with nothing to preserve. An
|
|
110
|
+
// UNREADABLE lockfile has everything to preserve and we cannot see it —
|
|
111
|
+
// degrading it to `[]` here is precisely the destructive overwrite this
|
|
112
|
+
// function was written to prevent, only triggered by a permission fault
|
|
113
|
+
// instead of a corrupt file (#791). Classify AFTER the failed read so the
|
|
114
|
+
// happy path costs no extra syscall and the answer describes the failure
|
|
115
|
+
// we actually got.
|
|
116
|
+
assertLockfilePathReadable(lockfilePath);
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
let parsed;
|
|
120
|
+
try {
|
|
121
|
+
parsed = JSON.parse(raw);
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
throw new ConfigError(`Refusing to modify lockfile ${lockfilePath}: existing content is not valid JSON (${error instanceof Error ? error.message : String(error)}). Fix or remove the file by hand before retrying — every existing lock entry would otherwise be lost.`, "INVALID_CONFIG_FILE");
|
|
125
|
+
}
|
|
126
|
+
if (!Array.isArray(parsed)) {
|
|
127
|
+
throw new ConfigError(`Refusing to modify lockfile ${lockfilePath}: existing content is not a JSON array. Fix or remove the file by hand before retrying — every existing lock entry would otherwise be lost.`, "INVALID_CONFIG_FILE");
|
|
128
|
+
}
|
|
129
|
+
return parsed.filter(isValidLockfileEntry);
|
|
130
|
+
}
|
|
57
131
|
/**
|
|
58
132
|
* The materialized content root recorded in the lock for a managed (git/npm)
|
|
59
133
|
* bundle — spec §10.2 desired/resolved split, where the desired config carries
|
|
@@ -93,10 +167,27 @@ export async function writeLockfile(entries) {
|
|
|
93
167
|
release();
|
|
94
168
|
}
|
|
95
169
|
}
|
|
170
|
+
/** Replace one exact parsed lock generation while the lockfile sentinel is held. */
|
|
171
|
+
export async function compareAndSwapLockfile(expected, desired) {
|
|
172
|
+
const release = await acquireLockSentinel();
|
|
173
|
+
try {
|
|
174
|
+
const current = readLockfileOrThrow();
|
|
175
|
+
if (JSON.stringify(current) !== JSON.stringify(expected))
|
|
176
|
+
return false;
|
|
177
|
+
writeLockfileUnlocked(desired);
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
finally {
|
|
181
|
+
release();
|
|
182
|
+
}
|
|
183
|
+
}
|
|
96
184
|
export async function upsertLockEntry(entry) {
|
|
97
185
|
const release = await acquireLockSentinel();
|
|
98
186
|
try {
|
|
99
|
-
|
|
187
|
+
// R-012: readLockfileOrThrow (not readLockfile) — a corrupt/malformed
|
|
188
|
+
// lockfile must abort the upsert loudly rather than read as `[]` and get
|
|
189
|
+
// silently overwritten with just this one entry.
|
|
190
|
+
const entries = readLockfileOrThrow();
|
|
100
191
|
const withoutExisting = entries.filter((e) => e.id !== entry.id);
|
|
101
192
|
writeLockfileUnlocked([...withoutExisting, entry]);
|
|
102
193
|
}
|
|
@@ -110,11 +201,14 @@ export async function upsertLockEntry(entry) {
|
|
|
110
201
|
* migrate-apply's config lock + maintenance barrier) whose synchronous body
|
|
111
202
|
* cannot await the sentinel's retry loop. No-op for an empty list.
|
|
112
203
|
*/
|
|
113
|
-
|
|
114
|
-
if (entries.length === 0)
|
|
115
|
-
return;
|
|
204
|
+
function readLockEntriesForMigration() {
|
|
116
205
|
let existing = [];
|
|
117
206
|
const lockfilePath = getLockfilePath();
|
|
207
|
+
// `mergeLockEntriesSync` writes `existing` straight back out, so an
|
|
208
|
+
// unreadable lockfile read as absent would be overwritten with just the
|
|
209
|
+
// migrator's sparse entries (#791). This is also what
|
|
210
|
+
// `assertMigrationLockfileReadable` promises to have checked.
|
|
211
|
+
assertLockfilePathReadable(lockfilePath);
|
|
118
212
|
if (fs.existsSync(lockfilePath)) {
|
|
119
213
|
let raw;
|
|
120
214
|
try {
|
|
@@ -128,15 +222,45 @@ export function mergeLockEntriesSync(entries) {
|
|
|
128
222
|
}
|
|
129
223
|
existing = raw;
|
|
130
224
|
}
|
|
131
|
-
|
|
132
|
-
|
|
225
|
+
return existing;
|
|
226
|
+
}
|
|
227
|
+
/** Validate the current lockfile before migrate-apply creates its backup or sentinel. */
|
|
228
|
+
export function assertMigrationLockfileReadable() {
|
|
229
|
+
readLockEntriesForMigration();
|
|
230
|
+
}
|
|
231
|
+
export function mergeLockEntriesSync(entries) {
|
|
232
|
+
const existing = readLockEntriesForMigration();
|
|
233
|
+
if (entries.length === 0)
|
|
234
|
+
return;
|
|
235
|
+
// MERGE per id, never replace: the migrator's entries are sparse (id/source/
|
|
236
|
+
// ref/localRoot), while an existing row may carry `resolvedVersion`,
|
|
237
|
+
// `resolvedRevision`, `integrity`, `installedAt`, … from a real install.
|
|
238
|
+
// Replacing the whole row discarded the user's recorded resolution/pin and
|
|
239
|
+
// left later update reporting comparing against an unknown prior version.
|
|
240
|
+
// Incoming DEFINED fields win; existing fields absent from the incoming
|
|
241
|
+
// entry are preserved.
|
|
242
|
+
const byId = new Map(existing.map((e) => [e.id, e]));
|
|
243
|
+
const merged = entries.map((incoming) => {
|
|
244
|
+
const prior = byId.get(incoming.id);
|
|
245
|
+
return prior ? { ...prior, ...incoming } : incoming;
|
|
246
|
+
});
|
|
247
|
+
const incomingIds = new Set(entries.map((e) => e.id));
|
|
248
|
+
writeLockfileUnlocked([...existing.filter((e) => !incomingIds.has(e.id)), ...merged]);
|
|
133
249
|
}
|
|
134
250
|
export async function removeLockEntry(id) {
|
|
135
|
-
|
|
251
|
+
// Returning early says "there is no lock record to remove", and the uninstall
|
|
252
|
+
// that called us reports success on that basis. Only an absent data dir earns
|
|
253
|
+
// it — one we cannot read may hold the very entry we were asked to drop, and
|
|
254
|
+
// silently leaving it behind is how a bundle stays "installed" forever (#791).
|
|
255
|
+
const dataDir = getDataDir();
|
|
256
|
+
assertLockfilePathReadable(dataDir);
|
|
257
|
+
if (!fs.existsSync(dataDir))
|
|
136
258
|
return;
|
|
137
259
|
const release = await acquireLockSentinel();
|
|
138
260
|
try {
|
|
139
|
-
|
|
261
|
+
// R-012: see upsertLockEntry — same destructive-overwrite risk on a
|
|
262
|
+
// corrupt lockfile, so the same strict reader is used here.
|
|
263
|
+
const entries = readLockfileOrThrow();
|
|
140
264
|
writeLockfileUnlocked(entries.filter((e) => e.id !== id));
|
|
141
265
|
}
|
|
142
266
|
finally {
|
|
@@ -50,22 +50,6 @@ const ERROR_PATTERNS = /error|failed|exception|cannot|undefined|null pointer|ENO
|
|
|
50
50
|
export function getAvailableHarnesses() {
|
|
51
51
|
return HARNESSES.filter((harness) => harness.isAvailable());
|
|
52
52
|
}
|
|
53
|
-
/**
|
|
54
|
-
* Map each available harness to its `{ harnessName, roots }` watch target,
|
|
55
|
-
* skipping harnesses that expose no roots (absent `watchRoots()` or an empty
|
|
56
|
-
* result). This is the one stable entry point the watcher uses so it never
|
|
57
|
-
* reaches into providers directly.
|
|
58
|
-
*/
|
|
59
|
-
export function getWatchTargets() {
|
|
60
|
-
const targets = [];
|
|
61
|
-
for (const harness of getAvailableHarnesses()) {
|
|
62
|
-
const roots = harness.watchRoots?.() ?? [];
|
|
63
|
-
if (roots.length === 0)
|
|
64
|
-
continue;
|
|
65
|
-
targets.push({ harnessName: harness.name, roots });
|
|
66
|
-
}
|
|
67
|
-
return targets;
|
|
68
|
-
}
|
|
69
53
|
export function normalizeSessionTopic(text) {
|
|
70
54
|
const normalized = text.replace(/\s+/g, " ").trim().toLowerCase();
|
|
71
55
|
if (normalized.length < 10)
|
|
@@ -107,16 +91,13 @@ export function aggregateSessionEvents(events) {
|
|
|
107
91
|
* Collect normalized session events from a set of harnesses for the health
|
|
108
92
|
* candidate scan (#568).
|
|
109
93
|
*
|
|
110
|
-
* Pipeline selection per harness
|
|
111
|
-
* -
|
|
112
|
-
* richer `listSessions()` + `readSession()` pipeline. `readSession` flattens
|
|
94
|
+
* Pipeline selection per harness:
|
|
95
|
+
* - Drive the richer `listSessions()` + `readSession()` pipeline. `readSession` flattens
|
|
113
96
|
* structured content — tool calls, assistant content blocks, thinking,
|
|
114
97
|
* tool_result — into event text (e.g. ClaudeCodeProvider's `parseClaudeEvent`
|
|
115
98
|
* surfaces `[tool:*]` / `[tool_result]` blocks that the legacy flat
|
|
116
99
|
* `readEvents` scan drops entirely). This is what lets health advisories see
|
|
117
100
|
* repeated tool failures / long runs that the flat scan hid.
|
|
118
|
-
* - legacy-only harness (`supportsReadSession === false`): fall back to the
|
|
119
|
-
* legacy flat `readEvents()` scan (behaviour-preserving).
|
|
120
101
|
*
|
|
121
102
|
* Extracted as a pure function (harnesses injected) so it is unit-testable
|
|
122
103
|
* without touching the real on-disk session-log locations.
|
|
@@ -129,19 +110,13 @@ export function aggregateSessionEvents(events) {
|
|
|
129
110
|
* health command (`akm health`, which calls this synchronously) blow past its
|
|
130
111
|
* latency budget. `listSessions()` returns summaries sorted newest-first, so
|
|
131
112
|
* capping to the most-recent N sessions per harness keeps the richer signal
|
|
132
|
-
* for what actually matters (recent activity) while bounding cost.
|
|
133
|
-
* flat-scan path is naturally cheaper and is left uncapped.
|
|
113
|
+
* for what actually matters (recent activity) while bounding cost.
|
|
134
114
|
*/
|
|
135
115
|
const DEFAULT_MAX_SESSIONS_PER_HARNESS = 50;
|
|
136
116
|
export function collectSessionEvents(harnesses, sinceMs, maxSessionsPerHarness = DEFAULT_MAX_SESSIONS_PER_HARNESS) {
|
|
137
117
|
const events = [];
|
|
138
118
|
for (const harness of harnesses) {
|
|
139
119
|
try {
|
|
140
|
-
if (harness.supportsReadSession === false) {
|
|
141
|
-
// Legacy-only harness: only the flat event scan is available.
|
|
142
|
-
events.push(...harness.readEvents({ sinceMs }));
|
|
143
|
-
continue;
|
|
144
|
-
}
|
|
145
120
|
// Rich path: enumerate sessions cheaply, then read each one's full
|
|
146
121
|
// structured event stream. Falls back to readEvents if listSessions
|
|
147
122
|
// surfaces nothing (e.g. a harness that wired readSession but whose
|
package/dist/llm/client.js
CHANGED
|
@@ -4,23 +4,17 @@
|
|
|
4
4
|
/**
|
|
5
5
|
* Low-level OpenAI-compatible chat completions client and capability probing.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* check) separate from higher-level metadata-enhancement workflows.
|
|
10
|
-
*
|
|
11
|
-
* `llm.ts` re-exports everything from this module for backward compatibility.
|
|
7
|
+
* Keeps transport-layer concerns (HTTP request, response parsing, capability
|
|
8
|
+
* probing, and availability checks) separate from higher-level workflows.
|
|
12
9
|
*/
|
|
13
|
-
import { fetchWithTimeout } from "../core/common.js";
|
|
10
|
+
import { fetchWithTimeout, readBodyWithByteCap } from "../core/common.js";
|
|
14
11
|
import { resolveSecret } from "../core/config/config.js";
|
|
15
12
|
import { formatExtraParamsIssue, validateExtraParams } from "../core/extra-params.js";
|
|
16
|
-
import {
|
|
17
|
-
import { redactSensitiveText } from "../core/redaction.js";
|
|
13
|
+
import { parseJsonResponse } from "../core/parse.js";
|
|
14
|
+
import { redactCredentialPatterns, redactSensitiveText } from "../core/redaction.js";
|
|
18
15
|
import { warnVerbose } from "../core/warn.js";
|
|
19
16
|
import { DEFAULT_LLM_TIMEOUT_MS } from "../integrations/agent/config.js";
|
|
20
|
-
import { emitLlmUsage, extractUsageTokens } from "./usage-telemetry.js";
|
|
21
|
-
// Re-export shared parse utilities so existing importers of `client.ts` continue
|
|
22
|
-
// to resolve `parseJsonResponse` and `parseEmbeddedJsonResponse` from this module.
|
|
23
|
-
export { escapeJsonStringControls, parseEmbeddedJsonResponse, parseJsonResponse, stripCodeFences, stripThinkBlocks, } from "../core/parse.js";
|
|
17
|
+
import { emitLlmUsage, extractUsageTokens, } from "./usage-telemetry.js";
|
|
24
18
|
/** Maximum length of an LLM error response body included in thrown errors. */
|
|
25
19
|
const ERROR_BODY_MAX_LEN = 200;
|
|
26
20
|
/** Stable OpenAI-compatible response-schema name used for every structured call. */
|
|
@@ -30,24 +24,14 @@ const JSON_SCHEMA_RESPONSE_NAME = "akm_response";
|
|
|
30
24
|
* including it in a thrown Error. The body is also trimmed to a fixed length
|
|
31
25
|
* so that a verbose provider response cannot leak large amounts of context.
|
|
32
26
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* - `key-…` / `key_…` shorthand keys
|
|
37
|
-
* - `"api_key": "…"` / `"apiKey": "…"` JSON fields
|
|
27
|
+
* The pattern set itself lives in {@link redactCredentialPatterns}
|
|
28
|
+
* (src/core/redaction.ts) so other output paths (e.g. task run logs) can
|
|
29
|
+
* reuse it without this function's length cap.
|
|
38
30
|
*/
|
|
39
31
|
export function redactErrorBody(input) {
|
|
40
32
|
if (!input)
|
|
41
33
|
return "";
|
|
42
|
-
let out = input
|
|
43
|
-
// Bearer tokens (case-insensitive)
|
|
44
|
-
.replace(/\bBearer\s+[A-Za-z0-9._\-+/=]+/gi, "Bearer [REDACTED]")
|
|
45
|
-
// sk-/sk_ style keys
|
|
46
|
-
.replace(/\bsk[-_][A-Za-z0-9._-]{6,}/g, "[REDACTED]")
|
|
47
|
-
// key-/key_ shorthand keys
|
|
48
|
-
.replace(/\bkey[-_][A-Za-z0-9._-]{6,}/g, "[REDACTED]")
|
|
49
|
-
// JSON-style "api_key": "...", "apiKey": "...", "api-key": "..."
|
|
50
|
-
.replace(/("(?:api[_-]?key|apiKey|authorization|token)"\s*:\s*")([^"]*)(")/gi, "$1[REDACTED]$3");
|
|
34
|
+
let out = redactCredentialPatterns(input);
|
|
51
35
|
if (out.length > ERROR_BODY_MAX_LEN) {
|
|
52
36
|
out = `${out.slice(0, ERROR_BODY_MAX_LEN)}…`;
|
|
53
37
|
}
|
|
@@ -103,6 +87,19 @@ const RETRY_BUDGET_FRACTION = 0.9;
|
|
|
103
87
|
export function sleep(ms) {
|
|
104
88
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
105
89
|
}
|
|
90
|
+
async function waitForRetry(ms, wait, signal) {
|
|
91
|
+
if (!signal)
|
|
92
|
+
return wait(ms);
|
|
93
|
+
if (signal.aborted)
|
|
94
|
+
throw new LlmCallError("LLM request aborted", "aborted");
|
|
95
|
+
await new Promise((resolve, reject) => {
|
|
96
|
+
const onAbort = () => reject(new LlmCallError("LLM request aborted", "aborted"));
|
|
97
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
98
|
+
wait(ms)
|
|
99
|
+
.then(resolve, reject)
|
|
100
|
+
.finally(() => signal.removeEventListener("abort", onAbort));
|
|
101
|
+
});
|
|
102
|
+
}
|
|
106
103
|
/** Compute a uniform jittered backoff in the [200, 800)ms range. */
|
|
107
104
|
function retryBackoffMs() {
|
|
108
105
|
return RETRY_BACKOFF_MIN_MS + Math.random() * (RETRY_BACKOFF_MAX_MS - RETRY_BACKOFF_MIN_MS);
|
|
@@ -209,7 +206,7 @@ async function chatCompletionReal(config, messages, options) {
|
|
|
209
206
|
// authoritative.
|
|
210
207
|
warnVerbose(`[akm] LLM transient failure (${err.code}); retrying once: ${err.message}`);
|
|
211
208
|
const wait = retryBackoffMs();
|
|
212
|
-
await (options?.sleep ?? sleep)
|
|
209
|
+
await waitForRetry(wait, options?.sleep ?? sleep, options?.signal);
|
|
213
210
|
// The retry must not exceed the original budget.
|
|
214
211
|
return await chatCompletionAttempt(config, messages, options, remaining);
|
|
215
212
|
}
|
|
@@ -248,90 +245,129 @@ async function chatCompletionAttempt(config, messages, options, timeoutMs) {
|
|
|
248
245
|
: config.provider === "vllm"
|
|
249
246
|
? { chat_template_kwargs: { enable_thinking: resolvedEnableThinking } }
|
|
250
247
|
: { enable_thinking: resolvedEnableThinking };
|
|
251
|
-
|
|
248
|
+
const requestBody = JSON.stringify({
|
|
249
|
+
model: config.model,
|
|
250
|
+
messages,
|
|
251
|
+
temperature: options?.temperature ?? config.temperature ?? 0.3,
|
|
252
|
+
...(resolvedMaxTokens !== undefined ? { max_tokens: resolvedMaxTokens } : {}),
|
|
253
|
+
...responseFormat,
|
|
254
|
+
...thinkingParams,
|
|
255
|
+
...config.extraParams,
|
|
256
|
+
});
|
|
257
|
+
// Wall-clock start for per-attempt usage telemetry (#576). Captured here so the
|
|
252
258
|
// emitted duration covers the full request/response/parse cycle of a single
|
|
253
259
|
// attempt, not the retry-wrapping `chatCompletion`.
|
|
254
260
|
const requestStartedAt = Date.now();
|
|
255
|
-
|
|
261
|
+
const requestDeadlineAt = timeoutMs === null ? null : requestStartedAt + timeoutMs;
|
|
262
|
+
const remainingAttemptMs = () => requestDeadlineAt === null ? undefined : Math.max(0, requestDeadlineAt - Date.now());
|
|
263
|
+
let terminalFields = {
|
|
264
|
+
model: config.model,
|
|
265
|
+
modelSource: "configured",
|
|
266
|
+
};
|
|
256
267
|
try {
|
|
257
|
-
response
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
...(resolvedMaxTokens !== undefined ? { max_tokens: resolvedMaxTokens } : {}),
|
|
265
|
-
...responseFormat,
|
|
266
|
-
...thinkingParams,
|
|
267
|
-
...config.extraParams,
|
|
268
|
-
}),
|
|
269
|
-
}, timeoutMs, options?.signal);
|
|
270
|
-
}
|
|
271
|
-
catch (err) {
|
|
272
|
-
// fetchWithTimeout throws a plain Error with a message containing
|
|
273
|
-
// "timed out" for AbortController-driven timeouts, or "aborted" for
|
|
274
|
-
// caller-driven cancellations. Map both to typed LlmCallError.
|
|
275
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
276
|
-
if (err instanceof DOMException && err.name === "AbortError") {
|
|
277
|
-
throw new LlmCallError(`Request timed out${timeoutMs === null ? "" : ` after ${timeoutMs}ms`}`, "timeout");
|
|
268
|
+
let response;
|
|
269
|
+
try {
|
|
270
|
+
response = await fetchWithTimeout(config.endpoint, {
|
|
271
|
+
method: "POST",
|
|
272
|
+
headers,
|
|
273
|
+
body: requestBody,
|
|
274
|
+
}, timeoutMs, options?.signal);
|
|
278
275
|
}
|
|
279
|
-
|
|
280
|
-
|
|
276
|
+
catch (err) {
|
|
277
|
+
// fetchWithTimeout throws a plain Error with a message containing
|
|
278
|
+
// "timed out" for AbortController-driven timeouts, or "aborted" for
|
|
279
|
+
// caller-driven cancellations. Map both to typed LlmCallError.
|
|
280
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
281
|
+
if (options?.signal?.aborted || msg.includes("Request aborted")) {
|
|
282
|
+
throw new LlmCallError("LLM request aborted", "aborted");
|
|
283
|
+
}
|
|
284
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
285
|
+
throw new LlmCallError(`Request timed out${timeoutMs === null ? "" : ` after ${timeoutMs}ms`}`, "timeout");
|
|
286
|
+
}
|
|
287
|
+
if (msg.includes("timed out")) {
|
|
288
|
+
throw new LlmCallError(`Request timed out${timeoutMs === null ? "" : ` after ${timeoutMs}ms`}`, "timeout");
|
|
289
|
+
}
|
|
290
|
+
throw new LlmCallError(`Network error: ${msg}`, "network_error");
|
|
281
291
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
292
|
+
if (!response.ok) {
|
|
293
|
+
const rawBody = await readBodyWithByteCap(response, undefined, {
|
|
294
|
+
...(requestDeadlineAt === null ? {} : { bodyTimeoutMs: remainingAttemptMs() }),
|
|
295
|
+
signal: options?.signal,
|
|
296
|
+
}).catch((err) => {
|
|
297
|
+
if (options?.signal?.aborted)
|
|
298
|
+
throw new LlmCallError("LLM response read aborted", "aborted");
|
|
299
|
+
if (err instanceof Error && err.name === "BodyReadTimeoutError") {
|
|
300
|
+
throw new LlmCallError("LLM response body read timed out", "timeout");
|
|
301
|
+
}
|
|
302
|
+
return "";
|
|
303
|
+
});
|
|
304
|
+
const safeBody = redactSensitiveText(redactErrorBody(rawBody), resolvedKey ? [resolvedKey] : []);
|
|
305
|
+
const status = response.status;
|
|
306
|
+
if (status === 429) {
|
|
307
|
+
throw new LlmCallError(`LLM request rate limited (429) ${config.endpoint}: ${safeBody}`, "rate_limited", status);
|
|
308
|
+
}
|
|
309
|
+
if (status >= 500 && isHtmlResponse(rawBody)) {
|
|
310
|
+
throw new LlmCallError(`LLM provider returned HTML instead of JSON (${status}) ${config.endpoint}: ${redactSensitiveText(htmlExcerpt(rawBody), resolvedKey ? [resolvedKey] : [])}`, "provider_html_error", status);
|
|
311
|
+
}
|
|
312
|
+
if (status >= 500) {
|
|
313
|
+
throw new LlmCallError(`LLM provider error (${status}) ${config.endpoint}: ${safeBody}`, "provider_error", status);
|
|
314
|
+
}
|
|
315
|
+
throw new LlmCallError(`LLM request failed (${status}) ${config.endpoint}: ${safeBody}`, "provider_error", status);
|
|
290
316
|
}
|
|
291
|
-
if
|
|
292
|
-
|
|
317
|
+
// A 2xx response is still an error if the body is HTML where JSON was
|
|
318
|
+
// expected (e.g. a provider serving its web UI). Read the raw body first so
|
|
319
|
+
// we can categorize an HTML page distinctly from a malformed-JSON parse_error.
|
|
320
|
+
let rawOkBody;
|
|
321
|
+
try {
|
|
322
|
+
rawOkBody = await readBodyWithByteCap(response, undefined, {
|
|
323
|
+
...(requestDeadlineAt === null ? {} : { bodyTimeoutMs: remainingAttemptMs() }),
|
|
324
|
+
signal: options?.signal,
|
|
325
|
+
});
|
|
293
326
|
}
|
|
294
|
-
|
|
295
|
-
|
|
327
|
+
catch (err) {
|
|
328
|
+
if (options?.signal?.aborted)
|
|
329
|
+
throw new LlmCallError("LLM response read aborted", "aborted");
|
|
330
|
+
if (err instanceof Error && err.name === "BodyReadTimeoutError") {
|
|
331
|
+
throw new LlmCallError("LLM response body read timed out", "timeout");
|
|
332
|
+
}
|
|
333
|
+
throw err;
|
|
296
334
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
335
|
+
if (isHtmlResponse(rawOkBody)) {
|
|
336
|
+
throw new LlmCallError(`LLM provider returned HTML instead of JSON (${response.status}) ${config.endpoint}: ${redactSensitiveText(htmlExcerpt(rawOkBody), resolvedKey ? [resolvedKey] : [])}`, "provider_html_error", response.status);
|
|
337
|
+
}
|
|
338
|
+
let json;
|
|
339
|
+
try {
|
|
340
|
+
json = JSON.parse(rawOkBody);
|
|
341
|
+
}
|
|
342
|
+
catch {
|
|
343
|
+
throw new LlmCallError(`LLM response was not valid JSON ${config.endpoint}: ${redactSensitiveText(redactErrorBody(rawOkBody), resolvedKey ? [resolvedKey] : [])}`, "parse_error", response.status);
|
|
344
|
+
}
|
|
345
|
+
const responseModel = typeof json.model === "string" && json.model.trim().length > 0 ? json.model : undefined;
|
|
346
|
+
terminalFields = {
|
|
347
|
+
model: responseModel ?? config.model,
|
|
348
|
+
modelSource: responseModel === undefined ? "configured" : "response",
|
|
349
|
+
finishReason: typeof json.choices?.[0]?.finish_reason === "string" ? json.choices[0].finish_reason : undefined,
|
|
350
|
+
...extractUsageTokens(json.usage),
|
|
351
|
+
};
|
|
352
|
+
const content = (json.choices?.[0]?.message?.content ?? "").trim();
|
|
353
|
+
const reasoning = (json.choices?.[0]?.message?.reasoning_content ?? "").trim();
|
|
354
|
+
const result = redactSensitiveText(content || reasoning, resolvedKey ? [resolvedKey] : []);
|
|
355
|
+
emitLlmUsage({
|
|
356
|
+
...terminalFields,
|
|
357
|
+
outcome: "success",
|
|
358
|
+
durationMs: Date.now() - requestStartedAt,
|
|
359
|
+
});
|
|
360
|
+
return result;
|
|
309
361
|
}
|
|
310
|
-
catch {
|
|
311
|
-
|
|
362
|
+
catch (err) {
|
|
363
|
+
emitLlmUsage({
|
|
364
|
+
...terminalFields,
|
|
365
|
+
outcome: "error",
|
|
366
|
+
durationMs: Date.now() - requestStartedAt,
|
|
367
|
+
errorCode: err instanceof LlmCallError ? err.code : "unknown_error",
|
|
368
|
+
});
|
|
369
|
+
throw err;
|
|
312
370
|
}
|
|
313
|
-
// Per-call usage telemetry (#576). Best-effort and fully isolated: a missing
|
|
314
|
-
// or garbled usage block still records duration + model, and a throwing sink
|
|
315
|
-
// can never fail the call (emitLlmUsage swallows its own errors). The stage
|
|
316
|
-
// is supplied ambiently by emitLlmUsage; no `stage` param is threaded here.
|
|
317
|
-
emitLlmUsage({
|
|
318
|
-
model: typeof json.model === "string" && json.model ? json.model : config.model,
|
|
319
|
-
durationMs: Date.now() - requestStartedAt,
|
|
320
|
-
finishReason: typeof json.choices?.[0]?.finish_reason === "string" ? json.choices[0].finish_reason : undefined,
|
|
321
|
-
...extractUsageTokens(json.usage),
|
|
322
|
-
});
|
|
323
|
-
const content = (json.choices?.[0]?.message?.content ?? "").trim();
|
|
324
|
-
const reasoning = (json.choices?.[0]?.message?.reasoning_content ?? "").trim();
|
|
325
|
-
return redactSensitiveText(content || reasoning, resolvedKey ? [resolvedKey] : []);
|
|
326
|
-
}
|
|
327
|
-
/**
|
|
328
|
-
* Strip `<think>` blocks, code fences, and escape control characters in JSON
|
|
329
|
-
* strings. Thin wrapper kept for backward compatibility with call sites that
|
|
330
|
-
* import `stripJsonFences` from this module. New code should prefer the
|
|
331
|
-
* granular helpers from `../core/parse`.
|
|
332
|
-
*/
|
|
333
|
-
export function stripJsonFences(raw) {
|
|
334
|
-
return escapeJsonStringControls(stripCodeFences(stripThinkBlocks(raw)));
|
|
335
371
|
}
|
|
336
372
|
// ── Availability check ──────────────────────────────────────────────────────
|
|
337
373
|
/**
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Calls the configured `/embeddings` endpoint and L2-normalizes the returned
|
|
8
8
|
* vectors so the scoring pipeline's L2-to-cosine conversion is correct.
|
|
9
9
|
*/
|
|
10
|
-
import { fetchWithTimeout, isHttpUrl } from "../../core/common.js";
|
|
10
|
+
import { fetchWithTimeout, isHttpUrl, readBodyWithByteCap } from "../../core/common.js";
|
|
11
11
|
import { resolveSecret } from "../../core/config/config.js";
|
|
12
12
|
const DEFAULT_REMOTE_BATCH_SIZE = 100;
|
|
13
13
|
/** Cheap token estimator: 4 chars ≈ 1 token. Used in verbose logging and error messages. */
|
|
@@ -49,10 +49,14 @@ export class RemoteEmbedder {
|
|
|
49
49
|
body: JSON.stringify(body),
|
|
50
50
|
}, 30_000, signal);
|
|
51
51
|
if (!response.ok) {
|
|
52
|
-
const errBody = await response
|
|
52
|
+
const errBody = await readBodyWithByteCap(response, undefined, { bodyTimeoutMs: 30_000, signal }).catch((err) => {
|
|
53
|
+
if (signal?.aborted)
|
|
54
|
+
throw err;
|
|
55
|
+
return "";
|
|
56
|
+
});
|
|
53
57
|
throw new Error(`Embedding request failed (${response.status}): ${errBody}`);
|
|
54
58
|
}
|
|
55
|
-
const json = (await response
|
|
59
|
+
const json = JSON.parse(await readBodyWithByteCap(response, undefined, { bodyTimeoutMs: 30_000, signal }));
|
|
56
60
|
if (!json.data?.[0]?.embedding) {
|
|
57
61
|
throw new Error(`Unexpected embedding response format: missing data[0].embedding.${embeddingEndpointPathHint(this.endpoint)}`);
|
|
58
62
|
}
|
|
@@ -85,10 +89,14 @@ export class RemoteEmbedder {
|
|
|
85
89
|
body: JSON.stringify(body),
|
|
86
90
|
}, 30_000, signal);
|
|
87
91
|
if (!response.ok) {
|
|
88
|
-
const respBody = await response
|
|
92
|
+
const respBody = await readBodyWithByteCap(response, undefined, { bodyTimeoutMs: 30_000, signal }).catch((err) => {
|
|
93
|
+
if (signal?.aborted)
|
|
94
|
+
throw err;
|
|
95
|
+
return "";
|
|
96
|
+
});
|
|
89
97
|
throw new Error(`Embedding batch request failed (${response.status}): ${respBody}`);
|
|
90
98
|
}
|
|
91
|
-
const json = (await response
|
|
99
|
+
const json = JSON.parse(await readBodyWithByteCap(response, undefined, { bodyTimeoutMs: 30_000, signal }));
|
|
92
100
|
if (!json.data || json.data.length !== batch.length) {
|
|
93
101
|
throw new Error(`Unexpected embedding batch response: expected ${batch.length} embeddings, got ${json.data?.length ?? 0}.${embeddingEndpointPathHint(this.endpoint)}`);
|
|
94
102
|
}
|
package/dist/llm/feature-gate.js
CHANGED
|
@@ -3,16 +3,11 @@
|
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
/**
|
|
5
5
|
* For each feature key, return the effective enabled state by reading the
|
|
6
|
-
* 0.9.0 config shape.
|
|
6
|
+
* 0.9.0 config shape.
|
|
7
7
|
*/
|
|
8
|
-
// Defaults below mirror the legacy LlmFeatureFlags docstrings so existing
|
|
9
|
-
// behaviour is preserved when a config is silent on a flag.
|
|
10
8
|
const FEATURE_LOCATION = {
|
|
11
|
-
// Legacy default: true
|
|
12
9
|
memory_inference: (cfg) => cfg.index?.memory?.enabled ?? true,
|
|
13
|
-
// Legacy default: true
|
|
14
10
|
graph_extraction: (cfg) => cfg.index?.graph?.enabled ?? true,
|
|
15
|
-
// Legacy default: false
|
|
16
11
|
metadata_enhance: (cfg) => cfg.index?.metadataEnhance?.enabled ?? false,
|
|
17
12
|
// Always on at the LLM-wrapper level. Enablement is decided ONCE at the
|
|
18
13
|
// extract entry point (`akmExtract`): the `extract.enabled` process toggle
|
|
@@ -26,8 +21,7 @@ const FEATURE_LOCATION = {
|
|
|
26
21
|
/**
|
|
27
22
|
* Pure predicate: is the named feature gate enabled in `config`?
|
|
28
23
|
*
|
|
29
|
-
* Reads from the unified 0.9.0 config shape.
|
|
30
|
-
* `LlmFeatureFlags` docstring defaults.
|
|
24
|
+
* Reads from the unified 0.9.0 config shape.
|
|
31
25
|
*/
|
|
32
26
|
export function isLlmFeatureEnabled(config, feature, improveEnabled) {
|
|
33
27
|
if (!config)
|
|
@@ -71,10 +65,8 @@ export async function tryLlmFeature(feature, config, fn, fallback, opts) {
|
|
|
71
65
|
}
|
|
72
66
|
}
|
|
73
67
|
/**
|
|
74
|
-
* Section-agnostic process gate
|
|
75
|
-
*
|
|
76
|
-
* call sites that knew the (section, processName) pair don't all need to
|
|
77
|
-
* relearn the new mapping.
|
|
68
|
+
* Section-agnostic process gate that maps process identifiers to their current
|
|
69
|
+
* config locations.
|
|
78
70
|
*
|
|
79
71
|
* For unknown (section, processName) pairs the result is `false`.
|
|
80
72
|
*/
|