akm-cli 0.9.1 → 0.9.2-alpha.2
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 +103 -28
- package/README.md +3 -1
- package/SECURITY.md +1 -1
- package/STABILITY.md +1 -1
- package/dist/akm +2 -2
- package/dist/akm-migrate +2 -2
- package/dist/assets/hints/cli-hints-full.md +14 -9
- package/dist/assets/improve-strategies/proactive-maintenance.json +1 -1
- package/dist/assets/improve-strategies/reflect-distill.json +1 -1
- package/dist/assets/models.json +35 -0
- package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +3 -4
- package/dist/assets/stash-skeleton/facts/conventions/organization.md +1 -3
- package/dist/assets/tasks/core/extract.yml +6 -5
- package/dist/assets/tasks/core/improve.yml +6 -5
- package/dist/assets/tasks/core/index-refresh.yml +6 -5
- package/dist/assets/tasks/core/sync.yml +6 -5
- package/dist/assets/tasks/core/version-check.yml +6 -5
- package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +6 -5
- package/dist/assets/tasks/improve/akm-improve-catchup.yml +6 -5
- package/dist/assets/tasks/improve/akm-improve-consolidate.yml +6 -5
- package/dist/assets/tasks/improve/akm-improve-frequent.yml +6 -5
- package/dist/assets/tasks/improve/akm-improve-nightly.yml +6 -5
- package/dist/cli/confirm.js +2 -2
- package/dist/cli/parse-args.js +3 -24
- package/dist/cli/retired-commands.js +1 -1
- package/dist/cli/shared.js +2 -2
- package/dist/cli.js +11 -9
- package/dist/commands/agent/agent-dispatch.js +55 -89
- package/dist/commands/agent/contribute-cli.js +12 -45
- package/dist/commands/command/builtin-action.js +32 -0
- package/dist/commands/command/command-cli.js +99 -0
- package/dist/commands/command/command-execution.js +308 -0
- package/dist/commands/command/execution-source-loader.js +176 -0
- package/dist/commands/command/portable-template.js +60 -0
- package/dist/commands/config-cli.js +10 -4
- package/dist/commands/env/env.js +4 -2
- package/dist/commands/feedback-cli.js +1 -1
- package/dist/commands/health/checks.js +241 -29
- package/dist/commands/health/html-report.js +0 -14
- package/dist/commands/health/report-view-model.js +0 -1
- package/dist/commands/health/surfaces.js +6 -7
- package/dist/commands/health/types.js +0 -2
- package/dist/commands/health.js +63 -18
- package/dist/commands/improve/collapse-detector.js +5 -6
- package/dist/commands/improve/consolidate.js +251 -214
- package/dist/commands/improve/distill/promote-memory.js +71 -34
- package/dist/commands/improve/distill/quality-gate.js +17 -5
- package/dist/commands/improve/distill.js +232 -155
- package/dist/commands/improve/eligibility.js +112 -79
- package/dist/commands/improve/execution.js +57 -0
- package/dist/commands/improve/extract-cli.js +5 -5
- package/dist/commands/improve/extract-prompt.js +64 -22
- package/dist/commands/improve/extract.js +608 -360
- package/dist/commands/improve/improve-strategies.js +43 -14
- package/dist/commands/improve/improve.js +249 -29
- package/dist/commands/improve/loop-stages.js +11 -17
- package/dist/commands/improve/memory/memory-contradiction-detect.js +90 -66
- package/dist/commands/improve/outcome-loop.js +22 -38
- package/dist/commands/improve/planner.js +134 -0
- package/dist/commands/improve/preparation.js +730 -409
- package/dist/commands/improve/reflect.js +386 -223
- package/dist/commands/improve/run-context.js +3 -4
- package/dist/commands/improve/salience.js +6 -58
- package/dist/commands/improve/session-asset.js +12 -12
- package/dist/commands/lint/index.js +101 -29
- package/dist/commands/migrate-cli.js +11 -69
- package/dist/commands/migration-tool.js +6 -9
- package/dist/commands/models-cli.js +27 -0
- package/dist/commands/proposal/drain.js +258 -186
- package/dist/commands/proposal/proposal-cli.js +32 -10
- package/dist/commands/proposal/proposal.js +2 -5
- package/dist/commands/proposal/propose.js +192 -172
- package/dist/commands/proposal/repository.js +54 -91
- package/dist/commands/proposal/validators/proposal-validators.js +9 -7
- package/dist/commands/read/curate.js +53 -22
- package/dist/commands/read/registry-search.js +25 -9
- package/dist/commands/read/remember-cli.js +14 -2
- package/dist/commands/read/search.js +10 -4
- package/dist/commands/read/show.js +139 -153
- package/dist/commands/registry-cli.js +16 -7
- package/dist/commands/remember.js +33 -18
- package/dist/commands/sources/add-cli.js +19 -178
- package/dist/commands/sources/bundle-cli.js +15 -3
- package/dist/commands/sources/dangerous-env-audit.js +135 -0
- package/dist/commands/sources/info.js +2 -1
- package/dist/commands/sources/installed-stashes.js +901 -177
- package/dist/commands/sources/schema-repair.js +174 -95
- package/dist/commands/sources/self-update.js +30 -74
- package/dist/commands/sources/source-add.js +3 -5
- package/dist/commands/sources/sources-cli.js +2 -15
- package/dist/commands/sources/update-transaction.js +220 -0
- package/dist/commands/tasks/tasks-cli.js +3 -3
- package/dist/commands/tasks/tasks.js +736 -317
- package/dist/commands/workflow-cli.js +2 -2
- package/dist/core/adapter/adapters/agent-skills-adapter.js +3 -0
- package/dist/core/adapter/adapters/akm-adapter.js +85 -35
- package/dist/core/adapter/adapters/akm-lint.js +54 -39
- package/dist/core/adapter/adapters/akm-metadata.js +45 -45
- package/dist/core/adapter/adapters/akm-task-adapter.js +32 -49
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +38 -23
- package/dist/core/adapter/adapters/dotenv-adapter.js +30 -1
- package/dist/core/adapter/adapters/generic-files-adapter.js +11 -0
- package/dist/core/adapter/adapters/index.js +0 -9
- package/dist/core/adapter/adapters/llm-wiki-adapter.js +4 -0
- package/dist/core/adapter/adapters/okf-adapter.js +4 -0
- package/dist/core/adapter/adapters/opencode-adapter.js +5 -8
- package/dist/core/adapter/adapters/tool-dir-shared.js +63 -6
- package/dist/core/adapter/adapters/website-snapshot-adapter.js +4 -0
- package/dist/core/adapter/execution-source.js +308 -0
- package/dist/core/adapter/recognize-match.js +36 -13
- package/dist/core/adapter/registry.js +0 -9
- package/dist/core/asset/stash-meta.js +94 -4
- package/dist/core/common.js +6 -11
- package/dist/core/config/config-io.js +3 -3
- package/dist/core/config/config-schema.js +18 -40
- package/dist/core/config/config-sources.js +11 -21
- package/dist/core/config/config-walker.js +31 -13
- package/dist/core/config/config.js +23 -26
- package/dist/core/config/schema/engines.js +8 -7
- package/dist/core/config/schema/improve-processes.js +29 -5
- package/dist/core/config/schema/index-config.js +0 -27
- package/dist/core/config/schema/primitives.js +1 -23
- package/dist/core/config/schema/sources-bundles.js +13 -16
- package/dist/core/errors.js +2 -0
- package/dist/core/events.js +68 -32
- package/dist/core/extra-params.js +1 -0
- package/dist/core/improve-result.js +315 -0
- package/dist/core/lesson-lint.js +0 -6
- package/dist/core/maintenance-barrier.js +4 -4
- package/dist/core/network-policy.js +152 -0
- package/dist/core/paths.js +1 -1
- package/dist/core/recognition-util.js +4 -4
- package/dist/core/registry-url.js +456 -0
- package/dist/core/state/migrations.js +161 -47
- package/dist/core/state-db.js +453 -80
- package/dist/core/system-error.js +32 -0
- package/dist/core/time.js +2 -12
- package/dist/core/write-source.js +0 -18
- package/dist/execution/directory-identity.js +52 -0
- package/dist/execution/executable-identity.js +107 -0
- package/dist/execution/guarded-source.js +398 -0
- package/dist/execution/json.js +95 -0
- package/dist/{commands/health/types-session-log.js → execution/limits.js} +2 -1
- package/dist/execution/record.js +55 -0
- package/dist/execution/resolved-request.js +730 -0
- package/dist/execution/source.js +320 -0
- package/dist/indexer/bundle-identity-guard.js +5 -4
- package/dist/indexer/db/graph-db.js +33 -0
- package/dist/indexer/graph/graph-boost.js +3 -4
- package/dist/indexer/graph/graph-extraction.js +562 -373
- package/dist/indexer/index-written-assets.js +78 -39
- package/dist/indexer/indexer.js +471 -432
- package/dist/indexer/installations.js +6 -0
- package/dist/indexer/lookup/adapter-concept-owner.js +283 -0
- package/dist/indexer/materialize-embeddings.js +155 -0
- package/dist/indexer/passes/memory-inference.js +227 -174
- package/dist/indexer/passes/metadata.js +263 -118
- package/dist/indexer/scan/doc-to-entry.js +7 -10
- package/dist/indexer/scan/drain-dir.js +51 -23
- package/dist/indexer/search/db-search.js +156 -50
- package/dist/indexer/search/fts-query.js +40 -40
- package/dist/indexer/search/ranking.js +36 -1
- package/dist/indexer/search/search-attribution.js +3 -1
- package/dist/indexer/search/search-fields.js +23 -14
- package/dist/indexer/search/search-hit-enrichers.js +1 -1
- package/dist/indexer/search/search-source.js +7 -16
- package/dist/indexer/search/semantic-status.js +10 -1
- package/dist/indexer/usage/show-usage.js +105 -0
- package/dist/indexer/usage/usage-events.js +7 -2
- package/dist/indexer/walk/matchers.js +40 -10
- package/dist/indexer/walk/path-resolver.js +5 -2
- package/dist/indexer/walk/walker.js +20 -2
- package/dist/integrations/agent/builder-shared.js +3 -6
- package/dist/integrations/agent/conversation-fallback.js +16 -0
- package/dist/integrations/agent/engine-resolution.js +87 -87
- package/dist/integrations/agent/execution-cascade.js +566 -0
- package/dist/integrations/agent/execution-definitions.js +211 -0
- package/dist/integrations/agent/execution-lowering.js +811 -0
- package/dist/integrations/agent/execution-preparation.js +67 -0
- package/dist/integrations/agent/index.js +0 -2
- package/dist/integrations/agent/inline-execution.js +74 -0
- package/dist/integrations/agent/model-map.js +515 -0
- package/dist/integrations/agent/persona-fallback.js +30 -0
- package/dist/integrations/agent/request-lowering.js +186 -0
- package/dist/integrations/agent/runner-dispatch.js +230 -37
- package/dist/integrations/agent/runner.js +12 -83
- package/dist/integrations/harnesses/aider/agent-builder.js +8 -0
- package/dist/integrations/harnesses/aider/index.js +0 -1
- package/dist/integrations/harnesses/amazonq/agent-builder.js +8 -0
- package/dist/integrations/harnesses/amazonq/index.js +0 -1
- package/dist/integrations/harnesses/claude/agent-builder.js +14 -1
- package/dist/integrations/harnesses/claude/index.js +1 -5
- package/dist/integrations/harnesses/claude/session-log.js +3 -33
- package/dist/integrations/harnesses/codex/agent-builder.js +8 -0
- package/dist/integrations/harnesses/codex/index.js +0 -1
- package/dist/integrations/harnesses/copilot/agent-builder.js +8 -0
- package/dist/integrations/harnesses/copilot/index.js +0 -1
- package/dist/integrations/harnesses/gemini/agent-builder.js +8 -0
- package/dist/integrations/harnesses/gemini/index.js +0 -1
- package/dist/integrations/harnesses/index.js +4 -44
- package/dist/integrations/harnesses/opencode/agent-builder.js +16 -9
- package/dist/integrations/harnesses/opencode/index.js +0 -2
- package/dist/integrations/harnesses/opencode/session-log.js +14 -204
- package/dist/integrations/harnesses/opencode-sdk/harness.js +12 -1
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +40 -42
- package/dist/integrations/harnesses/openhands/agent-builder.js +8 -0
- package/dist/integrations/harnesses/openhands/index.js +0 -1
- package/dist/integrations/harnesses/pi/agent-builder.js +8 -0
- package/dist/integrations/harnesses/pi/index.js +0 -1
- package/dist/integrations/harnesses/shared.js +0 -1
- package/dist/integrations/harnesses/types.js +1 -3
- package/dist/integrations/lockfile.js +82 -79
- package/dist/integrations/session-logs/index.js +6 -17
- package/dist/integrations/session-logs/provider-base.js +1 -29
- package/dist/llm/client.js +10 -5
- package/dist/llm/embedder.js +6 -7
- package/dist/llm/embedders/local.js +37 -88
- package/dist/llm/embedders/types.js +1 -1
- package/dist/llm/graph-extract.js +75 -50
- package/dist/llm/index-passes.js +43 -5
- package/dist/llm/memory-infer.js +8 -6
- package/dist/llm/metadata-enhance.js +5 -3
- package/dist/llm/structured-call.js +122 -25
- package/dist/output/format-exempt.js +1 -1
- package/dist/output/render-registry.js +0 -16
- package/dist/output/renderers.js +12 -7
- package/dist/output/shapes/curate.js +1 -0
- package/dist/output/shapes/helpers.js +10 -2
- package/dist/output/shapes/passthrough.js +2 -0
- package/dist/output/text/command-format.js +31 -33
- package/dist/output/text/health-format.js +1 -29
- package/dist/output/text/migrate.js +6 -56
- package/dist/output/text/proposal-format.js +16 -1
- package/dist/output/text/workflow-format.js +16 -0
- package/dist/registry/network.js +279 -0
- package/dist/registry/pinned-request-helper.js +247 -0
- package/dist/registry/pinned-transport.js +717 -0
- package/dist/registry/providers/skills-sh.js +18 -6
- package/dist/registry/providers/static-index.js +20 -7
- package/dist/registry/resolve.js +53 -28
- package/dist/scripts/akm-migrate-node.js +19334 -52269
- package/dist/scripts/akm-migrate.js +19270 -51612
- package/dist/setup/registry-stash-loader.js +64 -20
- package/dist/setup/semantic-assets.js +9 -34
- package/dist/setup/setup.js +12 -30
- package/dist/setup/source-identity.js +17 -0
- package/dist/setup/steps/sources.js +36 -15
- package/dist/setup/steps/tasks.js +39 -11
- package/dist/sources/providers/git-provider.js +3 -3
- package/dist/sources/providers/npm.js +2 -2
- package/dist/sources/providers/provider-utils.js +4 -3
- package/dist/sources/providers/website.js +11 -7
- package/dist/sources/snapshot-fetchers/host-guard.js +9 -136
- package/dist/sources/snapshot-fetchers/website-ingest.js +25 -109
- package/dist/sources/website-url.js +73 -0
- package/dist/storage/engines/sqlite-migrations.js +81 -26
- package/dist/storage/managed-db.js +27 -24
- package/dist/storage/repositories/events-repository.js +3 -0
- package/dist/storage/repositories/index-connection.js +42 -10
- package/dist/storage/repositories/index-entries-repository.js +203 -229
- package/dist/storage/repositories/index-entry-mapper.js +8 -12
- package/dist/storage/repositories/index-entry-schema.js +255 -0
- package/dist/storage/repositories/index-fts-repository.js +64 -71
- package/dist/storage/repositories/index-llm-cache-repository.js +8 -13
- package/dist/storage/repositories/index-meta-repository.js +0 -11
- package/dist/storage/repositories/index-schema.js +74 -350
- package/dist/storage/repositories/index-utility-repository.js +12 -17
- package/dist/storage/repositories/index-vec-repository.js +56 -7
- package/dist/storage/repositories/proposals-repository.js +4 -127
- package/dist/storage/repositories/registry-cache.js +2 -1
- package/dist/storage/repositories/task-history-repository.js +20 -40
- package/dist/storage/repositories/workflow-runs-repository.js +228 -129
- package/dist/storage/sqlite-read-snapshot.js +148 -0
- package/dist/tasks/backends/cron.js +170 -42
- package/dist/tasks/backends/index.js +1 -1
- package/dist/tasks/backends/launchd.js +787 -202
- package/dist/tasks/backends/schtasks.js +282 -83
- package/dist/tasks/embedded.js +7 -7
- package/dist/tasks/frozen-script.js +50 -0
- package/dist/tasks/resolve-akm-bin.js +5 -1
- package/dist/tasks/runner.js +239 -251
- package/dist/tasks/runtime-v3.js +281 -0
- package/dist/tasks/scheduler-binding.js +272 -0
- package/dist/tasks/scheduler-invocation.js +57 -43
- package/dist/tasks/scheduler-sync.js +654 -0
- package/dist/tasks/source-v3.js +752 -0
- package/dist/tasks/standalone-script-entry.js +5 -0
- package/dist/tasks/task-id.js +29 -0
- package/dist/workflows/authoring/authoring.js +15 -32
- package/dist/workflows/exec/dispatch-redaction.js +14 -8
- package/dist/workflows/exec/exec-unit.js +7 -28
- package/dist/workflows/exec/frozen-judge.js +57 -89
- package/dist/workflows/exec/lowering-notices.js +23 -0
- package/dist/workflows/exec/native-executor.js +301 -458
- package/dist/workflows/exec/param-secrets.js +4 -3
- package/dist/workflows/exec/run-workflow.js +26 -32
- package/dist/workflows/exec/step-work.js +105 -109
- package/dist/workflows/exec/unit-dispatch.js +103 -27
- package/dist/workflows/exec/unit-writer.js +3 -3
- package/dist/workflows/exec/worktree.js +2 -2
- package/dist/workflows/ir/compile.js +86 -72
- package/dist/workflows/ir/environment-v4.js +328 -0
- package/dist/workflows/ir/freeze-v4.js +122 -0
- package/dist/workflows/ir/plan-hash.js +13 -7
- package/dist/workflows/ir/schema-v4.js +525 -0
- package/dist/workflows/ir/schema.js +25 -284
- package/dist/workflows/ir/source-freeze-v4.js +506 -0
- package/dist/workflows/parser.js +27 -24
- package/dist/workflows/program/schema.js +1 -2
- package/dist/workflows/renderer.js +42 -29
- package/dist/workflows/resource-limits.js +4 -5
- package/dist/workflows/runtime/agent-identity.js +11 -13
- package/dist/workflows/runtime/plan-classifier.js +8 -8
- package/dist/workflows/runtime/runs.js +27 -43
- package/dist/workflows/runtime/workflow-asset-loader.js +45 -205
- package/dist/workflows/source-files.js +373 -0
- package/dist/workflows/source-ir/compile.js +196 -0
- package/dist/workflows/source-ir/github-yaml.js +577 -0
- package/dist/workflows/source-ir/ordering.js +38 -0
- package/dist/workflows/source-ir/program.js +50 -0
- package/dist/workflows/source-ir/result.js +26 -0
- package/dist/workflows/source-ir/schema.js +772 -0
- package/dist/workflows/source-ir/semantics.js +242 -0
- package/dist/workflows/source-ir/uses.js +14 -0
- package/docs/README.md +2 -0
- package/docs/migration/README.md +3 -1
- package/docs/migration/release-notes/0.9.2.md +55 -0
- package/docs/migration/release-notes/README.md +5 -0
- package/docs/migration/v0.8-to-v0.9.md +76 -1077
- package/docs/migration/v0.9.0-troubleshooting.md +104 -516
- package/docs/migration/v0.9.1-to-v0.9.2.md +150 -0
- package/docs/reference/README.md +1 -0
- package/docs/reference/cli.md +230 -98
- package/docs/reference/configuration.md +159 -36
- package/docs/reference/data-and-telemetry.md +19 -1
- package/docs/reference/supported-formats.md +23 -3
- package/docs/reference/tasks.md +182 -0
- package/docs/reference/workflow-schema.md +91 -40
- package/docs/reference/workflows.md +33 -6
- package/package.json +10 -6
- package/schemas/akm-config.json +372 -224
- package/schemas/akm-task.json +324 -80
- package/schemas/akm-workflow.json +6 -9
- package/dist/core/migration-operation.js +0 -75
- package/dist/integrations/agent/model-aliases.js +0 -74
- package/dist/tasks/parser.js +0 -380
- package/dist/tasks/schema.js +0 -123
- package/dist/tasks/validator.js +0 -80
- package/dist/workflows/ir/freeze.js +0 -320
- package/dist/workflows/runtime/document-cache.js +0 -13
|
@@ -37,10 +37,12 @@ import { ConfigError } from "../../core/errors.js";
|
|
|
37
37
|
import { getTaskLogDir } from "../../core/paths.js";
|
|
38
38
|
import { resolveAkmInvocation } from "../resolve-akm-bin.js";
|
|
39
39
|
import { parseSchedule, translateToSchtasks } from "../schedule.js";
|
|
40
|
-
import {
|
|
40
|
+
import { assertSchedulerExecutionEvidenceDigest, assertSchedulerExpectationIdentity, assertSchedulerMutationArtifact, assertSchedulerNativeArtifactCardinality, assertSchedulerNativeArtifactOwner, assertSchedulerRemovalArtifact, assertSchedulerRollbackArtifactCardinality, schedulerBindingNativeId, schedulerLogicalBindingId, schedulerLogicalBindingOwner, schedulerNativeArtifactKey, schedulerNativeBindingId, } from "../scheduler-binding.js";
|
|
41
|
+
import { buildScheduledBindingInvocation, parseScheduledBindingArgv, resolveScheduledTaskContext, schedulerContextDescriptor, schedulerContextPath, } from "../scheduler-invocation.js";
|
|
41
42
|
import { escapeXml, nodeExec, nodeFs, normalizeXmlForUtf16File, runOrThrow, } from "./exec-utils.js";
|
|
42
43
|
export const DEFAULT_FOLDER_PREFIX = "\\akm\\";
|
|
43
44
|
const SIGNATURE_PREFIX = "akm:v1:";
|
|
45
|
+
const SCHTASKS_SNAPSHOT = Symbol("akm-schtasks-binding-snapshot");
|
|
44
46
|
export function SCHTASKS_BACKEND(options = {}) {
|
|
45
47
|
const exec = options.exec ?? defaultSchtasksExec();
|
|
46
48
|
const fsLike = options.fs ?? defaultSchtasksFs();
|
|
@@ -50,10 +52,13 @@ export function SCHTASKS_BACKEND(options = {}) {
|
|
|
50
52
|
const scheduledContext = options.scheduledContext ?? resolveScheduledTaskContext();
|
|
51
53
|
const defaultContextPath = schedulerContextPath(schedulerContextDescriptor(scheduledContext, process.env.PATH ?? ""));
|
|
52
54
|
const userSid = options.userSid ?? resolveCurrentUserSid(exec);
|
|
53
|
-
const taskName = (
|
|
55
|
+
const taskName = (nativeId) => `${folder}${nativeId}`;
|
|
54
56
|
return {
|
|
55
57
|
name: "schtasks",
|
|
56
|
-
install(task, opts) {
|
|
58
|
+
install(task, opts, expected) {
|
|
59
|
+
if (expected)
|
|
60
|
+
assertSchedulerExpectationIdentity(expected, task);
|
|
61
|
+
const nativeId = schedulerBindingNativeId(task);
|
|
57
62
|
const xml = normalizeXmlForUtf16File(buildSchtasksXml(task, akmArgv, logDir, {
|
|
58
63
|
folderPrefix: folder,
|
|
59
64
|
contextPath: opts?.contextPath ?? defaultContextPath,
|
|
@@ -61,29 +66,55 @@ export function SCHTASKS_BACKEND(options = {}) {
|
|
|
61
66
|
binding: [...(opts?.binding ?? akmArgv)],
|
|
62
67
|
...(opts?.target !== undefined ? { target: opts.target } : {}),
|
|
63
68
|
}));
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
69
|
+
const initialArtifact = expected
|
|
70
|
+
? assertSchedulerNativeArtifactCardinality(inspectSchtasksState(exec, folder, taskName).artifacts, nativeId, expected.state === "absent" ? 0 : 1)
|
|
71
|
+
: undefined;
|
|
72
|
+
const existingNativeId = expected ? initialArtifact?.nativeId : nativeId;
|
|
73
|
+
const query = existingNativeId
|
|
74
|
+
? runOrThrow(exec, ["schtasks", "/Query", "/TN", taskName(existingNativeId), "/XML"], {
|
|
75
|
+
isOk: (r) => r.status === 0 || isMissingTaskResult(r),
|
|
76
|
+
message: (r) => `schtasks /Query failed (exit ${r.status}): ${r.stderr || r.stdout || "no output"}.`,
|
|
77
|
+
})
|
|
78
|
+
: { status: 1, stdout: "", stderr: "missing" };
|
|
79
|
+
if (expected) {
|
|
80
|
+
assertSchedulerMutationArtifact(query.status === 0 && existingNativeId ? schtasksArtifact(existingNativeId, query.stdout) : undefined, expected);
|
|
81
|
+
}
|
|
68
82
|
let previous;
|
|
69
83
|
if (query.status === 0) {
|
|
84
|
+
if (!expected) {
|
|
85
|
+
assertSchedulerNativeArtifactOwner(nativeId, task, extractSchtasksInvocation(query.stdout)?.invocation);
|
|
86
|
+
}
|
|
70
87
|
const enabled = taskXmlEnabled(query.stdout);
|
|
71
88
|
if (enabled === undefined) {
|
|
72
|
-
throw new ConfigError(`schtasks /Query returned an unreadable definition for "${taskName(
|
|
89
|
+
throw new ConfigError(`schtasks /Query returned an unreadable definition for "${taskName(nativeId)}"; refusing to replace it.`, "INVALID_CONFIG_FILE");
|
|
73
90
|
}
|
|
74
91
|
previous = { xml: normalizeXmlForUtf16File(query.stdout), enabled };
|
|
75
92
|
}
|
|
76
93
|
fsLike.ensureDir(logDir);
|
|
77
|
-
const tmpFile = path.join(fsLike.tmpdir(), `akm-task-${
|
|
94
|
+
const tmpFile = path.join(fsLike.tmpdir(), `akm-task-${nativeId}-${Date.now()}.xml`);
|
|
78
95
|
fsLike.writeFile(tmpFile, xml);
|
|
79
96
|
try {
|
|
97
|
+
if (expected) {
|
|
98
|
+
const finalArtifact = assertSchedulerNativeArtifactCardinality(inspectSchtasksState(exec, folder, taskName).artifacts, nativeId, expected.state === "absent" ? 0 : 1);
|
|
99
|
+
const finalNativeId = finalArtifact?.nativeId;
|
|
100
|
+
const current = finalNativeId
|
|
101
|
+
? runOrThrow(exec, ["schtasks", "/Query", "/TN", taskName(finalNativeId), "/XML"], {
|
|
102
|
+
isOk: (result) => result.status === 0 || isMissingTaskResult(result),
|
|
103
|
+
message: (result) => `schtasks /Query failed during final install CAS (exit ${result.status}): ${result.stderr || result.stdout || "no output"}.`,
|
|
104
|
+
})
|
|
105
|
+
: undefined;
|
|
106
|
+
assertSchedulerMutationArtifact(current?.status === 0 && finalNativeId ? schtasksArtifact(finalNativeId, current.stdout) : undefined, expected);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
assertSchtasksArtifactUnchanged(exec, taskName(nativeId), query, nativeId, task);
|
|
110
|
+
}
|
|
80
111
|
try {
|
|
81
112
|
// /F forces overwrite if a task with the same name exists.
|
|
82
|
-
runOrThrow(exec, ["schtasks", "/Create", "/TN", taskName(
|
|
113
|
+
runOrThrow(exec, ["schtasks", "/Create", "/TN", taskName(nativeId), "/XML", tmpFile, "/F"], {
|
|
83
114
|
message: (r) => `schtasks /Create failed (exit ${r.status}): ${r.stderr || r.stdout || "no output"}.`,
|
|
84
115
|
});
|
|
85
116
|
if (!task.enabled) {
|
|
86
|
-
runOrThrow(exec, ["schtasks", "/Change", "/TN", taskName(
|
|
117
|
+
runOrThrow(exec, ["schtasks", "/Change", "/TN", taskName(nativeId), "/DISABLE"], {
|
|
87
118
|
message: (r) => `schtasks /Change /DISABLE failed: ${r.stderr || r.stdout || "no output"}.`,
|
|
88
119
|
});
|
|
89
120
|
}
|
|
@@ -92,7 +123,7 @@ export function SCHTASKS_BACKEND(options = {}) {
|
|
|
92
123
|
const rollbackErrors = [];
|
|
93
124
|
if (previous === undefined) {
|
|
94
125
|
try {
|
|
95
|
-
const remove = exec.run(["schtasks", "/Delete", "/TN", taskName(
|
|
126
|
+
const remove = exec.run(["schtasks", "/Delete", "/TN", taskName(nativeId), "/F"]);
|
|
96
127
|
if (remove.status !== 0 && !isMissingTaskResult(remove)) {
|
|
97
128
|
rollbackErrors.push(new ConfigError(`schtasks /Delete during rollback failed: ${remove.stderr || remove.stdout || "no output"}.`, "INVALID_CONFIG_FILE"));
|
|
98
129
|
}
|
|
@@ -104,11 +135,11 @@ export function SCHTASKS_BACKEND(options = {}) {
|
|
|
104
135
|
else {
|
|
105
136
|
try {
|
|
106
137
|
fsLike.writeFile(tmpFile, previous.xml);
|
|
107
|
-
runOrThrow(exec, ["schtasks", "/Create", "/TN", taskName(
|
|
138
|
+
runOrThrow(exec, ["schtasks", "/Create", "/TN", taskName(nativeId), "/XML", tmpFile, "/F"], {
|
|
108
139
|
message: (r) => `schtasks /Create during rollback failed: ${r.stderr || r.stdout || "no output"}.`,
|
|
109
140
|
});
|
|
110
141
|
const stateFlag = previous.enabled ? "/ENABLE" : "/DISABLE";
|
|
111
|
-
runOrThrow(exec, ["schtasks", "/Change", "/TN", taskName(
|
|
142
|
+
runOrThrow(exec, ["schtasks", "/Change", "/TN", taskName(nativeId), stateFlag], {
|
|
112
143
|
message: (r) => `schtasks /Change ${stateFlag} during rollback failed: ${r.stderr || r.stdout || "no output"}.`,
|
|
113
144
|
});
|
|
114
145
|
}
|
|
@@ -127,78 +158,48 @@ export function SCHTASKS_BACKEND(options = {}) {
|
|
|
127
158
|
fsLike.removeFile(tmpFile);
|
|
128
159
|
}
|
|
129
160
|
},
|
|
130
|
-
uninstall(
|
|
131
|
-
|
|
161
|
+
uninstall(nativeId, expected) {
|
|
162
|
+
if (expected) {
|
|
163
|
+
assertSchedulerExpectationIdentity({ ...expected, state: "present" });
|
|
164
|
+
const artifact = assertSchedulerNativeArtifactCardinality(inspectSchtasksState(exec, folder, taskName).artifacts, nativeId, 1);
|
|
165
|
+
const currentNativeId = artifact?.nativeId ?? nativeId;
|
|
166
|
+
const current = runOrThrow(exec, ["schtasks", "/Query", "/TN", taskName(currentNativeId), "/XML"], {
|
|
167
|
+
isOk: (result) => result.status === 0 || isMissingTaskResult(result),
|
|
168
|
+
message: (result) => `schtasks /Query failed during final removal ownership check (exit ${result.status}): ${result.stderr || result.stdout || "no output"}.`,
|
|
169
|
+
});
|
|
170
|
+
if (current.status !== 0) {
|
|
171
|
+
throw new ConfigError(`Task Scheduler artifact ${JSON.stringify(nativeId)} disappeared after coherent inspection.`, "INVALID_CONFIG_FILE");
|
|
172
|
+
}
|
|
173
|
+
assertSchedulerRemovalArtifact(currentNativeId, expected, extractSchtasksInvocation(current.stdout)?.invocation, installedSignature(current.stdout));
|
|
174
|
+
}
|
|
175
|
+
runOrThrow(exec, ["schtasks", "/Delete", "/TN", taskName(nativeId), "/F"], {
|
|
132
176
|
isOk: (r) => r.status === 0 || /cannot find/i.test(r.stderr ?? ""),
|
|
133
177
|
message: (r) => `schtasks /Delete failed: ${r.stderr || r.stdout || "no output"}.`,
|
|
134
178
|
});
|
|
135
179
|
},
|
|
136
|
-
setEnabled(
|
|
180
|
+
setEnabled(nativeId, enabled) {
|
|
137
181
|
const flag = enabled ? "/ENABLE" : "/DISABLE";
|
|
138
|
-
runOrThrow(exec, ["schtasks", "/Change", "/TN", taskName(
|
|
182
|
+
runOrThrow(exec, ["schtasks", "/Change", "/TN", taskName(nativeId), flag], {
|
|
139
183
|
message: (r) => `schtasks /Change ${flag} failed: ${r.stderr || r.stdout || "no output"}.`,
|
|
140
184
|
});
|
|
141
185
|
},
|
|
142
186
|
list() {
|
|
143
|
-
|
|
144
|
-
message: (res) => `schtasks /Query failed (exit ${res.status}): ${res.stderr || res.stdout || "no output"}.`,
|
|
145
|
-
});
|
|
146
|
-
const ids = [];
|
|
147
|
-
for (const line of (r.stdout ?? "").split(/\r?\n/)) {
|
|
148
|
-
const m = line.match(/^"([^"]+)",/);
|
|
149
|
-
if (!m)
|
|
150
|
-
continue;
|
|
151
|
-
const name = m[1];
|
|
152
|
-
if (name.startsWith(folder)) {
|
|
153
|
-
ids.push(name.slice(folder.length));
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
const refs = [];
|
|
157
|
-
for (const id of ids) {
|
|
158
|
-
const query = runOrThrow(exec, ["schtasks", "/Query", "/TN", taskName(id), "/XML"], {
|
|
159
|
-
message: (r) => `schtasks /Query /XML for "${taskName(id)}" failed (exit ${r.status}): ${r.stderr || r.stdout || "no output"}.`,
|
|
160
|
-
});
|
|
161
|
-
const signature = installedSignature(query.stdout);
|
|
162
|
-
const installed = extractSchtasksInvocation(query.stdout);
|
|
163
|
-
// An invocation that no longer parses (e.g. a pre-0.9 `tasks run`
|
|
164
|
-
// entry surviving the scheduler-ABI respelling) is an orphan of its
|
|
165
|
-
// marker id, not a hard failure: omit it so `akmTasksSync` treats the
|
|
166
|
-
// id as "not present" and reinstalls it from the current task file.
|
|
167
|
-
if (!installed)
|
|
168
|
-
continue;
|
|
169
|
-
refs.push({
|
|
170
|
-
id,
|
|
171
|
-
...(signature !== undefined ? { signature } : {}),
|
|
172
|
-
...(installed.target !== undefined ? { target: installed.target } : {}),
|
|
173
|
-
binding: installed.binding,
|
|
174
|
-
contextPath: installed.contextPath,
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
return refs;
|
|
187
|
+
return [...inspectSchtasksState(exec, folder, taskName).installed];
|
|
178
188
|
},
|
|
179
189
|
listForRebind() {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
const signature = installedSignature(query.stdout);
|
|
194
|
-
const installed = extractSchtasksInvocation(query.stdout);
|
|
195
|
-
refs.push({
|
|
196
|
-
id,
|
|
197
|
-
...(signature !== undefined ? { signature } : {}),
|
|
198
|
-
...(installed?.target !== undefined ? { target: installed.target } : {}),
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
return refs;
|
|
190
|
+
return listSchtasksForRebind(exec, folder, taskName);
|
|
191
|
+
},
|
|
192
|
+
listNativeArtifacts() {
|
|
193
|
+
return [...inspectSchtasksState(exec, folder, taskName).artifacts];
|
|
194
|
+
},
|
|
195
|
+
inspectBindings() {
|
|
196
|
+
return inspectSchtasksState(exec, folder, taskName);
|
|
197
|
+
},
|
|
198
|
+
snapshotBindings(ids) {
|
|
199
|
+
return snapshotSchtasksBindings(ids, exec, folder, taskName);
|
|
200
|
+
},
|
|
201
|
+
restoreBindings(snapshot, expectedCurrent) {
|
|
202
|
+
restoreSchtasksBindings(snapshot, { exec, fsLike, folder, taskName }, expectedCurrent);
|
|
202
203
|
},
|
|
203
204
|
expectedSignature(task, opts) {
|
|
204
205
|
const signature = taskXmlSignature(buildSchtasksXml(task, akmArgv, logDir, {
|
|
@@ -214,6 +215,181 @@ export function SCHTASKS_BACKEND(options = {}) {
|
|
|
214
215
|
},
|
|
215
216
|
};
|
|
216
217
|
}
|
|
218
|
+
function listSchtasksForRebind(exec, folder, taskName) {
|
|
219
|
+
const result = runOrThrow(exec, ["schtasks", "/Query", "/FO", "CSV", "/NH"], {
|
|
220
|
+
message: (query) => `schtasks /Query failed (exit ${query.status}): ${query.stderr || query.stdout || "no output"}.`,
|
|
221
|
+
});
|
|
222
|
+
const refs = [];
|
|
223
|
+
for (const line of (result.stdout ?? "").split(/\r?\n/)) {
|
|
224
|
+
const name = line.match(/^"([^"]+)",/)?.[1];
|
|
225
|
+
if (!name?.startsWith(folder))
|
|
226
|
+
continue;
|
|
227
|
+
const id = name.slice(folder.length);
|
|
228
|
+
const query = runOrThrow(exec, ["schtasks", "/Query", "/TN", taskName(id), "/XML"], {
|
|
229
|
+
message: (current) => `schtasks /Query /XML for "${taskName(id)}" failed (exit ${current.status}): ${current.stderr || current.stdout || "no output"}.`,
|
|
230
|
+
});
|
|
231
|
+
const signature = installedSignature(query.stdout);
|
|
232
|
+
const installed = extractSchtasksInvocation(query.stdout);
|
|
233
|
+
const ref = {
|
|
234
|
+
id: installed ? schedulerLogicalBindingId(id, installed.invocation) : id,
|
|
235
|
+
...(signature !== undefined ? { signature } : {}),
|
|
236
|
+
...(installed?.target !== undefined ? { target: installed.target } : {}),
|
|
237
|
+
};
|
|
238
|
+
Object.defineProperty(ref, "nativeId", { value: id });
|
|
239
|
+
if (installed)
|
|
240
|
+
Object.defineProperty(ref, "invocation", { value: Object.freeze([...installed.invocation]) });
|
|
241
|
+
refs.push(ref);
|
|
242
|
+
}
|
|
243
|
+
return refs;
|
|
244
|
+
}
|
|
245
|
+
function inspectSchtasksState(exec, folder, taskName) {
|
|
246
|
+
const listing = runOrThrow(exec, ["schtasks", "/Query", "/FO", "CSV", "/NH"], {
|
|
247
|
+
message: (result) => `schtasks /Query failed (exit ${result.status}): ${result.stderr || result.stdout || "no output"}.`,
|
|
248
|
+
});
|
|
249
|
+
const installed = [];
|
|
250
|
+
const artifacts = [];
|
|
251
|
+
for (const nativeId of schtasksNativeIds(listing.stdout, folder)) {
|
|
252
|
+
const query = runOrThrow(exec, ["schtasks", "/Query", "/TN", taskName(nativeId), "/XML"], {
|
|
253
|
+
message: (result) => `schtasks /Query /XML for "${taskName(nativeId)}" failed (exit ${result.status}): ${result.stderr || result.stdout || "no output"}.`,
|
|
254
|
+
});
|
|
255
|
+
const artifact = schtasksArtifact(nativeId, query.stdout);
|
|
256
|
+
artifacts.push(artifact);
|
|
257
|
+
const parsed = extractSchtasksInvocation(query.stdout);
|
|
258
|
+
if (!parsed)
|
|
259
|
+
continue;
|
|
260
|
+
const ref = {
|
|
261
|
+
id: schedulerLogicalBindingId(nativeId, parsed.invocation),
|
|
262
|
+
...(artifact.fingerprint !== undefined ? { signature: artifact.fingerprint } : {}),
|
|
263
|
+
...(parsed.target !== undefined ? { target: parsed.target } : {}),
|
|
264
|
+
binding: parsed.binding,
|
|
265
|
+
contextPath: parsed.contextPath,
|
|
266
|
+
};
|
|
267
|
+
Object.defineProperty(ref, "nativeId", { value: nativeId });
|
|
268
|
+
Object.defineProperty(ref, "invocation", { value: Object.freeze([...parsed.invocation]) });
|
|
269
|
+
installed.push(ref);
|
|
270
|
+
}
|
|
271
|
+
return Object.freeze({ installed: Object.freeze(installed), artifacts: Object.freeze(artifacts) });
|
|
272
|
+
}
|
|
273
|
+
function schtasksNativeIds(stdout, folder) {
|
|
274
|
+
const ids = [];
|
|
275
|
+
for (const line of (stdout ?? "").split(/\r?\n/)) {
|
|
276
|
+
const match = line.match(/^"([^"]+)",/);
|
|
277
|
+
const name = match?.[1];
|
|
278
|
+
if (name?.startsWith(folder))
|
|
279
|
+
ids.push(name.slice(folder.length));
|
|
280
|
+
}
|
|
281
|
+
return ids;
|
|
282
|
+
}
|
|
283
|
+
function schtasksArtifact(nativeId, xml) {
|
|
284
|
+
const parsed = extractSchtasksInvocation(xml);
|
|
285
|
+
const owner = parsed ? schedulerLogicalBindingOwner(nativeId, parsed.invocation) : undefined;
|
|
286
|
+
const artifact = parsed
|
|
287
|
+
? {
|
|
288
|
+
nativeId,
|
|
289
|
+
...(owner !== undefined ? { bindingId: owner } : {}),
|
|
290
|
+
invocation: Object.freeze([...parsed.invocation]),
|
|
291
|
+
}
|
|
292
|
+
: { nativeId };
|
|
293
|
+
const fingerprint = installedSignature(xml);
|
|
294
|
+
if (fingerprint !== undefined)
|
|
295
|
+
Object.defineProperty(artifact, "fingerprint", { value: fingerprint });
|
|
296
|
+
return artifact;
|
|
297
|
+
}
|
|
298
|
+
function snapshotSchtasksBindings(ids, exec, folder, taskName) {
|
|
299
|
+
const listing = runOrThrow(exec, ["schtasks", "/Query", "/FO", "CSV", "/NH"], {
|
|
300
|
+
message: (result) => `schtasks /Query failed while snapshotting bindings (exit ${result.status}): ${result.stderr || result.stdout || "no output"}.`,
|
|
301
|
+
});
|
|
302
|
+
const requestedKeys = new Set(ids.map(schedulerNativeArtifactKey));
|
|
303
|
+
const enumeratedIds = new Set(ids);
|
|
304
|
+
for (const nativeId of schtasksNativeIds(listing.stdout, folder)) {
|
|
305
|
+
if (requestedKeys.has(schedulerNativeArtifactKey(nativeId)))
|
|
306
|
+
enumeratedIds.add(nativeId);
|
|
307
|
+
}
|
|
308
|
+
const entries = [...enumeratedIds].map((id) => {
|
|
309
|
+
const query = runOrThrow(exec, ["schtasks", "/Query", "/TN", taskName(id), "/XML"], {
|
|
310
|
+
isOk: (result) => result.status === 0 || isMissingTaskResult(result),
|
|
311
|
+
message: (result) => `schtasks /Query failed while snapshotting "${taskName(id)}": ${result.stderr || result.stdout || "no output"}.`,
|
|
312
|
+
});
|
|
313
|
+
if (query.status !== 0)
|
|
314
|
+
return Object.freeze({ id });
|
|
315
|
+
const enabled = taskXmlEnabled(query.stdout);
|
|
316
|
+
if (enabled === undefined) {
|
|
317
|
+
throw new ConfigError(`schtasks /Query returned an unreadable definition while snapshotting "${taskName(id)}".`, "INVALID_CONFIG_FILE");
|
|
318
|
+
}
|
|
319
|
+
return Object.freeze({ id, xml: normalizeXmlForUtf16File(query.stdout), enabled });
|
|
320
|
+
});
|
|
321
|
+
const artifacts = entries.flatMap((entry) => entry.xml === undefined ? [] : [schtasksArtifact(entry.id, entry.xml)]);
|
|
322
|
+
return Object.freeze({
|
|
323
|
+
kind: SCHTASKS_SNAPSHOT,
|
|
324
|
+
nativeIds: Object.freeze([...ids]),
|
|
325
|
+
artifacts: Object.freeze(artifacts),
|
|
326
|
+
entries: Object.freeze(entries),
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
function restoreSchtasksBindings(snapshot, context, expectedCurrent) {
|
|
330
|
+
if (!isSchtasksBindingSnapshot(snapshot)) {
|
|
331
|
+
throw new ConfigError("Invalid Task Scheduler snapshot.", "INVALID_CONFIG_FILE");
|
|
332
|
+
}
|
|
333
|
+
const errors = [];
|
|
334
|
+
let rollbackInventory;
|
|
335
|
+
if (expectedCurrent) {
|
|
336
|
+
try {
|
|
337
|
+
rollbackInventory = inspectSchtasksState(context.exec, context.folder, context.taskName);
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
errors.push(error);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
for (const entry of snapshot.entries) {
|
|
344
|
+
try {
|
|
345
|
+
if (expectedCurrent) {
|
|
346
|
+
if (!rollbackInventory)
|
|
347
|
+
continue;
|
|
348
|
+
const expected = expectedCurrent.find((candidate) => candidate.nativeId === entry.id);
|
|
349
|
+
if (!expected) {
|
|
350
|
+
throw new ConfigError(`Missing Task Scheduler rollback expectation for ${JSON.stringify(entry.id)}.`, "INVALID_CONFIG_FILE");
|
|
351
|
+
}
|
|
352
|
+
assertSchedulerRollbackArtifactCardinality(rollbackInventory.artifacts, expected);
|
|
353
|
+
}
|
|
354
|
+
restoreSchtasksEntry(entry, context);
|
|
355
|
+
}
|
|
356
|
+
catch (error) {
|
|
357
|
+
errors.push(error);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if (errors.length > 0) {
|
|
361
|
+
throw new AggregateError(errors, `Failed to completely restore ${errors.length} Task Scheduler operation(s).`);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
function restoreSchtasksEntry(entry, context) {
|
|
365
|
+
if (entry.xml === undefined) {
|
|
366
|
+
runOrThrow(context.exec, ["schtasks", "/Delete", "/TN", context.taskName(entry.id), "/F"], {
|
|
367
|
+
isOk: (result) => result.status === 0 || isMissingTaskResult(result),
|
|
368
|
+
message: (result) => `schtasks /Delete failed while restoring "${context.taskName(entry.id)}": ${result.stderr || result.stdout || "no output"}.`,
|
|
369
|
+
});
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
const tmpFile = path.join(context.fsLike.tmpdir(), `akm-task-restore-${schedulerNativeBindingId(entry.id)}-${Date.now()}.xml`);
|
|
373
|
+
context.fsLike.writeFile(tmpFile, entry.xml);
|
|
374
|
+
try {
|
|
375
|
+
runOrThrow(context.exec, ["schtasks", "/Create", "/TN", context.taskName(entry.id), "/XML", tmpFile, "/F"], {
|
|
376
|
+
message: (result) => `schtasks /Create failed while restoring "${context.taskName(entry.id)}": ${result.stderr || result.stdout || "no output"}.`,
|
|
377
|
+
});
|
|
378
|
+
const stateFlag = entry.enabled === false ? "/DISABLE" : "/ENABLE";
|
|
379
|
+
runOrThrow(context.exec, ["schtasks", "/Change", "/TN", context.taskName(entry.id), stateFlag], {
|
|
380
|
+
message: (result) => `schtasks /Change ${stateFlag} failed while restoring "${context.taskName(entry.id)}": ${result.stderr || result.stdout || "no output"}.`,
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
finally {
|
|
384
|
+
context.fsLike.removeFile(tmpFile);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
function isSchtasksBindingSnapshot(value) {
|
|
388
|
+
return (typeof value === "object" &&
|
|
389
|
+
value !== null &&
|
|
390
|
+
value.kind === SCHTASKS_SNAPSHOT &&
|
|
391
|
+
Array.isArray(value.entries));
|
|
392
|
+
}
|
|
217
393
|
/**
|
|
218
394
|
* Recover the bundle name embedded as a `--bundle <bundle>` pair in the
|
|
219
395
|
* PowerShell `<Arguments>` of an installed Task Scheduler definition. Returns
|
|
@@ -230,7 +406,7 @@ export function extractSchtasksInvocation(xml) {
|
|
|
230
406
|
const invocationStart = findPowerShellInvocationOperator(commandLine);
|
|
231
407
|
if (invocationStart === undefined)
|
|
232
408
|
return undefined;
|
|
233
|
-
return
|
|
409
|
+
return parseScheduledBindingArgv(parsePowerShellSingleQuotedArgs(commandLine, invocationStart + 1));
|
|
234
410
|
}
|
|
235
411
|
function findPowerShellInvocationOperator(script) {
|
|
236
412
|
let inSingleQuote = false;
|
|
@@ -285,13 +461,32 @@ function parsePowerShellSingleQuotedArgs(script, start) {
|
|
|
285
461
|
}
|
|
286
462
|
return argv;
|
|
287
463
|
}
|
|
464
|
+
/** Close the read/prepare-to-/Create ownership race at the native boundary. */
|
|
465
|
+
function assertSchtasksArtifactUnchanged(exec, nativeTaskName, previous, nativeId, task) {
|
|
466
|
+
// This runs outside install's rollback region: no scheduler mutation has
|
|
467
|
+
// occurred, so a raced owner must remain untouched rather than be restored
|
|
468
|
+
// from the stale first read.
|
|
469
|
+
const current = runOrThrow(exec, ["schtasks", "/Query", "/TN", nativeTaskName, "/XML"], {
|
|
470
|
+
isOk: (result) => result.status === 0 || isMissingTaskResult(result),
|
|
471
|
+
message: (result) => `schtasks /Query failed during final ownership check (exit ${result.status}): ${result.stderr || result.stdout || "no output"}.`,
|
|
472
|
+
});
|
|
473
|
+
const changed = current.status !== previous.status ||
|
|
474
|
+
(current.status === 0 && normalizeXmlForUtf16File(current.stdout) !== normalizeXmlForUtf16File(previous.stdout));
|
|
475
|
+
if (changed) {
|
|
476
|
+
throw new ConfigError(`schtasks task "${nativeTaskName}" changed while it was being prepared; refusing to replace an unverified owner.`, "INVALID_CONFIG_FILE");
|
|
477
|
+
}
|
|
478
|
+
if (current.status === 0) {
|
|
479
|
+
assertSchedulerNativeArtifactOwner(nativeId, task, extractSchtasksInvocation(current.stdout)?.invocation);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
288
482
|
export function buildSchtasksXml(task, akmArgv, logDir, options) {
|
|
289
483
|
const folder = options.folderPrefix ?? DEFAULT_FOLDER_PREFIX;
|
|
290
484
|
const now = options.now ? options.now() : new Date();
|
|
291
485
|
const definition = buildSchtasksDefinition(task, options.binding ?? akmArgv, logDir, folder, options.contextPath, options.userSid, options.target);
|
|
292
486
|
const triggerXml = renderSchtasksTrigger(definition.trigger, now);
|
|
487
|
+
const nativeId = schedulerBindingNativeId(task);
|
|
293
488
|
return schtasksTemplate
|
|
294
|
-
.replaceAll("{{TASK_ID}}", escapeXml(
|
|
489
|
+
.replaceAll("{{TASK_ID}}", escapeXml(nativeId))
|
|
295
490
|
.replaceAll("{{FOLDER}}", escapeXml(folder))
|
|
296
491
|
.replace("{{SIGNATURE}}", definition.signature)
|
|
297
492
|
.replace("{{TRIGGER_XML}}", triggerXml)
|
|
@@ -301,19 +496,23 @@ export function buildSchtasksXml(task, akmArgv, logDir, options) {
|
|
|
301
496
|
.replace("{{ARGS}}", escapeXml(definition.args))
|
|
302
497
|
.replace("{{LOG_PATH}}", escapeXml(definition.logPath));
|
|
303
498
|
}
|
|
304
|
-
function buildSchtasksDefinition(task, akmArgv, logDir, folder, contextPath, userSid,
|
|
305
|
-
const spec = parseSchedule(task.
|
|
499
|
+
function buildSchtasksDefinition(task, akmArgv, logDir, folder, contextPath, userSid, _target) {
|
|
500
|
+
const spec = parseSchedule(task.cron, "schtasks");
|
|
306
501
|
const trigger = translateToSchtasks(spec);
|
|
307
|
-
const invocation =
|
|
502
|
+
const invocation = buildScheduledBindingInvocation(akmArgv, contextPath, task.invocation);
|
|
308
503
|
const invoke = `& ${invocation.argv.map((arg) => quotePowerShell(arg)).join(" ")}`;
|
|
309
|
-
const
|
|
504
|
+
const executionEvidence = task.executionEvidenceDigest === undefined
|
|
505
|
+
? ""
|
|
506
|
+
: `$null=${quotePowerShell(`akm-workflow-evidence:${assertSchedulerExecutionEvidenceDigest(task.executionEvidenceDigest)}`)}; `;
|
|
507
|
+
const script = `${executionEvidence}${invoke}; exit $LASTEXITCODE`;
|
|
310
508
|
const command = "powershell.exe";
|
|
311
509
|
const args = ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", script].map(quoteArg).join(" ");
|
|
312
|
-
const
|
|
510
|
+
const nativeId = schedulerBindingNativeId(task);
|
|
511
|
+
const logPath = path.join(logDir, `${nativeId}.log`);
|
|
313
512
|
// The boundary changes on reinstall, and enabled state can change via /Change.
|
|
314
513
|
// Keep both outside the stored definition fingerprint so no-op sync stays stable.
|
|
315
514
|
const fingerprint = createHash("sha256")
|
|
316
|
-
.update(JSON.stringify({ folder, id:
|
|
515
|
+
.update(JSON.stringify({ folder, id: nativeId, trigger, command, args, logPath, userSid }))
|
|
317
516
|
.digest("hex");
|
|
318
517
|
return { trigger, command, args, logPath, signature: `${SIGNATURE_PREFIX}${fingerprint}` };
|
|
319
518
|
}
|
package/dist/tasks/embedded.js
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
import fs from "node:fs";
|
|
23
23
|
import path from "node:path";
|
|
24
24
|
import { getDirname } from "../runtime.js";
|
|
25
|
-
import {
|
|
25
|
+
import { parseTaskV3Yaml } from "./source-v3.js";
|
|
26
26
|
/** Directory holding the bundled task template categories. */
|
|
27
27
|
const TASKS_ASSETS_DIR = path.join(getDirname(import.meta.url), "../assets/tasks");
|
|
28
28
|
/**
|
|
@@ -67,20 +67,20 @@ export function listEmbeddedTasks() {
|
|
|
67
67
|
}
|
|
68
68
|
let task;
|
|
69
69
|
try {
|
|
70
|
-
task =
|
|
70
|
+
task = parseTaskV3Yaml({ yaml, filePath, workspaceRoot: TASKS_ASSETS_DIR });
|
|
71
71
|
}
|
|
72
72
|
catch {
|
|
73
73
|
continue;
|
|
74
74
|
}
|
|
75
|
-
if (task.target.kind !== "
|
|
75
|
+
if (task.target.kind !== "run" || task.akm?.schedule === undefined)
|
|
76
76
|
continue;
|
|
77
77
|
tasks.push({
|
|
78
78
|
id,
|
|
79
79
|
label: `${category}/${id}`,
|
|
80
|
-
command: task.target.
|
|
81
|
-
schedule: task.schedule,
|
|
82
|
-
description: task.description ?? "",
|
|
83
|
-
enabled: task.enabled,
|
|
80
|
+
command: task.target.run,
|
|
81
|
+
schedule: task.akm.schedule,
|
|
82
|
+
description: task.akm.description ?? "",
|
|
83
|
+
enabled: task.akm.enabled !== false,
|
|
84
84
|
yaml,
|
|
85
85
|
});
|
|
86
86
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
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
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import os from "node:os";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { assertNever } from "../core/assert.js";
|
|
9
|
+
import { STANDALONE_FROZEN_SCRIPT_ARG } from "./standalone-script-entry.js";
|
|
10
|
+
export function materializeFrozenScript(script) {
|
|
11
|
+
const bytes = Buffer.from(script.bytesBase64, "base64");
|
|
12
|
+
const digest = createHash("sha256").update(bytes).digest("hex");
|
|
13
|
+
if (bytes.byteLength !== script.byteLength || digest !== script.sha256) {
|
|
14
|
+
throw new Error(`Frozen script snapshot ${script.sourceRef} failed its byte/hash integrity check.`);
|
|
15
|
+
}
|
|
16
|
+
const directory = fs.mkdtempSync(path.join(os.tmpdir(), "akm-task-script-"));
|
|
17
|
+
const file = path.join(directory, `snapshot${script.extension}`);
|
|
18
|
+
fs.writeFileSync(file, bytes, { mode: 0o700 });
|
|
19
|
+
return Object.freeze({ directory, file });
|
|
20
|
+
}
|
|
21
|
+
export function frozenScriptCommand(script, materializedPath) {
|
|
22
|
+
switch (script.interpreter) {
|
|
23
|
+
case "bun":
|
|
24
|
+
return [process.execPath, materializedPath];
|
|
25
|
+
case "bun-standalone":
|
|
26
|
+
return [process.execPath, STANDALONE_FROZEN_SCRIPT_ARG, materializedPath];
|
|
27
|
+
case "powershell":
|
|
28
|
+
return ["powershell", "-NoProfile", "-NonInteractive", "-File", materializedPath];
|
|
29
|
+
case "cmd":
|
|
30
|
+
return ["cmd", "/d", "/s", "/c", materializedPath];
|
|
31
|
+
case "go":
|
|
32
|
+
return ["go", "run", materializedPath];
|
|
33
|
+
case "kotlin":
|
|
34
|
+
return script.extension === ".kts" ? ["kotlinc", "-script", materializedPath] : ["kotlin", materializedPath];
|
|
35
|
+
case "sh":
|
|
36
|
+
case "python":
|
|
37
|
+
case "ruby":
|
|
38
|
+
case "perl":
|
|
39
|
+
case "php":
|
|
40
|
+
case "lua":
|
|
41
|
+
case "rscript":
|
|
42
|
+
case "swift":
|
|
43
|
+
return [script.interpreter, materializedPath];
|
|
44
|
+
default:
|
|
45
|
+
return assertNever(script.interpreter, "frozenScriptCommand");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export function cleanupFrozenScript(materialized) {
|
|
49
|
+
fs.rmSync(materialized.directory, { recursive: true, force: true });
|
|
50
|
+
}
|
|
@@ -24,12 +24,16 @@ import path from "node:path";
|
|
|
24
24
|
import { fileURLToPath } from "node:url";
|
|
25
25
|
import { ConfigError } from "../core/errors.js";
|
|
26
26
|
import { mainPath as runtimeMainPath } from "../runtime.js";
|
|
27
|
+
/** True only for Bun's virtual main path inside a compiled standalone executable. */
|
|
28
|
+
export function isBunStandaloneMain(mainPath = runtimeMainPath) {
|
|
29
|
+
return (mainPath?.startsWith("/$bunfs/") === true || (mainPath !== undefined && /^[A-Za-z]:[\\/]~BUN[\\/]/i.test(mainPath)));
|
|
30
|
+
}
|
|
27
31
|
export function resolveAkmInvocation(options = {}) {
|
|
28
32
|
const env = options.env ?? process.env;
|
|
29
33
|
const runtime = options.runtime ?? (process.versions.bun ? "bun" : "node");
|
|
30
34
|
const execPath = options.execPath ?? process.execPath;
|
|
31
35
|
const mainPath = options.mainPath ?? runtimeMainPath;
|
|
32
|
-
const isStandaloneMain =
|
|
36
|
+
const isStandaloneMain = isBunStandaloneMain(mainPath);
|
|
33
37
|
if (runtime === "bun" && isStandaloneMain && execPath) {
|
|
34
38
|
return { argv: [absoluteInvocationPath(execPath)], via: "standalone", kind: "standalone", eligible: true };
|
|
35
39
|
}
|