akm-cli 0.9.16 → 0.9.17-alpha.10
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 +2101 -0
- package/STABILITY.md +11 -10
- package/dist/akm +124 -193
- package/dist/akm-migrate +38 -19
- package/dist/assets/hints/cli-hints-full.md +6 -7
- package/dist/assets/improve-strategies/catchup.json +0 -3
- package/dist/assets/improve-strategies/consolidate.json +0 -1
- package/dist/assets/improve-strategies/default.json +1 -2
- package/dist/assets/improve-strategies/proactive-maintenance.json +1 -2
- package/dist/assets/improve-strategies/quick.json +1 -2
- package/dist/assets/improve-strategies/reflect-distill.json +1 -2
- package/dist/assets/improve-strategies/thorough.json +0 -3
- package/dist/assets/prompts/consolidate-pair.md +20 -0
- package/dist/assets/prompts/consolidate-system.md +4 -11
- package/dist/assets/prompts/retrieval-relevance-judge.md +6 -0
- package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +20 -20
- package/dist/assets/stash-skeleton/facts/conventions/domains.md +2 -2
- package/dist/assets/templates/html/health.html +3 -5
- package/dist/cli/retired-commands.js +1 -1
- package/dist/cli/shared.js +6 -2
- package/dist/cli/unknown-flags.js +24 -1
- package/dist/cli.js +68 -10
- package/dist/commands/agent/agent-dispatch.js +1 -1
- package/dist/commands/command/command-execution.js +24 -62
- package/dist/commands/feedback-cli.js +0 -1
- package/dist/commands/health/accept-rate.js +6 -0
- package/dist/commands/health/archive-usage.js +92 -0
- package/dist/commands/health/checks.js +83 -74
- package/dist/commands/health/config-skew.js +38 -0
- package/dist/commands/health/data-dir-usage.js +25 -13
- package/dist/commands/health/egress.js +54 -0
- package/dist/commands/health/html-report.js +1 -42
- package/dist/commands/health/improve-metrics.js +136 -591
- package/dist/commands/health/md-report.js +1 -6
- package/dist/commands/health/plugin-staleness.js +53 -3
- package/dist/commands/health/renderers.js +12 -4
- package/dist/commands/health/report-view-model.js +14 -120
- package/dist/commands/health/types-improve.js +4 -19
- package/dist/commands/health/windows.js +64 -74
- package/dist/commands/health.js +145 -143
- package/dist/commands/improve/consolidate/chunking.js +26 -117
- package/dist/commands/improve/consolidate/continuity-check.js +137 -0
- package/dist/commands/improve/consolidate/pair-pass.js +791 -0
- package/dist/commands/improve/consolidate/sanitize.js +54 -149
- package/dist/commands/improve/consolidate.js +589 -1127
- package/dist/commands/improve/content-hash.js +16 -24
- package/dist/commands/improve/distill/content-repair.js +18 -100
- package/dist/commands/improve/distill-guards.js +20 -81
- package/dist/commands/improve/distill-promotion-policy.js +23 -243
- package/dist/commands/improve/distill.js +608 -1041
- package/dist/commands/improve/eligibility.js +126 -390
- package/dist/commands/improve/execution.js +8 -10
- package/dist/commands/improve/extract-prompt.js +1 -2
- package/dist/commands/improve/extract.js +487 -1046
- package/dist/commands/improve/feedback-valence.js +0 -25
- package/dist/commands/improve/improve-cli.js +75 -169
- package/dist/commands/improve/improve-result-file.js +10 -66
- package/dist/commands/improve/improve-strategies.js +52 -4
- package/dist/commands/improve/improve-usage-report.js +18 -64
- package/dist/commands/improve/improve.js +480 -1074
- package/dist/commands/improve/ledger.js +119 -0
- package/dist/commands/improve/locks.js +2 -8
- package/dist/commands/improve/loop-stages.js +415 -1073
- package/dist/commands/improve/memory/derived-ref.js +12 -77
- package/dist/commands/improve/memory/memory-belief.js +16 -118
- package/dist/commands/improve/memory/memory-improve.js +266 -14
- package/dist/commands/improve/outcome-loop.js +28 -156
- package/dist/commands/improve/planner.js +5 -15
- package/dist/commands/improve/preparation.js +779 -2319
- package/dist/commands/improve/proactive-maintenance.js +34 -101
- package/dist/commands/improve/reflect-noise.js +104 -280
- package/dist/commands/improve/reflect.js +642 -1353
- package/dist/commands/improve/retrieval-gate.js +127 -0
- package/dist/commands/improve/retrieval-scope.js +92 -0
- package/dist/commands/improve/salience.js +41 -240
- package/dist/commands/improve/session-asset.js +19 -100
- package/dist/commands/improve/stage.js +322 -0
- package/dist/commands/lint/base-linter.js +37 -15
- package/dist/commands/proposal/drain.js +261 -578
- package/dist/commands/proposal/proposal-cli.js +19 -20
- package/dist/commands/proposal/proposal-types.js +31 -24
- package/dist/commands/proposal/proposal.js +38 -8
- package/dist/commands/proposal/propose.js +134 -160
- package/dist/commands/proposal/repository.js +1097 -1394
- package/dist/commands/proposal/validators/proposal-quality-validators.js +71 -174
- package/dist/commands/proposal/validators/proposal-validators.js +1 -1
- package/dist/commands/proposal/validators/proposals.js +22 -89
- package/dist/commands/read/curate.js +105 -462
- package/dist/commands/read/knowledge.js +3 -2
- package/dist/commands/read/search-cli.js +16 -33
- package/dist/commands/read/search.js +17 -23
- package/dist/commands/read/show.js +57 -108
- package/dist/commands/sources/bundle-cli.js +25 -2
- package/dist/commands/sources/bundle-config-ops.js +4 -0
- package/dist/commands/sources/dangerous-env-audit.js +1 -2
- package/dist/commands/sources/info.js +127 -29
- package/dist/commands/sources/installed-stashes.js +197 -746
- package/dist/commands/sources/schema-repair.js +98 -129
- package/dist/commands/sources/source-add.js +62 -12
- package/dist/commands/sources/source-manage.js +9 -2
- package/dist/commands/sources/stash-cli.js +24 -4
- package/dist/commands/tasks/explain.js +10 -13
- package/dist/commands/tasks/tasks-cli.js +12 -13
- package/dist/commands/tasks/tasks.js +350 -936
- package/dist/commands/tasks/validate.js +26 -24
- package/dist/commands/workflow/plan.js +22 -29
- package/dist/commands/workflow-cli.js +4 -4
- package/dist/core/adapter/adapters/akm-adapter.js +2 -1
- package/dist/core/adapter/adapters/akm-lint.js +2 -3
- package/dist/core/adapter/adapters/akm-metadata.js +42 -12
- package/dist/core/adapter/adapters/akm-task-adapter.js +29 -8
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +1 -1
- package/dist/core/adapter/execution-source.js +17 -29
- package/dist/core/asset/asset-placement.js +4 -13
- package/dist/core/asset/frontmatter.js +106 -1
- package/dist/core/asset/resolve-ref.js +1 -1
- package/dist/core/bundle-id.js +42 -5
- package/dist/core/bundle-rename.js +285 -0
- package/dist/core/config/config-io.js +1 -2
- package/dist/core/config/config-schema.js +9 -34
- package/dist/core/config/config-walker.js +1 -1
- package/dist/core/config/config.js +184 -111
- package/dist/core/config/engine-semantics.js +0 -2
- package/dist/core/config/legacy-source-shape-shim.js +38 -9
- package/dist/core/config/schema/embedding.js +20 -5
- package/dist/core/config/schema/engines.js +5 -0
- package/dist/core/config/schema/execution.js +1 -1
- package/dist/core/config/schema/experimental.js +1 -1
- package/dist/core/config/schema/improve-processes.js +54 -125
- package/dist/core/config/schema/improve.js +4 -42
- package/dist/core/config/schema/index-config.js +9 -48
- package/dist/core/config/schema/scheduler.js +12 -12
- package/dist/core/config/schema/search.js +6 -22
- package/dist/core/env-secret-ref.js +0 -1
- package/dist/core/errors.js +8 -9
- package/dist/core/file-change.js +13 -5
- package/dist/core/file-lock.js +76 -173
- package/dist/core/improve-result.js +35 -7
- package/dist/core/improve-types.js +0 -1
- package/dist/core/logs-db.js +2 -2
- package/dist/core/loopback.js +7 -12
- package/dist/core/non-task-input.js +20 -0
- package/dist/core/parse.js +13 -16
- package/dist/core/paths.js +0 -24
- package/dist/core/redaction.js +109 -2
- package/dist/core/run-lock.js +2 -5
- package/dist/core/spawn-env.js +1 -1
- package/dist/core/state/migrations.js +123 -61
- package/dist/core/state-db-scope.js +2 -4
- package/dist/core/state-db.js +126 -692
- package/dist/core/time.js +0 -20
- package/dist/core/type-presentation.js +1 -9
- package/dist/core/write-source.js +294 -1005
- package/dist/execution/input-contract.js +1 -1
- package/dist/execution/resolved-request.js +135 -689
- package/dist/execution/source.js +63 -257
- package/dist/execution/target-ref.js +1 -1
- package/dist/indexer/bundle-identity-guard.js +2 -2
- package/dist/indexer/db/llm-cache.js +2 -2
- package/dist/indexer/ensure-index.js +77 -73
- package/dist/indexer/index-rebuild-lock.js +3 -11
- package/dist/indexer/index-writer-lock.js +8 -17
- package/dist/indexer/index-written-assets.js +141 -154
- package/dist/indexer/indexer.js +400 -1124
- package/dist/indexer/links/declared-links.js +90 -0
- package/dist/indexer/materialize-embeddings.js +60 -397
- package/dist/indexer/passes/memory-inference.js +96 -90
- package/dist/indexer/passes/metadata.js +132 -219
- package/dist/indexer/read-preflight.js +0 -7
- package/dist/indexer/scan/doc-to-entry.js +2 -3
- package/dist/indexer/scan/drain-dir.js +1 -1
- package/dist/indexer/search/db-search.js +190 -590
- package/dist/indexer/search/fts-query.js +30 -41
- package/dist/indexer/search/ranking.js +28 -154
- package/dist/indexer/search/search-attribution.js +12 -32
- package/dist/indexer/search/search-fields.js +11 -15
- package/dist/indexer/search/search-hit-enrichers.js +54 -85
- package/dist/indexer/search/search-source.js +1 -4
- package/dist/indexer/usage/usage-events.js +36 -7
- package/dist/indexer/walk/walker.js +3 -4
- package/dist/integrations/agent/engine-fallback.js +23 -40
- package/dist/integrations/agent/engine-resolution.js +93 -183
- package/dist/integrations/agent/execution.js +507 -0
- package/dist/integrations/agent/model-map.js +28 -156
- package/dist/integrations/agent/request-lowering.js +66 -141
- package/dist/integrations/agent/runner-dispatch.js +143 -321
- package/dist/integrations/agent/runner.js +54 -14
- package/dist/integrations/lockfile.js +53 -101
- package/dist/llm/client.js +18 -6
- package/dist/llm/embedders/deterministic.js +2 -3
- package/dist/llm/embedders/profile.js +71 -0
- package/dist/llm/embedders/remote.js +11 -17
- package/dist/llm/feature-gate.js +0 -8
- package/dist/llm/index-passes.js +3 -5
- package/dist/llm/memory-infer.js +1 -2
- package/dist/llm/structured-call.js +5 -24
- package/dist/output/generic-render.js +23 -11
- package/dist/output/html-render.js +13 -10
- package/dist/output/render-registry.js +3 -32
- package/dist/output/shapes/helpers.js +25 -38
- package/dist/output/shapes/passthrough.js +1 -9
- package/dist/{indexer/graph/graph-types.js → output/text/bundle-rename.js} +4 -1
- package/dist/output/text/command-format.js +69 -31
- package/dist/output/text/helpers.js +1 -1
- package/dist/output/text/migrate.js +5 -14
- package/dist/output/text/proposal-format.js +48 -3
- package/dist/output/text/show-format.js +13 -17
- package/dist/output/text/workflow-format.js +0 -32
- package/dist/output/text.js +2 -0
- package/dist/registry/factory.js +4 -19
- package/dist/registry/network.js +66 -220
- package/dist/registry/providers/index.js +0 -2
- package/dist/registry/providers/skills-sh.js +3 -14
- package/dist/registry/providers/static-index.js +24 -26
- package/dist/registry/resolve.js +55 -131
- package/dist/scripts/akm-migrate-node.js +42948 -92369
- package/dist/scripts/akm-migrate.js +42935 -92354
- package/dist/setup/registry-stash-loader.js +4 -13
- package/dist/setup/semantic-assets.js +3 -44
- package/dist/setup/setup.js +1 -1
- package/dist/setup/steps/connection.js +5 -6
- package/dist/setup/steps/platforms.js +2 -2
- package/dist/setup/steps/tasks.js +25 -15
- package/dist/sources/provider-factory.js +17 -18
- package/dist/sources/providers/filesystem.js +2 -3
- package/dist/sources/providers/git-install.js +7 -1
- package/dist/sources/providers/git-provider.js +0 -3
- package/dist/sources/providers/git-stash.js +83 -21
- package/dist/sources/providers/npm.js +2 -4
- package/dist/sources/providers/provider-utils.js +5 -10
- package/dist/sources/providers/website.js +0 -2
- package/dist/sources/snapshot-fetchers/website-ingest.js +1 -1
- package/dist/sources/website-url.js +2 -2
- package/dist/storage/database.js +9 -35
- package/dist/storage/repositories/improve-ledger-repository.js +209 -0
- package/dist/storage/repositories/index-connection.js +39 -72
- package/dist/storage/repositories/index-entries-repository.js +131 -129
- package/dist/storage/repositories/index-entry-mapper.js +1 -2
- package/dist/storage/repositories/index-entry-schema.js +101 -268
- package/dist/storage/repositories/index-fts-repository.js +86 -256
- package/dist/storage/repositories/index-links-repository.js +143 -0
- package/dist/storage/repositories/index-llm-cache-repository.js +7 -9
- package/dist/storage/repositories/index-meta-repository.js +6 -4
- package/dist/storage/repositories/index-schema.js +257 -325
- package/dist/storage/repositories/index-utility-repository.js +8 -29
- package/dist/storage/repositories/index-vec-repository.js +133 -414
- package/dist/storage/repositories/outcome-repository.js +2 -1
- package/dist/storage/repositories/proposals-repository.js +104 -1
- package/dist/storage/repositories/registry-index-cache-repository.js +100 -0
- package/dist/storage/repositories/salience-repository.js +1 -19
- package/dist/storage/repositories/task-history-repository.js +26 -4
- package/dist/storage/repositories/workflow-runs-repository.js +53 -244
- package/dist/storage/sqlite-migrations.js +136 -0
- package/dist/storage/sqlite-pragmas.js +11 -9
- package/dist/storage/sqlite-transaction.js +170 -0
- package/dist/storage/state-db-integrity.js +130 -0
- package/dist/tasks/activation-config.js +134 -62
- package/dist/tasks/backends/cron.js +191 -302
- package/dist/tasks/backends/exec-utils.js +2 -5
- package/dist/tasks/backends/launchd.js +141 -748
- package/dist/tasks/backends/schtasks.js +119 -623
- package/dist/tasks/prepare/prepare-support.js +5 -15
- package/dist/tasks/prepare/prepare.js +0 -2
- package/dist/tasks/resolve-akm-bin.js +20 -79
- package/dist/tasks/run/attempt-lifecycle.js +0 -1
- package/dist/tasks/run/load-task.js +1 -1
- package/dist/tasks/scheduler-binding.js +20 -238
- package/dist/tasks/scheduler-invocation.js +136 -244
- package/dist/tasks/scheduler-lock.js +53 -0
- package/dist/tasks/scheduler-sync.js +368 -679
- package/dist/tasks/source/parse-task-source.js +55 -9
- package/dist/tasks/source/task-source-v3-frozen.js +3 -4
- package/dist/tasks/source/task-to-v4.js +464 -88
- package/dist/workflows/authoring/authoring.js +3 -12
- package/dist/workflows/compile.js +211 -0
- package/dist/workflows/concurrency-policy.js +13 -74
- package/dist/workflows/exec/child-invocation.js +3 -17
- package/dist/workflows/exec/child-workflow.js +32 -141
- package/dist/workflows/exec/dispatch-redaction.js +13 -53
- package/dist/workflows/exec/environment.js +98 -0
- package/dist/workflows/exec/exec-unit.js +33 -140
- package/dist/workflows/exec/frozen-judge.js +7 -59
- package/dist/workflows/exec/native-executor.js +82 -341
- package/dist/workflows/exec/param-secrets.js +29 -47
- package/dist/workflows/exec/run-workflow.js +154 -387
- package/dist/workflows/exec/scheduler.js +9 -36
- package/dist/workflows/exec/step-work.js +127 -430
- package/dist/workflows/exec/unit-dispatch.js +11 -63
- package/dist/workflows/exec/unit-writer.js +8 -52
- package/dist/workflows/exec/worktree.js +39 -273
- package/dist/workflows/freeze/child-output-references.js +4 -15
- package/dist/workflows/freeze/environment.js +99 -92
- package/dist/workflows/freeze/freeze.js +172 -0
- package/dist/workflows/freeze/step-values.js +19 -21
- package/dist/workflows/freeze/targets/child-workflow.js +23 -92
- package/dist/workflows/freeze/targets/command.js +10 -33
- package/dist/workflows/freeze/targets/script.js +5 -12
- package/dist/workflows/freeze/targets/shell.js +3 -6
- package/dist/workflows/freeze/targets/task.js +25 -80
- package/dist/workflows/freeze/task-bindings.js +20 -67
- package/dist/workflows/{source-ir/github-yaml.js → github-yaml.js} +88 -206
- package/dist/workflows/ir/params.js +6 -51
- package/dist/workflows/ir/plan-hash.js +2 -34
- package/dist/workflows/parser.js +140 -43
- package/dist/{commands/improve/consolidate/types.js → workflows/plan.js} +2 -1
- package/dist/workflows/renderer.js +36 -69
- package/dist/workflows/resource-limits.js +12 -120
- package/dist/workflows/runtime/agent-identity.js +8 -40
- package/dist/workflows/runtime/run-outputs.js +3 -6
- package/dist/workflows/runtime/run-plan.js +316 -0
- package/dist/workflows/runtime/runs.js +48 -200
- package/dist/workflows/runtime/workflow-asset-loader.js +24 -57
- package/dist/workflows/{source-ir/semantics.js → source-semantics.js} +16 -20
- package/dist/workflows/validate-summary.js +2 -7
- package/docs/integration/bundling-akm.md +49 -42
- package/docs/migration/README.md +1 -0
- package/docs/migration/release-notes/0.9.17.md +43 -0
- package/docs/migration/v0.9.1-to-v0.9.2.md +23 -7
- package/docs/reference/cli.md +232 -135
- package/docs/reference/configuration.md +71 -57
- package/docs/reference/data-and-telemetry.md +20 -21
- package/docs/reference/tasks.md +105 -39
- package/docs/reference/workflow-schema.md +14 -18
- package/docs/reference/workflows.md +6 -9
- package/package.json +1 -1
- package/schemas/akm-config.json +115 -738
- package/schemas/akm-workflow.json +1 -0
- package/dist/assets/improve-strategies/graph-refresh.json +0 -15
- package/dist/assets/prompts/contradiction-judge.md +0 -33
- package/dist/assets/prompts/graph-extract-system.md +0 -1
- package/dist/assets/prompts/graph-extract-user-prompt.md +0 -35
- package/dist/assets/prompts/metadata-enhance-system.md +0 -1
- package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +0 -4
- package/dist/commands/health/advisories.js +0 -150
- package/dist/commands/health/metrics.js +0 -329
- package/dist/commands/health/surfaces.js +0 -102
- package/dist/commands/improve/anti-collapse.js +0 -83
- package/dist/commands/improve/collapse-detector.js +0 -432
- package/dist/commands/improve/consolidate/eligibility.js +0 -48
- package/dist/commands/improve/consolidate/merge.js +0 -149
- package/dist/commands/improve/distill/promote-memory.js +0 -291
- package/dist/commands/improve/distill/quality-gate.js +0 -337
- package/dist/commands/improve/eval-cases.js +0 -52
- package/dist/commands/improve/memory/memory-contradiction-detect.js +0 -291
- package/dist/commands/improve/proposal-envelope.js +0 -31
- package/dist/commands/improve/run-context.js +0 -123
- package/dist/commands/improve/shared.js +0 -31
- package/dist/commands/improve/source-identity.js +0 -28
- package/dist/commands/improve/triage.js +0 -96
- package/dist/commands/proposal/drain-policies.js +0 -151
- package/dist/commands/sources/update-transaction.js +0 -220
- package/dist/core/action-contributors.js +0 -28
- package/dist/core/config/config-version-shim.js +0 -101
- package/dist/core/fs-txn.js +0 -405
- package/dist/core/lexical-score.js +0 -25
- package/dist/core/maintenance-barrier.js +0 -167
- package/dist/execution/executable-identity.js +0 -105
- package/dist/execution/guarded-source.js +0 -427
- package/dist/indexer/db/graph-db.js +0 -444
- package/dist/indexer/graph/graph-boost.js +0 -427
- package/dist/indexer/graph/graph-dedup.js +0 -95
- package/dist/indexer/graph/graph-extraction.js +0 -1108
- package/dist/indexer/search/name-match.js +0 -35
- package/dist/indexer/search/ranking-contributors.js +0 -515
- package/dist/indexer/search/ranking-types.js +0 -4
- package/dist/indexer/walk/project-context.js +0 -192
- package/dist/integrations/agent/execution-cascade.js +0 -566
- package/dist/integrations/agent/execution-definitions.js +0 -202
- package/dist/integrations/agent/execution-lowering.js +0 -841
- package/dist/integrations/agent/execution-preparation.js +0 -98
- package/dist/integrations/agent/inline-execution.js +0 -74
- package/dist/llm/graph-extract.js +0 -728
- package/dist/llm/metadata-enhance.js +0 -96
- package/dist/registry/create-provider-registry.js +0 -29
- package/dist/registry/pinned-request-helper.js +0 -247
- package/dist/registry/pinned-transport.js +0 -717
- package/dist/sources/providers/index.js +0 -14
- package/dist/storage/engines/sqlite-migrations.js +0 -271
- package/dist/storage/repositories/canaries-repository.js +0 -107
- package/dist/storage/repositories/embedding-salvage-repository.js +0 -184
- package/dist/storage/repositories/registry-cache.js +0 -113
- package/dist/tasks/scheduler-sync-preview.js +0 -52
- package/dist/tasks/source/task-to-v3.js +0 -507
- package/dist/workflows/freeze/resolve-steps.js +0 -86
- package/dist/workflows/freeze/source-freeze.js +0 -64
- package/dist/workflows/ir/compile.js +0 -321
- package/dist/workflows/ir/environment-v4.js +0 -330
- package/dist/workflows/ir/freeze-v4.js +0 -153
- package/dist/workflows/ir/schema-v4.js +0 -745
- package/dist/workflows/ir/schema.js +0 -354
- package/dist/workflows/program/schema.js +0 -77
- package/dist/workflows/runtime/checkin.js +0 -57
- package/dist/workflows/runtime/plan-classifier.js +0 -196
- package/dist/workflows/runtime/unit-checkin.js +0 -45
- package/dist/workflows/runtime/unit-phases.js +0 -20
- package/dist/workflows/schema.js +0 -4
- package/dist/workflows/source-ir/compile.js +0 -200
- package/dist/workflows/source-ir/program.js +0 -50
- package/dist/workflows/source-ir/result.js +0 -26
- package/dist/workflows/source-ir/schema.js +0 -786
- package/dist/workflows/source-ir/triggers.js +0 -79
- package/dist/workflows/source-ir/uses.js +0 -40
- package/dist/workflows/validator.js +0 -60
|
@@ -1,13 +1,39 @@
|
|
|
1
1
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* How a native scheduler row invokes akm.
|
|
6
|
+
*
|
|
7
|
+
* A row is `<launcher…> task run <id> --bundle <bundle> --scheduled
|
|
8
|
+
* [--<input> <value>…]`, or `<launcher…> workflow run <qualified-ref>`, and it
|
|
9
|
+
* carries its own context: the environment the scheduled process needs,
|
|
10
|
+
* set inline by the row itself (a cron `VAR=value` prefix, a launchd
|
|
11
|
+
* `EnvironmentVariables` entry, a PowerShell `$env:` assignment).
|
|
12
|
+
*
|
|
13
|
+
* - `AKM_BUNDLE_DIR`: the working stash of the process that ran
|
|
14
|
+
* `akm task sync`, on every row. The scheduled run uses the same working
|
|
15
|
+
* stash, `--bundle <name>` finds a stash no config names, and sync
|
|
16
|
+
* attributes the row to the installation that wrote it (#846).
|
|
17
|
+
* - `AKM_CONFIG_DIR`, `AKM_DATA_DIR`, `AKM_CACHE_DIR` and `AKM_STATE_DIR`
|
|
18
|
+
* only when the process that ran `akm task sync` set them explicitly. A
|
|
19
|
+
* default resolves at fire time exactly as it does for an interactive
|
|
20
|
+
* command.
|
|
21
|
+
*
|
|
22
|
+
* That is exactly what the `--scheduler-context` descriptor held.
|
|
23
|
+
*
|
|
24
|
+
* `PATH` is the scheduler's own: the crontab's `# akm:env` block, the plist's
|
|
25
|
+
* `EnvironmentVariables`.
|
|
26
|
+
*
|
|
27
|
+
* Rows written by 0.9.0 through 0.9.17-alpha.6 name a
|
|
28
|
+
* `--scheduler-context <descriptor>` file instead. The CLI still accepts that
|
|
29
|
+
* argument and applies the file's environment, so such a row keeps firing
|
|
30
|
+
* until the next `akm task sync` rewrites it.
|
|
31
|
+
*/
|
|
5
32
|
import fs from "node:fs";
|
|
6
33
|
import path from "node:path";
|
|
7
34
|
import { bundleRefToString, parseBundleRef } from "../core/asset/asset-ref.js";
|
|
8
35
|
import { resolveStashDir } from "../core/common.js";
|
|
9
36
|
import { ConfigError } from "../core/errors.js";
|
|
10
|
-
import { getCacheDir, getConfigDir, getDataDir, getTaskContextDir } from "../core/paths.js";
|
|
11
37
|
import { INPUT_NAME_PATTERN } from "../execution/input-contract.js";
|
|
12
38
|
import { normaliseTaskConceptId } from "./task-id.js";
|
|
13
39
|
export const SCHEDULED_TASK_CONTEXT_KEYS = [
|
|
@@ -18,9 +44,18 @@ export const SCHEDULED_TASK_CONTEXT_KEYS = [
|
|
|
18
44
|
"AKM_STATE_DIR",
|
|
19
45
|
];
|
|
20
46
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
47
|
+
* Directories a row carries ONLY when the process that ran `task sync` had
|
|
48
|
+
* them set explicitly in its environment. They are never resolved from
|
|
49
|
+
* defaults: a sync run from inside an app whose environment pointed `$STATE`
|
|
50
|
+
* somewhere private (an OpenCode desktop session, 2026-08 → 2026-09) froze
|
|
51
|
+
* that directory into eight cron rows, and the scheduled improve runs then
|
|
52
|
+
* held their locks in a `locks/` directory no interactive command could see.
|
|
53
|
+
*/
|
|
54
|
+
const EXPLICIT_CONTEXT_KEYS = ["AKM_CONFIG_DIR", "AKM_DATA_DIR", "AKM_CACHE_DIR", "AKM_STATE_DIR"];
|
|
55
|
+
/**
|
|
56
|
+
* The AKM directory context of the current process. Child environments that
|
|
57
|
+
* are built from an allowlist must forward this closed set explicitly so
|
|
58
|
+
* nested AKM commands stay in the same installation.
|
|
24
59
|
*/
|
|
25
60
|
export function scheduledTaskContextEnv(env = process.env) {
|
|
26
61
|
const out = {};
|
|
@@ -31,134 +66,112 @@ export function scheduledTaskContextEnv(env = process.env) {
|
|
|
31
66
|
}
|
|
32
67
|
return out;
|
|
33
68
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
// Capture the complete process directory context used by scheduled commands.
|
|
43
|
-
AKM_STATE_DIR: path.resolve(resolveStateDir(env, platform)),
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
/** Build an installed argv from one already-validated public scheduler tail. */
|
|
47
|
-
export function buildScheduledBindingInvocation(akmArgv, contextPath, invocation) {
|
|
48
|
-
const parsed = parsePublicSchedulerInvocation(invocation);
|
|
49
|
-
if (!parsed)
|
|
50
|
-
throw invalidSchedulerInvocation();
|
|
51
|
-
return {
|
|
52
|
-
argv: [...akmArgv, SCHEDULER_CONTEXT_ARG, assertAbsolutePath(contextPath), ...parsed.invocation],
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
export function schedulerContextDescriptor(context = resolveScheduledTaskContext(), envPath = process.env.PATH ?? "") {
|
|
56
|
-
return {
|
|
57
|
-
version: 1,
|
|
58
|
-
environment: { ...canonicalContext(context), PATH: validatePathValue(envPath) },
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
export function schedulerContextPath(descriptor) {
|
|
62
|
-
const bytes = serializeDescriptor(descriptor);
|
|
63
|
-
const digest = createHash("sha256").update(bytes).digest("hex");
|
|
64
|
-
return path.join(getTaskContextDir(descriptor.environment), `${digest}.json`);
|
|
65
|
-
}
|
|
66
|
-
/** Write a content-addressed descriptor without ever replacing existing content. */
|
|
67
|
-
export function writeSchedulerContextDescriptor(descriptor = schedulerContextDescriptor()) {
|
|
68
|
-
const file = schedulerContextPath(descriptor);
|
|
69
|
-
const bytes = serializeDescriptor(descriptor);
|
|
70
|
-
fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
71
|
-
if (process.platform !== "win32") {
|
|
72
|
-
fs.chmodSync(path.dirname(file), 0o700);
|
|
73
|
-
}
|
|
74
|
-
if (lstatIfExists(file)) {
|
|
75
|
-
if (serializeDescriptor(validateSchedulerContextDescriptor(file)) !== bytes)
|
|
76
|
-
throw invalidSchedulerContext();
|
|
77
|
-
return file;
|
|
78
|
-
}
|
|
79
|
-
const temp = path.join(path.dirname(file), `.${path.basename(file)}.${process.pid}.${Date.now()}.tmp`);
|
|
69
|
+
/**
|
|
70
|
+
* The environment every row a sync writes sets inline: `AKM_BUNDLE_DIR`,
|
|
71
|
+
* the syncing process's working stash, plus whichever `AKM_*_DIR` overrides
|
|
72
|
+
* it set explicitly. With no working stash to resolve (no default bundle, no
|
|
73
|
+
* `AKM_BUNDLE_DIR`), rows name their bundle by `--bundle` alone.
|
|
74
|
+
*/
|
|
75
|
+
export function scheduledRowEnvironment(env = process.env) {
|
|
76
|
+
const out = {};
|
|
80
77
|
try {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
catch (error) {
|
|
86
|
-
if (!lstatIfExists(file) || serializeDescriptor(validateSchedulerContextDescriptor(file)) !== bytes)
|
|
87
|
-
throw error;
|
|
88
|
-
}
|
|
89
|
-
restrictDescriptor(file);
|
|
90
|
-
validateSchedulerContextDescriptor(file);
|
|
78
|
+
out.AKM_BUNDLE_DIR = path.resolve(resolveStashDir(env));
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// No working stash: there is no primary bundle to attribute rows to either.
|
|
91
82
|
}
|
|
92
|
-
|
|
93
|
-
|
|
83
|
+
for (const key of EXPLICIT_CONTEXT_KEYS) {
|
|
84
|
+
const value = env[key]?.trim();
|
|
85
|
+
if (value)
|
|
86
|
+
out[key] = path.resolve(value);
|
|
94
87
|
}
|
|
95
|
-
return
|
|
88
|
+
return out;
|
|
96
89
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
90
|
+
/** A row environment's entries, in {@link SCHEDULED_TASK_CONTEXT_KEYS} order. */
|
|
91
|
+
export function scheduledRowEnvironmentEntries(environment) {
|
|
92
|
+
const entries = [];
|
|
93
|
+
for (const key of SCHEDULED_TASK_CONTEXT_KEYS) {
|
|
94
|
+
const value = environment?.[key];
|
|
95
|
+
if (value)
|
|
96
|
+
entries.push([key, value]);
|
|
97
|
+
}
|
|
98
|
+
return entries;
|
|
102
99
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
100
|
+
/** Keep the {@link SCHEDULED_TASK_CONTEXT_KEYS} a parsed row set; `undefined` when there are none. */
|
|
101
|
+
export function scheduledRowEnvironmentFrom(variables) {
|
|
102
|
+
const out = {};
|
|
103
|
+
for (const key of SCHEDULED_TASK_CONTEXT_KEYS) {
|
|
104
|
+
const value = variables[key];
|
|
105
|
+
if (value)
|
|
106
|
+
out[key] = value;
|
|
108
107
|
}
|
|
109
|
-
|
|
110
|
-
|
|
108
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
109
|
+
}
|
|
110
|
+
const SCHEDULER_CONTEXT_ARG = "--scheduler-context";
|
|
111
|
+
/** The installed argv: the launcher, then one already-validated public scheduler tail. */
|
|
112
|
+
export function buildScheduledInvocation(akmArgv, invocation) {
|
|
113
|
+
const parsed = parsePublicSchedulerInvocation(invocation);
|
|
114
|
+
if (!parsed)
|
|
115
|
+
throw invalidSchedulerInvocation();
|
|
116
|
+
return [...akmArgv, ...parsed.invocation];
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Parse an installed row's argv (after any inline environment): the current
|
|
120
|
+
* shape, the `--scheduler-context <descriptor>` shape 0.9.0 – 0.9.17-alpha.6
|
|
121
|
+
* wrote, or the bare shape before that. A row that carries
|
|
122
|
+
* `--scheduler-context` but does not parse around it is never reread as
|
|
123
|
+
* another shape.
|
|
124
|
+
*/
|
|
125
|
+
export function parseScheduledInvocationArgv(argv) {
|
|
126
|
+
const contextIndex = argv.indexOf(SCHEDULER_CONTEXT_ARG);
|
|
127
|
+
if (contextIndex !== -1) {
|
|
128
|
+
if (contextIndex < 1 || argv.indexOf(SCHEDULER_CONTEXT_ARG, contextIndex + 1) !== -1)
|
|
129
|
+
return undefined;
|
|
130
|
+
const contextPath = argv[contextIndex + 1];
|
|
131
|
+
if (!contextPath)
|
|
132
|
+
return undefined;
|
|
133
|
+
const publicInvocation = parsePublicSchedulerInvocation(argv.slice(contextIndex + 2));
|
|
134
|
+
if (!publicInvocation)
|
|
135
|
+
return undefined;
|
|
136
|
+
return { binding: argv.slice(0, contextIndex), contextPath, ...publicInvocation };
|
|
111
137
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
let descriptorBytes;
|
|
117
|
-
let fd;
|
|
118
|
-
try {
|
|
119
|
-
const noFollow = process.platform === "win32" ? 0 : fs.constants.O_NOFOLLOW;
|
|
120
|
-
fd = fs.openSync(absolute, fs.constants.O_RDONLY | noFollow);
|
|
121
|
-
const stat = fs.fstatSync(fd);
|
|
122
|
-
if (!stat.isFile())
|
|
123
|
-
throw schedulerContextFileError(absolute, "path is not a regular file");
|
|
124
|
-
if (process.platform !== "win32") {
|
|
125
|
-
if (typeof process.getuid !== "function") {
|
|
126
|
-
throw schedulerContextFileError(absolute, "current uid is unavailable for ownership verification");
|
|
127
|
-
}
|
|
128
|
-
const uid = process.getuid();
|
|
129
|
-
if (stat.uid !== uid)
|
|
130
|
-
throw schedulerContextFileError(absolute, `file owner ${stat.uid} does not match uid ${uid}`);
|
|
131
|
-
if ((stat.mode & 0o077) !== 0) {
|
|
132
|
-
throw schedulerContextFileError(absolute, "group or other permissions must be disabled");
|
|
133
|
-
}
|
|
138
|
+
for (let index = 1; index < argv.length - 1; index += 1) {
|
|
139
|
+
if ((argv[index] === "task" || argv[index] === "workflow") && argv[index + 1] === "run") {
|
|
140
|
+
const publicInvocation = parsePublicSchedulerInvocation(argv.slice(index));
|
|
141
|
+
return publicInvocation ? { binding: argv.slice(0, index), ...publicInvocation } : undefined;
|
|
134
142
|
}
|
|
135
|
-
descriptorBytes = fs.readFileSync(fd);
|
|
136
|
-
}
|
|
137
|
-
catch (error) {
|
|
138
|
-
if (error instanceof ConfigError)
|
|
139
|
-
throw error;
|
|
140
|
-
throw schedulerContextFileError(absolute, error instanceof Error ? error.message : String(error));
|
|
141
|
-
}
|
|
142
|
-
finally {
|
|
143
|
-
if (fd !== undefined)
|
|
144
|
-
fs.closeSync(fd);
|
|
145
|
-
}
|
|
146
|
-
const filename = path.basename(absolute);
|
|
147
|
-
const match = /^([a-f0-9]{64})\.json$/.exec(filename);
|
|
148
|
-
const digest = createHash("sha256").update(descriptorBytes).digest("hex");
|
|
149
|
-
if (!match || match[1] !== digest) {
|
|
150
|
-
throw schedulerContextFileError(absolute, "content SHA-256 does not match the descriptor filename");
|
|
151
143
|
}
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* The environment a `--scheduler-context` descriptor names: its `AKM_*_DIR`
|
|
148
|
+
* keys and, in one written before 0.9.17, `PATH`. It is read as plain JSON —
|
|
149
|
+
* the file is this user's own, next to the data it names.
|
|
150
|
+
*/
|
|
151
|
+
export function readLegacySchedulerContext(file) {
|
|
152
152
|
let parsed;
|
|
153
153
|
try {
|
|
154
|
-
parsed = JSON.parse(
|
|
154
|
+
parsed = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
155
155
|
}
|
|
156
156
|
catch (error) {
|
|
157
|
-
throw
|
|
157
|
+
throw new ConfigError(`Cannot read scheduler context "${file}": ${error instanceof Error ? error.message : String(error)}. ` +
|
|
158
|
+
"Run `akm task sync` to rewrite this scheduled row.", "INVALID_CONFIG_FILE");
|
|
158
159
|
}
|
|
159
|
-
|
|
160
|
+
const environment = parsed !== null && typeof parsed === "object" ? parsed.environment : undefined;
|
|
161
|
+
const out = {};
|
|
162
|
+
if (environment === null || typeof environment !== "object")
|
|
163
|
+
return out;
|
|
164
|
+
for (const key of [...SCHEDULED_TASK_CONTEXT_KEYS, "PATH"]) {
|
|
165
|
+
const value = environment[key];
|
|
166
|
+
if (typeof value === "string" && value.length > 0)
|
|
167
|
+
out[key] = value;
|
|
168
|
+
}
|
|
169
|
+
return out;
|
|
160
170
|
}
|
|
161
|
-
/**
|
|
171
|
+
/**
|
|
172
|
+
* Apply and remove a row's `--scheduler-context <descriptor>` before citty
|
|
173
|
+
* parses argv. Only a row written by 0.9.0 – 0.9.17-alpha.6 passes it.
|
|
174
|
+
*/
|
|
162
175
|
export function consumeSchedulerContextArg(argv, env = process.env) {
|
|
163
176
|
const separator = argv.indexOf("--");
|
|
164
177
|
const index = argv.slice(0, separator === -1 ? argv.length : separator).indexOf(SCHEDULER_CONTEXT_ARG);
|
|
@@ -166,37 +179,16 @@ export function consumeSchedulerContextArg(argv, env = process.env) {
|
|
|
166
179
|
return argv;
|
|
167
180
|
const file = argv[index + 1];
|
|
168
181
|
if (!file) {
|
|
169
|
-
throw new ConfigError(`${SCHEDULER_CONTEXT_ARG} requires
|
|
182
|
+
throw new ConfigError(`${SCHEDULER_CONTEXT_ARG} requires a descriptor path.`, "INVALID_CONFIG_FILE");
|
|
170
183
|
}
|
|
171
|
-
|
|
184
|
+
Object.assign(env, readLegacySchedulerContext(file));
|
|
172
185
|
return [...argv.slice(0, index), ...argv.slice(index + 2)];
|
|
173
186
|
}
|
|
174
|
-
/** Parse either the public scheduled task or qualified workflow invocation. */
|
|
175
|
-
export function parseScheduledBindingArgv(argv) {
|
|
176
|
-
const contextIndex = argv.indexOf(SCHEDULER_CONTEXT_ARG);
|
|
177
|
-
if (contextIndex < 1 || argv.indexOf(SCHEDULER_CONTEXT_ARG, contextIndex + 1) !== -1)
|
|
178
|
-
return undefined;
|
|
179
|
-
const contextPath = argv[contextIndex + 1];
|
|
180
|
-
if (!contextPath)
|
|
181
|
-
return undefined;
|
|
182
|
-
const publicInvocation = parsePublicSchedulerInvocation(argv.slice(contextIndex + 2));
|
|
183
|
-
if (!publicInvocation)
|
|
184
|
-
return undefined;
|
|
185
|
-
return {
|
|
186
|
-
binding: [...argv.slice(0, contextIndex)],
|
|
187
|
-
contextPath: assertAbsolutePath(contextPath),
|
|
188
|
-
invocation: publicInvocation.invocation,
|
|
189
|
-
...(publicInvocation.target !== undefined ? { target: publicInvocation.target } : {}),
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
187
|
/**
|
|
193
|
-
* Parse just the public `task run …` / `workflow run …` tail,
|
|
194
|
-
*
|
|
195
|
-
* pre-`--scheduler-context` invocation still sitting inside akm's own
|
|
196
|
-
* ownership-marked block (see `extractCronInvocation` in
|
|
197
|
-
* `src/tasks/backends/cron.ts`) without re-implementing this grammar.
|
|
188
|
+
* Parse just the public `task run …` / `workflow run …` tail, the part of a
|
|
189
|
+
* row that names what it runs.
|
|
198
190
|
*/
|
|
199
|
-
|
|
191
|
+
function parsePublicSchedulerInvocation(invocation) {
|
|
200
192
|
if (invocation[0] === "task" && invocation[1] === "run" && invocation[2]) {
|
|
201
193
|
try {
|
|
202
194
|
if (normaliseTaskConceptId(invocation[2]) !== invocation[2])
|
|
@@ -290,106 +282,6 @@ function isValidSchedulerInputFlagTail(tail) {
|
|
|
290
282
|
}
|
|
291
283
|
return true;
|
|
292
284
|
}
|
|
293
|
-
function canonicalContext(input) {
|
|
294
|
-
const inputKeys = Object.keys(input);
|
|
295
|
-
if (inputKeys.length !== SCHEDULED_TASK_CONTEXT_KEYS.length ||
|
|
296
|
-
inputKeys.some((key) => !SCHEDULED_TASK_CONTEXT_KEYS.includes(key))) {
|
|
297
|
-
throw invalidSchedulerContext();
|
|
298
|
-
}
|
|
299
|
-
const context = {};
|
|
300
|
-
for (const key of SCHEDULED_TASK_CONTEXT_KEYS) {
|
|
301
|
-
const value = input[key];
|
|
302
|
-
if (typeof value !== "string" ||
|
|
303
|
-
value.trim().length === 0 ||
|
|
304
|
-
containsControlCharacter(value) ||
|
|
305
|
-
(!path.posix.isAbsolute(value) && !path.win32.isAbsolute(value))) {
|
|
306
|
-
throw invalidSchedulerContext();
|
|
307
|
-
}
|
|
308
|
-
context[key] = value;
|
|
309
|
-
}
|
|
310
|
-
return context;
|
|
311
|
-
}
|
|
312
|
-
function canonicalDescriptor(input) {
|
|
313
|
-
if (typeof input !== "object" || input === null || Array.isArray(input))
|
|
314
|
-
throw invalidSchedulerContext();
|
|
315
|
-
const record = input;
|
|
316
|
-
if (Object.keys(record).length !== 2 || record.version !== 1)
|
|
317
|
-
throw invalidSchedulerContext();
|
|
318
|
-
const rawEnvironment = record.environment;
|
|
319
|
-
if (typeof rawEnvironment !== "object" || rawEnvironment === null || Array.isArray(rawEnvironment)) {
|
|
320
|
-
throw invalidSchedulerContext();
|
|
321
|
-
}
|
|
322
|
-
const environment = rawEnvironment;
|
|
323
|
-
if (Object.keys(environment).length !== SCHEDULED_TASK_CONTEXT_KEYS.length + 1)
|
|
324
|
-
throw invalidSchedulerContext();
|
|
325
|
-
const PATH = validatePathValue(environment.PATH);
|
|
326
|
-
const { PATH: _, ...rawContext } = environment;
|
|
327
|
-
return { version: 1, environment: { ...canonicalContext(rawContext), PATH } };
|
|
328
|
-
}
|
|
329
|
-
function serializeDescriptor(descriptor) {
|
|
330
|
-
return `${JSON.stringify(canonicalDescriptor(descriptor))}\n`;
|
|
331
|
-
}
|
|
332
|
-
function validatePathValue(value) {
|
|
333
|
-
if (typeof value !== "string" || containsControlCharacter(value))
|
|
334
|
-
throw invalidSchedulerContext();
|
|
335
|
-
return value;
|
|
336
|
-
}
|
|
337
|
-
function assertAbsolutePath(value) {
|
|
338
|
-
if (containsControlCharacter(value) || (!path.posix.isAbsolute(value) && !path.win32.isAbsolute(value))) {
|
|
339
|
-
throw invalidSchedulerContext();
|
|
340
|
-
}
|
|
341
|
-
return value;
|
|
342
|
-
}
|
|
343
|
-
function restrictDescriptor(file) {
|
|
344
|
-
if (process.platform !== "win32")
|
|
345
|
-
fs.chmodSync(file, 0o600);
|
|
346
|
-
}
|
|
347
|
-
function lstatIfExists(file) {
|
|
348
|
-
try {
|
|
349
|
-
return fs.lstatSync(file);
|
|
350
|
-
}
|
|
351
|
-
catch (error) {
|
|
352
|
-
if (error.code === "ENOENT")
|
|
353
|
-
return undefined;
|
|
354
|
-
throw error;
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
function schedulerContextFileError(file, reason) {
|
|
358
|
-
return new ConfigError(`Invalid scheduler context descriptor "${file}": ${reason}.`, "INVALID_CONFIG_FILE");
|
|
359
|
-
}
|
|
360
|
-
function containsControlCharacter(value) {
|
|
361
|
-
for (const char of value) {
|
|
362
|
-
const code = char.codePointAt(0) ?? 0;
|
|
363
|
-
if (code < 0x20 || (code >= 0x7f && code <= 0x9f))
|
|
364
|
-
return true;
|
|
365
|
-
}
|
|
366
|
-
return false;
|
|
367
|
-
}
|
|
368
|
-
function resolveStateDir(env, platform) {
|
|
369
|
-
const override = env.AKM_STATE_DIR?.trim();
|
|
370
|
-
if (override)
|
|
371
|
-
return override;
|
|
372
|
-
if (platform === "win32") {
|
|
373
|
-
const localAppData = env.LOCALAPPDATA?.trim();
|
|
374
|
-
if (localAppData)
|
|
375
|
-
return path.join(localAppData, "akm", "state");
|
|
376
|
-
const userProfile = env.USERPROFILE?.trim();
|
|
377
|
-
if (userProfile)
|
|
378
|
-
return path.join(userProfile, "AppData", "Local", "akm", "state");
|
|
379
|
-
const appData = env.APPDATA?.trim();
|
|
380
|
-
if (appData)
|
|
381
|
-
return path.join(appData, "..", "Local", "akm", "state");
|
|
382
|
-
throw new ConfigError("Unable to determine state directory. Set LOCALAPPDATA, USERPROFILE, or APPDATA.", "CONFIG_DIR_UNRESOLVABLE");
|
|
383
|
-
}
|
|
384
|
-
const xdgStateHome = env.XDG_STATE_HOME?.trim();
|
|
385
|
-
if (xdgStateHome)
|
|
386
|
-
return path.join(xdgStateHome, "akm");
|
|
387
|
-
const home = env.HOME?.trim();
|
|
388
|
-
return home ? path.join(home, ".local", "state", "akm") : path.join("/tmp", "akm-state");
|
|
389
|
-
}
|
|
390
|
-
function invalidSchedulerContext() {
|
|
391
|
-
return new ConfigError(`Invalid scheduler context; expected exactly ${SCHEDULED_TASK_CONTEXT_KEYS.join(", ")} as absolute paths.`, "INVALID_CONFIG_FILE");
|
|
392
|
-
}
|
|
393
285
|
function invalidSchedulerInvocation() {
|
|
394
286
|
return new ConfigError("Invalid scheduler invocation; expected public " +
|
|
395
287
|
"`task run <id> [--bundle <bundle>] --scheduled [--<input> <value>…]` or `workflow run <qualified-ref>` argv.", "INVALID_CONFIG_FILE");
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
/**
|
|
5
|
+
* The one process lock around native scheduler writes.
|
|
6
|
+
*
|
|
7
|
+
* `task sync|add|enable|disable|prune --yes` read the installed rows, plan,
|
|
8
|
+
* and apply under it, so two akm processes never interleave their writes to
|
|
9
|
+
* the same crontab or LaunchAgents directory. It is an `O_EXCL` lock file
|
|
10
|
+
* under `$STATE/locks`, reclaimed only from a verifiably dead holder, and
|
|
11
|
+
* re-entrant within one process (`add` runs `sync` inside it).
|
|
12
|
+
*/
|
|
13
|
+
import fs from "node:fs";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
import { TransientError } from "../core/errors.js";
|
|
16
|
+
import { createLockPayload, probeLock, reclaimStaleLock, releaseLock, tryAcquireLockSync } from "../core/file-lock.js";
|
|
17
|
+
import { getStateDir } from "../core/paths.js";
|
|
18
|
+
export function schedulerLockPath() {
|
|
19
|
+
return path.join(getStateDir(), "locks", "scheduler.lock");
|
|
20
|
+
}
|
|
21
|
+
let depth = 0;
|
|
22
|
+
export async function withSchedulerLock(fn) {
|
|
23
|
+
if (depth > 0) {
|
|
24
|
+
depth += 1;
|
|
25
|
+
try {
|
|
26
|
+
return await fn();
|
|
27
|
+
}
|
|
28
|
+
finally {
|
|
29
|
+
depth -= 1;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const lockPath = schedulerLockPath();
|
|
33
|
+
fs.mkdirSync(path.dirname(lockPath), { recursive: true });
|
|
34
|
+
const acquire = () => tryAcquireLockSync(lockPath, createLockPayload({ purpose: "scheduler" }));
|
|
35
|
+
let ownership = acquire();
|
|
36
|
+
if (!ownership) {
|
|
37
|
+
const probe = probeLock(lockPath);
|
|
38
|
+
if (probe.state === "absent" || (probe.state === "stale" && reclaimStaleLock(lockPath, probe))) {
|
|
39
|
+
ownership = acquire();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (!ownership) {
|
|
43
|
+
throw new TransientError("Another akm process is syncing the scheduler right now.", "SCHEDULER_LOCK_HELD");
|
|
44
|
+
}
|
|
45
|
+
depth = 1;
|
|
46
|
+
try {
|
|
47
|
+
return await fn();
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
depth = 0;
|
|
51
|
+
releaseLock(ownership);
|
|
52
|
+
}
|
|
53
|
+
}
|