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.
Files changed (350) hide show
  1. package/CHANGELOG.md +103 -28
  2. package/README.md +3 -1
  3. package/SECURITY.md +1 -1
  4. package/STABILITY.md +1 -1
  5. package/dist/akm +2 -2
  6. package/dist/akm-migrate +2 -2
  7. package/dist/assets/hints/cli-hints-full.md +14 -9
  8. package/dist/assets/improve-strategies/proactive-maintenance.json +1 -1
  9. package/dist/assets/improve-strategies/reflect-distill.json +1 -1
  10. package/dist/assets/models.json +35 -0
  11. package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +3 -4
  12. package/dist/assets/stash-skeleton/facts/conventions/organization.md +1 -3
  13. package/dist/assets/tasks/core/extract.yml +6 -5
  14. package/dist/assets/tasks/core/improve.yml +6 -5
  15. package/dist/assets/tasks/core/index-refresh.yml +6 -5
  16. package/dist/assets/tasks/core/sync.yml +6 -5
  17. package/dist/assets/tasks/core/version-check.yml +6 -5
  18. package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +6 -5
  19. package/dist/assets/tasks/improve/akm-improve-catchup.yml +6 -5
  20. package/dist/assets/tasks/improve/akm-improve-consolidate.yml +6 -5
  21. package/dist/assets/tasks/improve/akm-improve-frequent.yml +6 -5
  22. package/dist/assets/tasks/improve/akm-improve-nightly.yml +6 -5
  23. package/dist/cli/confirm.js +2 -2
  24. package/dist/cli/parse-args.js +3 -24
  25. package/dist/cli/retired-commands.js +1 -1
  26. package/dist/cli/shared.js +2 -2
  27. package/dist/cli.js +11 -9
  28. package/dist/commands/agent/agent-dispatch.js +55 -89
  29. package/dist/commands/agent/contribute-cli.js +12 -45
  30. package/dist/commands/command/builtin-action.js +32 -0
  31. package/dist/commands/command/command-cli.js +99 -0
  32. package/dist/commands/command/command-execution.js +308 -0
  33. package/dist/commands/command/execution-source-loader.js +176 -0
  34. package/dist/commands/command/portable-template.js +60 -0
  35. package/dist/commands/config-cli.js +10 -4
  36. package/dist/commands/env/env.js +4 -2
  37. package/dist/commands/feedback-cli.js +1 -1
  38. package/dist/commands/health/checks.js +241 -29
  39. package/dist/commands/health/html-report.js +0 -14
  40. package/dist/commands/health/report-view-model.js +0 -1
  41. package/dist/commands/health/surfaces.js +6 -7
  42. package/dist/commands/health/types.js +0 -2
  43. package/dist/commands/health.js +63 -18
  44. package/dist/commands/improve/collapse-detector.js +5 -6
  45. package/dist/commands/improve/consolidate.js +251 -214
  46. package/dist/commands/improve/distill/promote-memory.js +71 -34
  47. package/dist/commands/improve/distill/quality-gate.js +17 -5
  48. package/dist/commands/improve/distill.js +232 -155
  49. package/dist/commands/improve/eligibility.js +112 -79
  50. package/dist/commands/improve/execution.js +57 -0
  51. package/dist/commands/improve/extract-cli.js +5 -5
  52. package/dist/commands/improve/extract-prompt.js +64 -22
  53. package/dist/commands/improve/extract.js +608 -360
  54. package/dist/commands/improve/improve-strategies.js +43 -14
  55. package/dist/commands/improve/improve.js +249 -29
  56. package/dist/commands/improve/loop-stages.js +11 -17
  57. package/dist/commands/improve/memory/memory-contradiction-detect.js +90 -66
  58. package/dist/commands/improve/outcome-loop.js +22 -38
  59. package/dist/commands/improve/planner.js +134 -0
  60. package/dist/commands/improve/preparation.js +730 -409
  61. package/dist/commands/improve/reflect.js +386 -223
  62. package/dist/commands/improve/run-context.js +3 -4
  63. package/dist/commands/improve/salience.js +6 -58
  64. package/dist/commands/improve/session-asset.js +12 -12
  65. package/dist/commands/lint/index.js +101 -29
  66. package/dist/commands/migrate-cli.js +11 -69
  67. package/dist/commands/migration-tool.js +6 -9
  68. package/dist/commands/models-cli.js +27 -0
  69. package/dist/commands/proposal/drain.js +258 -186
  70. package/dist/commands/proposal/proposal-cli.js +32 -10
  71. package/dist/commands/proposal/proposal.js +2 -5
  72. package/dist/commands/proposal/propose.js +192 -172
  73. package/dist/commands/proposal/repository.js +54 -91
  74. package/dist/commands/proposal/validators/proposal-validators.js +9 -7
  75. package/dist/commands/read/curate.js +53 -22
  76. package/dist/commands/read/registry-search.js +25 -9
  77. package/dist/commands/read/remember-cli.js +14 -2
  78. package/dist/commands/read/search.js +10 -4
  79. package/dist/commands/read/show.js +139 -153
  80. package/dist/commands/registry-cli.js +16 -7
  81. package/dist/commands/remember.js +33 -18
  82. package/dist/commands/sources/add-cli.js +19 -178
  83. package/dist/commands/sources/bundle-cli.js +15 -3
  84. package/dist/commands/sources/dangerous-env-audit.js +135 -0
  85. package/dist/commands/sources/info.js +2 -1
  86. package/dist/commands/sources/installed-stashes.js +901 -177
  87. package/dist/commands/sources/schema-repair.js +174 -95
  88. package/dist/commands/sources/self-update.js +30 -74
  89. package/dist/commands/sources/source-add.js +3 -5
  90. package/dist/commands/sources/sources-cli.js +2 -15
  91. package/dist/commands/sources/update-transaction.js +220 -0
  92. package/dist/commands/tasks/tasks-cli.js +3 -3
  93. package/dist/commands/tasks/tasks.js +736 -317
  94. package/dist/commands/workflow-cli.js +2 -2
  95. package/dist/core/adapter/adapters/agent-skills-adapter.js +3 -0
  96. package/dist/core/adapter/adapters/akm-adapter.js +85 -35
  97. package/dist/core/adapter/adapters/akm-lint.js +54 -39
  98. package/dist/core/adapter/adapters/akm-metadata.js +45 -45
  99. package/dist/core/adapter/adapters/akm-task-adapter.js +32 -49
  100. package/dist/core/adapter/adapters/akm-workflow-adapter.js +38 -23
  101. package/dist/core/adapter/adapters/dotenv-adapter.js +30 -1
  102. package/dist/core/adapter/adapters/generic-files-adapter.js +11 -0
  103. package/dist/core/adapter/adapters/index.js +0 -9
  104. package/dist/core/adapter/adapters/llm-wiki-adapter.js +4 -0
  105. package/dist/core/adapter/adapters/okf-adapter.js +4 -0
  106. package/dist/core/adapter/adapters/opencode-adapter.js +5 -8
  107. package/dist/core/adapter/adapters/tool-dir-shared.js +63 -6
  108. package/dist/core/adapter/adapters/website-snapshot-adapter.js +4 -0
  109. package/dist/core/adapter/execution-source.js +308 -0
  110. package/dist/core/adapter/recognize-match.js +36 -13
  111. package/dist/core/adapter/registry.js +0 -9
  112. package/dist/core/asset/stash-meta.js +94 -4
  113. package/dist/core/common.js +6 -11
  114. package/dist/core/config/config-io.js +3 -3
  115. package/dist/core/config/config-schema.js +18 -40
  116. package/dist/core/config/config-sources.js +11 -21
  117. package/dist/core/config/config-walker.js +31 -13
  118. package/dist/core/config/config.js +23 -26
  119. package/dist/core/config/schema/engines.js +8 -7
  120. package/dist/core/config/schema/improve-processes.js +29 -5
  121. package/dist/core/config/schema/index-config.js +0 -27
  122. package/dist/core/config/schema/primitives.js +1 -23
  123. package/dist/core/config/schema/sources-bundles.js +13 -16
  124. package/dist/core/errors.js +2 -0
  125. package/dist/core/events.js +68 -32
  126. package/dist/core/extra-params.js +1 -0
  127. package/dist/core/improve-result.js +315 -0
  128. package/dist/core/lesson-lint.js +0 -6
  129. package/dist/core/maintenance-barrier.js +4 -4
  130. package/dist/core/network-policy.js +152 -0
  131. package/dist/core/paths.js +1 -1
  132. package/dist/core/recognition-util.js +4 -4
  133. package/dist/core/registry-url.js +456 -0
  134. package/dist/core/state/migrations.js +161 -47
  135. package/dist/core/state-db.js +453 -80
  136. package/dist/core/system-error.js +32 -0
  137. package/dist/core/time.js +2 -12
  138. package/dist/core/write-source.js +0 -18
  139. package/dist/execution/directory-identity.js +52 -0
  140. package/dist/execution/executable-identity.js +107 -0
  141. package/dist/execution/guarded-source.js +398 -0
  142. package/dist/execution/json.js +95 -0
  143. package/dist/{commands/health/types-session-log.js → execution/limits.js} +2 -1
  144. package/dist/execution/record.js +55 -0
  145. package/dist/execution/resolved-request.js +730 -0
  146. package/dist/execution/source.js +320 -0
  147. package/dist/indexer/bundle-identity-guard.js +5 -4
  148. package/dist/indexer/db/graph-db.js +33 -0
  149. package/dist/indexer/graph/graph-boost.js +3 -4
  150. package/dist/indexer/graph/graph-extraction.js +562 -373
  151. package/dist/indexer/index-written-assets.js +78 -39
  152. package/dist/indexer/indexer.js +471 -432
  153. package/dist/indexer/installations.js +6 -0
  154. package/dist/indexer/lookup/adapter-concept-owner.js +283 -0
  155. package/dist/indexer/materialize-embeddings.js +155 -0
  156. package/dist/indexer/passes/memory-inference.js +227 -174
  157. package/dist/indexer/passes/metadata.js +263 -118
  158. package/dist/indexer/scan/doc-to-entry.js +7 -10
  159. package/dist/indexer/scan/drain-dir.js +51 -23
  160. package/dist/indexer/search/db-search.js +156 -50
  161. package/dist/indexer/search/fts-query.js +40 -40
  162. package/dist/indexer/search/ranking.js +36 -1
  163. package/dist/indexer/search/search-attribution.js +3 -1
  164. package/dist/indexer/search/search-fields.js +23 -14
  165. package/dist/indexer/search/search-hit-enrichers.js +1 -1
  166. package/dist/indexer/search/search-source.js +7 -16
  167. package/dist/indexer/search/semantic-status.js +10 -1
  168. package/dist/indexer/usage/show-usage.js +105 -0
  169. package/dist/indexer/usage/usage-events.js +7 -2
  170. package/dist/indexer/walk/matchers.js +40 -10
  171. package/dist/indexer/walk/path-resolver.js +5 -2
  172. package/dist/indexer/walk/walker.js +20 -2
  173. package/dist/integrations/agent/builder-shared.js +3 -6
  174. package/dist/integrations/agent/conversation-fallback.js +16 -0
  175. package/dist/integrations/agent/engine-resolution.js +87 -87
  176. package/dist/integrations/agent/execution-cascade.js +566 -0
  177. package/dist/integrations/agent/execution-definitions.js +211 -0
  178. package/dist/integrations/agent/execution-lowering.js +811 -0
  179. package/dist/integrations/agent/execution-preparation.js +67 -0
  180. package/dist/integrations/agent/index.js +0 -2
  181. package/dist/integrations/agent/inline-execution.js +74 -0
  182. package/dist/integrations/agent/model-map.js +515 -0
  183. package/dist/integrations/agent/persona-fallback.js +30 -0
  184. package/dist/integrations/agent/request-lowering.js +186 -0
  185. package/dist/integrations/agent/runner-dispatch.js +230 -37
  186. package/dist/integrations/agent/runner.js +12 -83
  187. package/dist/integrations/harnesses/aider/agent-builder.js +8 -0
  188. package/dist/integrations/harnesses/aider/index.js +0 -1
  189. package/dist/integrations/harnesses/amazonq/agent-builder.js +8 -0
  190. package/dist/integrations/harnesses/amazonq/index.js +0 -1
  191. package/dist/integrations/harnesses/claude/agent-builder.js +14 -1
  192. package/dist/integrations/harnesses/claude/index.js +1 -5
  193. package/dist/integrations/harnesses/claude/session-log.js +3 -33
  194. package/dist/integrations/harnesses/codex/agent-builder.js +8 -0
  195. package/dist/integrations/harnesses/codex/index.js +0 -1
  196. package/dist/integrations/harnesses/copilot/agent-builder.js +8 -0
  197. package/dist/integrations/harnesses/copilot/index.js +0 -1
  198. package/dist/integrations/harnesses/gemini/agent-builder.js +8 -0
  199. package/dist/integrations/harnesses/gemini/index.js +0 -1
  200. package/dist/integrations/harnesses/index.js +4 -44
  201. package/dist/integrations/harnesses/opencode/agent-builder.js +16 -9
  202. package/dist/integrations/harnesses/opencode/index.js +0 -2
  203. package/dist/integrations/harnesses/opencode/session-log.js +14 -204
  204. package/dist/integrations/harnesses/opencode-sdk/harness.js +12 -1
  205. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +40 -42
  206. package/dist/integrations/harnesses/openhands/agent-builder.js +8 -0
  207. package/dist/integrations/harnesses/openhands/index.js +0 -1
  208. package/dist/integrations/harnesses/pi/agent-builder.js +8 -0
  209. package/dist/integrations/harnesses/pi/index.js +0 -1
  210. package/dist/integrations/harnesses/shared.js +0 -1
  211. package/dist/integrations/harnesses/types.js +1 -3
  212. package/dist/integrations/lockfile.js +82 -79
  213. package/dist/integrations/session-logs/index.js +6 -17
  214. package/dist/integrations/session-logs/provider-base.js +1 -29
  215. package/dist/llm/client.js +10 -5
  216. package/dist/llm/embedder.js +6 -7
  217. package/dist/llm/embedders/local.js +37 -88
  218. package/dist/llm/embedders/types.js +1 -1
  219. package/dist/llm/graph-extract.js +75 -50
  220. package/dist/llm/index-passes.js +43 -5
  221. package/dist/llm/memory-infer.js +8 -6
  222. package/dist/llm/metadata-enhance.js +5 -3
  223. package/dist/llm/structured-call.js +122 -25
  224. package/dist/output/format-exempt.js +1 -1
  225. package/dist/output/render-registry.js +0 -16
  226. package/dist/output/renderers.js +12 -7
  227. package/dist/output/shapes/curate.js +1 -0
  228. package/dist/output/shapes/helpers.js +10 -2
  229. package/dist/output/shapes/passthrough.js +2 -0
  230. package/dist/output/text/command-format.js +31 -33
  231. package/dist/output/text/health-format.js +1 -29
  232. package/dist/output/text/migrate.js +6 -56
  233. package/dist/output/text/proposal-format.js +16 -1
  234. package/dist/output/text/workflow-format.js +16 -0
  235. package/dist/registry/network.js +279 -0
  236. package/dist/registry/pinned-request-helper.js +247 -0
  237. package/dist/registry/pinned-transport.js +717 -0
  238. package/dist/registry/providers/skills-sh.js +18 -6
  239. package/dist/registry/providers/static-index.js +20 -7
  240. package/dist/registry/resolve.js +53 -28
  241. package/dist/scripts/akm-migrate-node.js +19334 -52269
  242. package/dist/scripts/akm-migrate.js +19270 -51612
  243. package/dist/setup/registry-stash-loader.js +64 -20
  244. package/dist/setup/semantic-assets.js +9 -34
  245. package/dist/setup/setup.js +12 -30
  246. package/dist/setup/source-identity.js +17 -0
  247. package/dist/setup/steps/sources.js +36 -15
  248. package/dist/setup/steps/tasks.js +39 -11
  249. package/dist/sources/providers/git-provider.js +3 -3
  250. package/dist/sources/providers/npm.js +2 -2
  251. package/dist/sources/providers/provider-utils.js +4 -3
  252. package/dist/sources/providers/website.js +11 -7
  253. package/dist/sources/snapshot-fetchers/host-guard.js +9 -136
  254. package/dist/sources/snapshot-fetchers/website-ingest.js +25 -109
  255. package/dist/sources/website-url.js +73 -0
  256. package/dist/storage/engines/sqlite-migrations.js +81 -26
  257. package/dist/storage/managed-db.js +27 -24
  258. package/dist/storage/repositories/events-repository.js +3 -0
  259. package/dist/storage/repositories/index-connection.js +42 -10
  260. package/dist/storage/repositories/index-entries-repository.js +203 -229
  261. package/dist/storage/repositories/index-entry-mapper.js +8 -12
  262. package/dist/storage/repositories/index-entry-schema.js +255 -0
  263. package/dist/storage/repositories/index-fts-repository.js +64 -71
  264. package/dist/storage/repositories/index-llm-cache-repository.js +8 -13
  265. package/dist/storage/repositories/index-meta-repository.js +0 -11
  266. package/dist/storage/repositories/index-schema.js +74 -350
  267. package/dist/storage/repositories/index-utility-repository.js +12 -17
  268. package/dist/storage/repositories/index-vec-repository.js +56 -7
  269. package/dist/storage/repositories/proposals-repository.js +4 -127
  270. package/dist/storage/repositories/registry-cache.js +2 -1
  271. package/dist/storage/repositories/task-history-repository.js +20 -40
  272. package/dist/storage/repositories/workflow-runs-repository.js +228 -129
  273. package/dist/storage/sqlite-read-snapshot.js +148 -0
  274. package/dist/tasks/backends/cron.js +170 -42
  275. package/dist/tasks/backends/index.js +1 -1
  276. package/dist/tasks/backends/launchd.js +787 -202
  277. package/dist/tasks/backends/schtasks.js +282 -83
  278. package/dist/tasks/embedded.js +7 -7
  279. package/dist/tasks/frozen-script.js +50 -0
  280. package/dist/tasks/resolve-akm-bin.js +5 -1
  281. package/dist/tasks/runner.js +239 -251
  282. package/dist/tasks/runtime-v3.js +281 -0
  283. package/dist/tasks/scheduler-binding.js +272 -0
  284. package/dist/tasks/scheduler-invocation.js +57 -43
  285. package/dist/tasks/scheduler-sync.js +654 -0
  286. package/dist/tasks/source-v3.js +752 -0
  287. package/dist/tasks/standalone-script-entry.js +5 -0
  288. package/dist/tasks/task-id.js +29 -0
  289. package/dist/workflows/authoring/authoring.js +15 -32
  290. package/dist/workflows/exec/dispatch-redaction.js +14 -8
  291. package/dist/workflows/exec/exec-unit.js +7 -28
  292. package/dist/workflows/exec/frozen-judge.js +57 -89
  293. package/dist/workflows/exec/lowering-notices.js +23 -0
  294. package/dist/workflows/exec/native-executor.js +301 -458
  295. package/dist/workflows/exec/param-secrets.js +4 -3
  296. package/dist/workflows/exec/run-workflow.js +26 -32
  297. package/dist/workflows/exec/step-work.js +105 -109
  298. package/dist/workflows/exec/unit-dispatch.js +103 -27
  299. package/dist/workflows/exec/unit-writer.js +3 -3
  300. package/dist/workflows/exec/worktree.js +2 -2
  301. package/dist/workflows/ir/compile.js +86 -72
  302. package/dist/workflows/ir/environment-v4.js +328 -0
  303. package/dist/workflows/ir/freeze-v4.js +122 -0
  304. package/dist/workflows/ir/plan-hash.js +13 -7
  305. package/dist/workflows/ir/schema-v4.js +525 -0
  306. package/dist/workflows/ir/schema.js +25 -284
  307. package/dist/workflows/ir/source-freeze-v4.js +506 -0
  308. package/dist/workflows/parser.js +27 -24
  309. package/dist/workflows/program/schema.js +1 -2
  310. package/dist/workflows/renderer.js +42 -29
  311. package/dist/workflows/resource-limits.js +4 -5
  312. package/dist/workflows/runtime/agent-identity.js +11 -13
  313. package/dist/workflows/runtime/plan-classifier.js +8 -8
  314. package/dist/workflows/runtime/runs.js +27 -43
  315. package/dist/workflows/runtime/workflow-asset-loader.js +45 -205
  316. package/dist/workflows/source-files.js +373 -0
  317. package/dist/workflows/source-ir/compile.js +196 -0
  318. package/dist/workflows/source-ir/github-yaml.js +577 -0
  319. package/dist/workflows/source-ir/ordering.js +38 -0
  320. package/dist/workflows/source-ir/program.js +50 -0
  321. package/dist/workflows/source-ir/result.js +26 -0
  322. package/dist/workflows/source-ir/schema.js +772 -0
  323. package/dist/workflows/source-ir/semantics.js +242 -0
  324. package/dist/workflows/source-ir/uses.js +14 -0
  325. package/docs/README.md +2 -0
  326. package/docs/migration/README.md +3 -1
  327. package/docs/migration/release-notes/0.9.2.md +55 -0
  328. package/docs/migration/release-notes/README.md +5 -0
  329. package/docs/migration/v0.8-to-v0.9.md +76 -1077
  330. package/docs/migration/v0.9.0-troubleshooting.md +104 -516
  331. package/docs/migration/v0.9.1-to-v0.9.2.md +150 -0
  332. package/docs/reference/README.md +1 -0
  333. package/docs/reference/cli.md +230 -98
  334. package/docs/reference/configuration.md +159 -36
  335. package/docs/reference/data-and-telemetry.md +19 -1
  336. package/docs/reference/supported-formats.md +23 -3
  337. package/docs/reference/tasks.md +182 -0
  338. package/docs/reference/workflow-schema.md +91 -40
  339. package/docs/reference/workflows.md +33 -6
  340. package/package.json +10 -6
  341. package/schemas/akm-config.json +372 -224
  342. package/schemas/akm-task.json +324 -80
  343. package/schemas/akm-workflow.json +6 -9
  344. package/dist/core/migration-operation.js +0 -75
  345. package/dist/integrations/agent/model-aliases.js +0 -74
  346. package/dist/tasks/parser.js +0 -380
  347. package/dist/tasks/schema.js +0 -123
  348. package/dist/tasks/validator.js +0 -80
  349. package/dist/workflows/ir/freeze.js +0 -320
  350. package/dist/workflows/runtime/document-cache.js +0 -13
@@ -5,37 +5,24 @@
5
5
  * `akm task run <id>` — what cron / launchd / schtasks invoke at the
6
6
  * scheduled moment.
7
7
  *
8
- * Responsibilities:
9
- *
10
- * 1. Resolve the task file via `resolveAssetPath(stashDir, "task", id)`.
11
- * 2. Parse the task document. (Validation runs at `tasks add` /
12
- * `tasks sync` time, not here at run time we still want to attempt
13
- * execution and surface the actual failure rather than re-fail on a
14
- * validation error that the user already knows about.)
15
- * 3. Skip disabled tasks only when the invocation is scheduler-generated;
16
- * explicit manual runs are allowed for catch-up and testing.
17
- * 4. Dispatch by target kind:
18
- * • workflow → `runWorkflowSteps({ target: ref, params, signal, … })`
19
- * under a whole-run timeout (issue 11): an unattended run
20
- * gets the same abort path `akm workflow run --timeout`
21
- * gives an interactive one.
22
- * • prompt → `executeRunner(engine, prompt, { stdio: "captured" })`
23
- * 5. Capture stdout / stderr as structured rows in logs.db (task_logs) and,
24
- * transitionally, as a flat text tail at `<cacheDir>/tasks/logs/<id>/<ts>.log`
25
- * (per the #579 logs audit).
26
- * 6. Write a history row to state.db task_history table.
8
+ * The durable boundary is intentional: id/bundle resolution, source read,
9
+ * strict v3 parsing, target resolution, command authorization/lowering,
10
+ * workflow projectability, and frozen script-byte capture all finish before
11
+ * an attempt is reserved or a log is created. Once prepared, the runner skips
12
+ * disabled scheduler firings or dispatches the immutable command, workflow,
13
+ * shell, or script projection and records that actual attempt.
27
14
  *
28
15
  * Returns a structured result so the CLI handler can shape it for `output()`
29
16
  * and so tests can assert against it without scraping stdout.
30
17
  */
31
18
  import fs from "node:fs";
32
- import os from "node:os";
33
19
  import path from "node:path";
20
+ import { dispatchPreparedCommandInvocation } from "../commands/command/command-execution.js";
34
21
  import { armAbortDeadline } from "../core/abort-deadline.js";
35
22
  import { shouldSkipUnactivatedTask } from "../core/activation-policy.js";
23
+ import { detectAdapterId } from "../core/adapter/detect-adapter.js";
36
24
  import { assertNever } from "../core/assert.js";
37
- import { placementSpecFor } from "../core/asset/asset-placement.js";
38
- import { parseRefInput } from "../core/asset/resolve-ref.js";
25
+ import { makeBundleRef } from "../core/asset/asset-ref.js";
39
26
  import { loadConfig } from "../core/config/config.js";
40
27
  import { AkmError, NotFoundError, rethrowIfTestIsolationError } from "../core/errors.js";
41
28
  import { buildTaskRunId, insertTaskLogLines, openLogsDatabase, } from "../core/logs-db.js";
@@ -43,84 +30,79 @@ import { getTaskLogDir } from "../core/paths.js";
43
30
  import { redactCredentialPatterns, redactSensitiveText } from "../core/redaction.js";
44
31
  import { withStateDb } from "../core/state-db.js";
45
32
  import { runManagedSubprocess } from "../core/subprocess.js";
46
- import { fallbackAnnouncement, NO_ENGINE_MESSAGE_SUFFIX, NO_ENGINE_REMEDY, withEngineFallback, } from "../integrations/agent/engine-fallback.js";
47
- import { resolveEngine, resolveLlmEngineUse } from "../integrations/agent/engine-resolution.js";
48
- import { resolveModel } from "../integrations/agent/model-aliases.js";
49
- import { executeRunner } from "../integrations/agent/runner-dispatch.js";
50
- import { chatCompletion } from "../llm/client.js";
33
+ import { resolveWriteTarget } from "../core/write-source.js";
34
+ import { assertFrozenDirectoryIdentity } from "../execution/directory-identity.js";
35
+ import { resolveAdapterConceptOwner } from "../indexer/lookup/adapter-concept-owner.js";
51
36
  import { resolveAssetPath } from "../sources/resolve.js";
52
37
  import { decodeTaskHistoryMetadata, finalizeTaskHistoryAttempt, getTaskHistory, getTaskHistoryRuns, queryTaskHistory, reserveTaskHistoryAttempt, upsertTaskHistory, } from "../storage/repositories/task-history-repository.js";
53
38
  import { runWorkflowSteps } from "../workflows/exec/run-workflow.js";
54
- import { findBareAkmExecutableIndex } from "./command-executable.js";
39
+ import { cleanupFrozenScript, frozenScriptCommand, materializeFrozenScript } from "./frozen-script.js";
55
40
  import { collectTaskLogSensitiveValues } from "./log-redaction.js";
56
- import { parseTaskDocument } from "./parser.js";
57
41
  import { resolveAkmInvocation } from "./resolve-akm-bin.js";
42
+ import { prepareTaskV3Execution, } from "./runtime-v3.js";
58
43
  import { scheduledTaskContextEnv } from "./scheduler-invocation.js";
59
- import { validateTaskId } from "./task-id.js";
44
+ import { parseTaskV3Yaml } from "./source-v3.js";
45
+ import { validateTaskConceptId, validateTaskId } from "./task-id.js";
60
46
  export const INVALID_TASK_ATTEMPT_ID = "_invalid-task-id";
47
+ const CONFIG_FREE_TASK_RUNTIME = Object.freeze({
48
+ configVersion: "0.9.0",
49
+ semanticSearchMode: "off",
50
+ });
61
51
  export async function runTask(id, options) {
62
- const runAgentImpl = options.runAgentImpl;
63
52
  const runWorkflowStepsImpl = options.runWorkflowStepsImpl ?? runWorkflowSteps;
64
53
  const now = options.now ?? (() => new Date());
65
54
  const requestedStartedAt = now();
66
- try {
55
+ const stashDir = options.stashDir;
56
+ const adapterId = options.adapterId ?? detectAdapterId(stashDir);
57
+ if (adapterId === "akm-task")
58
+ validateTaskConceptId(id);
59
+ else
67
60
  validateTaskId(id);
68
- }
69
- catch (failure) {
70
- const attempt = reserveTaskAttempt(INVALID_TASK_ATTEMPT_ID, requestedStartedAt);
71
- recordTaskAttemptFailure({
72
- taskId: INVALID_TASK_ATTEMPT_ID,
73
- reason: "invalid_task_id",
74
- failure,
75
- startedAt: attempt.startedAt,
76
- finishedAt: now(),
77
- logDir: options.logDir,
78
- historyReserved: attempt.historyReserved,
79
- });
80
- throw failure;
81
- }
61
+ const taskConceptId = adapterId === "akm" ? `tasks/${id}` : id;
62
+ const owner = resolveAdapterConceptOwner(stashDir, adapterId, taskConceptId);
63
+ if (!owner) {
64
+ throw new NotFoundError(`Task ${JSON.stringify(id)} was not found in the configured ${JSON.stringify(adapterId)} component.`, "ASSET_NOT_FOUND");
65
+ }
66
+ const filePath = owner.path;
67
+ const yaml = fs.readFileSync(filePath, "utf8");
68
+ const source = parseTaskV3Yaml({ yaml, filePath, workspaceRoot: stashDir });
69
+ const requiresCommandConfig = source.target.kind === "uses" &&
70
+ (source.target.uses.kind === "builtin-command" || source.target.uses.kind === "command");
71
+ const config = requiresCommandConfig ? loadConfig() : CONFIG_FREE_TASK_RUNTIME;
72
+ const bundleName = options.bundleName ?? config.defaultBundle ?? "stash";
73
+ const task = await prepareTaskV3Execution(source, {
74
+ taskId: id,
75
+ taskRef: makeBundleRef(bundleName, taskConceptId),
76
+ bundleName,
77
+ bundleRoot: stashDir,
78
+ config,
79
+ // Agent profiles build child env from an allowlist, so freeze the closed
80
+ // scheduler-restored AKM directory context before command preparation.
81
+ ...(options.scheduled ? { schedulerContext: scheduledTaskContextEnv() } : {}),
82
+ resolveAsset: async ({ bundle, type, name }) => {
83
+ if (bundle === bundleName) {
84
+ return { file: await resolveAssetPath(stashDir, type, name), bundleRoot: stashDir };
85
+ }
86
+ const resolutionConfig = requiresCommandConfig ? config : loadConfig();
87
+ const resolvedBundle = resolveWriteTarget(resolutionConfig, bundle, { requireWritable: false });
88
+ return {
89
+ file: await resolveAssetPath(resolvedBundle.source.path, type, name),
90
+ bundleRoot: resolvedBundle.source.path,
91
+ };
92
+ },
93
+ });
94
+ // All validation, parsing, source resolution, command cascade preparation,
95
+ // and frozen-byte capture above is non-mutating. Only a fully projectable
96
+ // task may reserve durable history or create a log.
82
97
  const attempt = reserveTaskAttempt(id, requestedStartedAt);
83
98
  const startedAt = attempt.startedAt;
84
- let failureReason = "task_load_failed";
99
+ const startedIso = startedAt.toISOString();
100
+ const logPath = resolveTaskLogPath(options.logDir, id, startedIso);
85
101
  try {
86
- const stashDir = options.stashDir;
87
- const filePath = await resolveAssetPath(stashDir, "task", id);
88
- const yaml = fs.readFileSync(filePath, "utf8");
89
- failureReason = "task_parse_failed";
90
- const task = parseTaskDocument({ yaml, filePath, id });
91
- failureReason = "task_dispatch_failed";
92
- const startedIso = startedAt.toISOString();
93
- const logPath = resolveTaskLogPath(options.logDir, id, startedIso);
94
102
  if (shouldSkipUnactivatedTask({ enabled: task.enabled, scheduled: options.scheduled === true })) {
95
- const finishedAt = finishAttempt(startedAt, now());
96
- const disabledTarget = task.target.kind === "workflow"
97
- ? { kind: "workflow", ref: task.target.ref }
98
- : task.target.kind === "command"
99
- ? { kind: "command", cmd: task.target.cmd }
100
- : { kind: "prompt", engine: task.target.engine ?? null };
101
- const result = {
102
- id,
103
- status: "disabled",
104
- startedAt: startedIso,
105
- finishedAt: finishedAt.toISOString(),
106
- durationMs: finishedAt.getTime() - startedAt.getTime(),
107
- log: logPath,
108
- target: disabledTarget,
109
- };
110
- const disabledLine = `[akm task] task "${id}" is disabled — skipping run.`;
111
- persistRunLog({
112
- taskId: id,
113
- startedAtIso: startedIso,
114
- finishedAtIso: result.finishedAt,
115
- logPath,
116
- fileText: `${disabledLine}\n`,
117
- dbLines: [{ line: disabledLine }],
118
- redactNames: task.redact,
119
- });
120
- appendHistory(result, attempt.historyReserved);
121
- return result;
103
+ return finishDisabledTask(task, logPath, startedAt, now(), attempt.historyReserved);
122
104
  }
123
- if (task.target.kind === "workflow") {
105
+ if (task.kind === "workflow") {
124
106
  return await runWorkflowTask({
125
107
  task,
126
108
  logPath,
@@ -132,34 +114,34 @@ export async function runTask(id, options) {
132
114
  ...(options.clearTimeoutFn ? { clearTimeoutFn: options.clearTimeoutFn } : {}),
133
115
  });
134
116
  }
135
- if (task.target.kind === "command") {
136
- return await runCommandTask({
117
+ if (task.kind === "command") {
118
+ return await runPreparedCommandTask({
137
119
  task,
138
120
  logPath,
139
121
  startedAt,
140
122
  now,
141
123
  historyReserved: attempt.historyReserved,
142
- ...(options.spawnFn ? { spawnFn: options.spawnFn } : {}),
143
- ...(options.setTimeoutFn ? { setTimeoutFn: options.setTimeoutFn } : {}),
144
- ...(options.clearTimeoutFn ? { clearTimeoutFn: options.clearTimeoutFn } : {}),
124
+ runAgentImpl: options.runAgentImpl,
125
+ agentOptions: options.agentOptions,
126
+ chatCompletionImpl: options.chatCompletionImpl,
145
127
  });
146
128
  }
147
- return await runPromptTask({
129
+ options.beforeNativeDispatch?.(task);
130
+ return await runNativeTask({
148
131
  task,
149
- stashDir,
150
132
  logPath,
151
133
  startedAt,
152
134
  now,
153
- runAgentImpl,
154
- agentOptions: options.agentOptions,
155
- chatCompletionImpl: options.chatCompletionImpl ?? chatCompletion,
156
135
  historyReserved: attempt.historyReserved,
136
+ ...(options.spawnFn ? { spawnFn: options.spawnFn } : {}),
137
+ ...(options.setTimeoutFn ? { setTimeoutFn: options.setTimeoutFn } : {}),
138
+ ...(options.clearTimeoutFn ? { clearTimeoutFn: options.clearTimeoutFn } : {}),
157
139
  });
158
140
  }
159
141
  catch (failure) {
160
142
  recordTaskAttemptFailure({
161
143
  taskId: id,
162
- reason: failureReason,
144
+ reason: "task_dispatch_failed",
163
145
  failure,
164
146
  startedAt,
165
147
  finishedAt: now(),
@@ -169,31 +151,121 @@ export async function runTask(id, options) {
169
151
  throw failure;
170
152
  }
171
153
  }
172
- // ── command target ──────────────────────────────────────────────────────────
173
- async function runCommandTask(input) {
154
+ function preparedResultTarget(task) {
155
+ if (task.kind === "workflow")
156
+ return { kind: "workflow", ref: task.ref };
157
+ if (task.kind === "command")
158
+ return { kind: "prompt", engine: task.invocation.request.engine.name ?? null };
159
+ return { kind: "command" };
160
+ }
161
+ function finishDisabledTask(task, logPath, startedAt, observedFinishedAt, historyReserved) {
162
+ const finishedAt = finishAttempt(startedAt, observedFinishedAt);
163
+ const line = `[akm task] task "${task.taskId}" is disabled — skipping run.`;
164
+ const result = {
165
+ id: task.taskId,
166
+ status: "disabled",
167
+ startedAt: startedAt.toISOString(),
168
+ finishedAt: finishedAt.toISOString(),
169
+ durationMs: finishedAt.getTime() - startedAt.getTime(),
170
+ log: logPath,
171
+ target: preparedResultTarget(task),
172
+ };
173
+ persistRunLog({
174
+ taskId: task.taskId,
175
+ startedAtIso: result.startedAt,
176
+ finishedAtIso: result.finishedAt,
177
+ logPath,
178
+ fileText: `${line}\n`,
179
+ dbLines: [{ line }],
180
+ redactNames: task.redact,
181
+ environment: task.environment,
182
+ });
183
+ appendHistory(result, historyReserved);
184
+ return result;
185
+ }
186
+ // ── shell and frozen-script targets ─────────────────────────────────────────
187
+ function shellCommand(task) {
188
+ const command = resolveLeadingBareAkmCommand(task.command, task.shell);
189
+ switch (task.shell) {
190
+ case "sh":
191
+ case "bash":
192
+ case "zsh":
193
+ return [task.shell, "-c", command];
194
+ case "pwsh":
195
+ case "powershell":
196
+ return [task.shell, "-NoProfile", "-NonInteractive", "-Command", command];
197
+ case "cmd":
198
+ return ["cmd", "/d", "/s", "/c", command];
199
+ default:
200
+ return assertNever(task.shell, "shellCommand");
201
+ }
202
+ }
203
+ /**
204
+ * Bind an unambiguous leading bare `akm` (including the task-v2 migrator's
205
+ * quoted form) to this installation. Explicit paths and arbitrary shell
206
+ * fragments remain author-controlled.
207
+ */
208
+ function resolveLeadingBareAkmCommand(command, shell) {
209
+ const leadingBareAkm = /^(\s*)(?:akm(?:\.exe)?|'akm(?:\.exe)?'|"akm(?:\.exe)?")(?=$|[\s;|&])/i;
210
+ if (!leadingBareAkm.test(command))
211
+ return command;
212
+ const invocation = resolveAkmInvocation()
213
+ .argv.map((part) => quoteShellArgument(part, shell))
214
+ .join(" ");
215
+ return command.replace(leadingBareAkm, (_match, leadingWhitespace) => `${leadingWhitespace}${invocation}`);
216
+ }
217
+ function quoteShellArgument(value, shell) {
218
+ switch (shell) {
219
+ case "sh":
220
+ case "bash":
221
+ case "zsh":
222
+ return `'${value.replaceAll("'", `'"'"'`)}'`;
223
+ case "pwsh":
224
+ case "powershell":
225
+ return `'${value.replaceAll("'", "''")}'`;
226
+ case "cmd":
227
+ return `"${value.replaceAll('"', '""')}"`;
228
+ default:
229
+ return assertNever(shell, "quoteShellArgument");
230
+ }
231
+ }
232
+ async function runNativeTask(input) {
174
233
  const { task, logPath, startedAt, now, historyReserved } = input;
175
- if (task.target.kind !== "command")
176
- throw new Error("invariant: command target");
177
- const { cmd } = task.target;
178
- const spawnCmd = resolveNestedAkmCommand(cmd);
234
+ let materialized;
235
+ let cmd = task.kind === "shell" ? shellCommand(task) : [];
179
236
  // Unset → the unattended default; `null` → the explicit no-timeout opt-out.
180
237
  const timeoutMs = task.timeoutMs !== undefined ? task.timeoutMs : DEFAULT_SCHEDULED_TASK_TIMEOUT_MS;
181
- const header = `[akm task] task=${task.id} kind=command cmd=${cmd.join(" ")}`;
238
+ const header = task.kind === "shell"
239
+ ? `[akm task] task=${task.taskId} kind=run shell=${task.shell}`
240
+ : `[akm task] task=${task.taskId} kind=script ref=${task.sourceRef} sha256=${task.sha256}`;
182
241
  const logLines = [header];
183
242
  const dbLines = [{ line: header }];
184
243
  let exitCode = null;
185
244
  try {
245
+ // The projector froze both canonical paths and filesystem identities before
246
+ // history mutation. Re-resolve the authored root/cwd immediately before
247
+ // spawn so a symlink, ancestor, bundle-root, or directory/file swap cannot
248
+ // redirect execution outside that physical workspace.
249
+ assertFrozenDirectoryIdentity(task.cwdIdentity);
250
+ if (task.kind === "script") {
251
+ materialized = materializeFrozenScript(task);
252
+ cmd = frozenScriptCommand(task, materialized.file);
253
+ }
186
254
  // Managed spawn (src/core/subprocess.ts): process-GROUP kill so a timeout
187
255
  // reaps the whole command tree (no orphans), and a SIGTERM→SIGKILL ladder
188
256
  // so a child that ignores SIGTERM can't wedge the run forever.
189
- const result = await runManagedSubprocess(spawnCmd, {
257
+ const result = await runManagedSubprocess(cmd, {
190
258
  capture: true,
191
- cwd: process.env.HOME ?? os.tmpdir(),
259
+ cwd: task.cwd,
192
260
  // Stamp task-runner provenance so any akm invocation in the command tree
193
261
  // records usage events as machine traffic, not user demand (DRIFT-6).
194
262
  // A more specific stamp already in the environment (e.g. improve's
195
263
  // AKM_EVENT_SOURCE=improve on its child spawns) still wins in children.
196
- env: { ...process.env, AKM_EVENT_SOURCE: process.env.AKM_EVENT_SOURCE ?? "task" },
264
+ env: {
265
+ ...process.env,
266
+ ...task.environment,
267
+ AKM_EVENT_SOURCE: process.env.AKM_EVENT_SOURCE ?? "task",
268
+ },
197
269
  timeoutMs,
198
270
  ...(input.spawnFn ? { spawnFn: input.spawnFn } : {}),
199
271
  ...(input.setTimeoutFn ? { setTimeoutFn: input.setTimeoutFn } : {}),
@@ -227,19 +299,24 @@ async function runCommandTask(input) {
227
299
  dbLines.push({ level: "error", line: `spawn_error=${msg}` });
228
300
  exitCode = 1;
229
301
  }
302
+ finally {
303
+ if (materialized)
304
+ cleanupFrozenScript(materialized);
305
+ }
230
306
  const finishedAt = finishAttempt(startedAt, now());
231
307
  persistRunLog({
232
- taskId: task.id,
308
+ taskId: task.taskId,
233
309
  startedAtIso: startedAt.toISOString(),
234
310
  finishedAtIso: finishedAt.toISOString(),
235
311
  logPath,
236
312
  fileText: `${logLines.join("\n")}\n`,
237
313
  dbLines,
238
314
  redactNames: task.redact,
315
+ environment: task.environment,
239
316
  });
240
317
  const status = exitCode === 0 ? "completed" : "failed";
241
318
  const result = {
242
- id: task.id,
319
+ id: task.taskId,
243
320
  status,
244
321
  startedAt: startedAt.toISOString(),
245
322
  finishedAt: finishedAt.toISOString(),
@@ -251,13 +328,6 @@ async function runCommandTask(input) {
251
328
  appendHistory(result, historyReserved);
252
329
  return result;
253
330
  }
254
- /** Avoid a second PATH lookup when a task invokes the same AKM installation. */
255
- function resolveNestedAkmCommand(cmd) {
256
- const akmIndex = findBareAkmExecutableIndex(cmd);
257
- if (akmIndex === undefined)
258
- return cmd;
259
- return [...cmd.slice(0, akmIndex), ...resolveAkmInvocation().argv, ...cmd.slice(akmIndex + 1)];
260
- }
261
331
  // ── workflow target ─────────────────────────────────────────────────────────
262
332
  /**
263
333
  * Whole-run timeout applied to a workflow-bound task that does not declare its
@@ -296,15 +366,8 @@ export const DEFAULT_WORKFLOW_TASK_TIMEOUT_MS = 6 * 60 * 60 * 1000;
296
366
  export const DEFAULT_SCHEDULED_TASK_TIMEOUT_MS = DEFAULT_WORKFLOW_TASK_TIMEOUT_MS;
297
367
  async function runWorkflowTask(input) {
298
368
  const { task, logPath, startedAt, now, runWorkflowStepsImpl, historyReserved } = input;
299
- if (task.target.kind !== "workflow")
300
- throw new Error("invariant: workflow target");
301
- const workflowTarget = task.target;
302
- const ref = parseRefInput(workflowTarget.ref);
303
- if (ref.type !== "workflow") {
304
- throw new NotFoundError(`Task "${task.id}" workflow target must be a workflow ref (got "${workflowTarget.ref}").`, "WORKFLOW_NOT_FOUND");
305
- }
306
369
  // Unset → the unattended default; `null` → the explicit no-timeout opt-out.
307
- const timeoutMs = workflowTarget.timeoutMs === undefined ? DEFAULT_WORKFLOW_TASK_TIMEOUT_MS : workflowTarget.timeoutMs;
370
+ const timeoutMs = task.timeoutMs === undefined ? DEFAULT_WORKFLOW_TASK_TIMEOUT_MS : task.timeoutMs;
308
371
  // The shared deadline `akm workflow run --timeout` also arms
309
372
  // ({@link armAbortDeadline}): one AbortController for the run's lifetime,
310
373
  // aborted by a timer. The engine reads `options.signal` at every step
@@ -314,7 +377,7 @@ async function runWorkflowTask(input) {
314
377
  const controller = new AbortController();
315
378
  const deadline = armAbortDeadline(controller, {
316
379
  timeoutMs,
317
- reason: `Workflow task "${task.id}" timed out after ${timeoutMs}ms.`,
380
+ reason: `Workflow task "${task.taskId}" timed out after ${timeoutMs}ms.`,
318
381
  ...(input.setTimeoutFn ? { setTimeoutFn: input.setTimeoutFn } : {}),
319
382
  ...(input.clearTimeoutFn ? { clearTimeoutFn: input.clearTimeoutFn } : {}),
320
383
  });
@@ -333,11 +396,11 @@ async function runWorkflowTask(input) {
333
396
  process.env.AKM_EVENT_SOURCE = priorEventSource ?? "task";
334
397
  try {
335
398
  const execution = await runWorkflowStepsImpl({
336
- target: workflowTarget.ref,
337
- params: workflowTarget.params,
399
+ target: task.ref,
400
+ params: task.params,
338
401
  signal: controller.signal,
339
- ...(workflowTarget.maxSteps !== undefined ? { maxSteps: workflowTarget.maxSteps } : {}),
340
- ...(workflowTarget.maxRetries !== undefined ? { maxRetries: workflowTarget.maxRetries } : {}),
402
+ ...(task.maxSteps !== undefined ? { maxSteps: task.maxSteps } : {}),
403
+ ...(task.maxRetries !== undefined ? { maxRetries: task.maxRetries } : {}),
341
404
  });
342
405
  detail = execution.run;
343
406
  runWarnings = execution.warnings ?? [];
@@ -387,25 +450,26 @@ async function runWorkflowTask(input) {
387
450
  ...(timedOutAfterMs !== undefined ? { timedOutAfterMs } : {}),
388
451
  });
389
452
  persistRunLog({
390
- taskId: task.id,
453
+ taskId: task.taskId,
391
454
  startedAtIso: startedAt.toISOString(),
392
455
  finishedAtIso: finishedAt.toISOString(),
393
456
  logPath,
394
457
  fileText: log.fileText,
395
458
  dbLines: log.dbLines,
396
459
  redactNames: task.redact,
460
+ environment: task.environment,
397
461
  });
398
462
  const result = {
399
- id: task.id,
463
+ id: task.taskId,
400
464
  status,
401
465
  startedAt: startedAt.toISOString(),
402
466
  finishedAt: finishedAt.toISOString(),
403
467
  durationMs: finishedAt.getTime() - startedAt.getTime(),
404
468
  log: logPath,
405
- target: { kind: "workflow", ref: task.target.ref },
469
+ target: { kind: "workflow", ref: task.ref },
406
470
  detail: {
407
471
  runId: detail?.id,
408
- ...(failure ? { error: failure.message } : {}),
472
+ ...(failure ? { error: scrubTaskOutput(task, failure.message) } : {}),
409
473
  },
410
474
  };
411
475
  appendHistory(result, historyReserved);
@@ -445,7 +509,7 @@ function mapWorkflowStatus(status) {
445
509
  }
446
510
  function renderWorkflowLog(input) {
447
511
  const dbLines = [
448
- { line: `[akm task] task=${input.task.id} kind=workflow ref=${input.task.target.ref}` },
512
+ { line: `[akm task] task=${input.task.taskId} kind=workflow ref=${input.task.ref}` },
449
513
  ];
450
514
  for (const warning of input.warnings ?? [])
451
515
  dbLines.push({ level: "warn", line: warning });
@@ -461,98 +525,30 @@ function renderWorkflowLog(input) {
461
525
  }
462
526
  return { fileText: `${dbLines.map((entry) => entry.line).join("\n")}\n`, dbLines };
463
527
  }
464
- // ── prompt target ───────────────────────────────────────────────────────────
465
- async function runPromptTask(input) {
466
- const { task, stashDir, logPath, startedAt, now, agentOptions } = input;
467
- if (task.target.kind !== "prompt")
468
- throw new Error("invariant: prompt target");
469
- const promptTarget = task.target;
470
- // Same implicit opencode-sdk fallback the workflow freeze boundary applies,
471
- // so a scheduled prompt task on an engine-less install behaves identically.
472
- const { config, fallbackEngineName } = withEngineFallback(loadConfig());
473
- const engineName = promptTarget.engine ?? config.defaults?.engine;
474
- // `promptTarget.engine` outranks `defaults.engine`, so the fallback is only
475
- // reportable when it is the engine actually selected.
476
- const engineAnnouncement = fallbackAnnouncement(fallbackEngineName, engineName);
477
- if (!engineName)
478
- throw new NotFoundError(`Task "${task.id}" ${NO_ENGINE_MESSAGE_SUFFIX} ${NO_ENGINE_REMEDY}`, "ASSET_NOT_FOUND");
479
- let runner = resolveEngine(engineName, config);
480
- if (runner.kind === "llm") {
481
- const resolved = resolveLlmEngineUse(config, [
482
- {
483
- engine: engineName,
484
- ...(promptTarget.model !== undefined ? { model: promptTarget.model } : {}),
485
- ...(promptTarget.timeoutMs !== undefined ? { timeoutMs: promptTarget.timeoutMs } : {}),
486
- ...(promptTarget.llm !== undefined ? { llm: promptTarget.llm } : {}),
487
- },
488
- ]);
489
- runner = {
490
- kind: "llm",
491
- engine: resolved.engine,
492
- connection: resolved.connection,
493
- ...(resolved.credential ? { credential: resolved.credential } : {}),
494
- timeoutMs: resolved.timeoutMs,
495
- };
496
- }
497
- else {
498
- if (promptTarget.llm !== undefined) {
499
- throw new NotFoundError(`Task "${task.id}" uses llm overrides with non-LLM engine "${engineName}".`, "ASSET_NOT_FOUND");
500
- }
501
- const requestedModel = promptTarget.model;
502
- const platform = runner.profile.platform;
503
- if (!platform)
504
- throw new Error(`Engine "${engineName}" resolved without a platform.`);
505
- const model = requestedModel
506
- ? resolveModel(requestedModel, platform, runner.profile.modelAliases, runner.profile.globalModelAliases)
507
- : runner.profile.model;
508
- runner = {
509
- ...runner,
510
- profile: { ...runner.profile, ...(model ? { model, modelIsExact: true } : {}) },
511
- // Unset → the unattended default (DEFAULT_AGENT_TIMEOUT_MS is null, which
512
- // let a prompting or wedged agent CLI hang the schedule); `null` → the
513
- // explicit no-timeout opt-out.
514
- timeoutMs: promptTarget.timeoutMs !== undefined ? promptTarget.timeoutMs : DEFAULT_SCHEDULED_TASK_TIMEOUT_MS,
515
- };
516
- }
517
- const promptText = await resolvePromptText(task, stashDir);
518
- const result = await executeRunner(runner, promptText, {
519
- stdio: "captured",
520
- cwd: stashDir,
521
- ...agentOptions,
522
- // Stamp task-runner provenance for any akm invocation the agent makes
523
- // (DRIFT-6: agent-task traffic must not be recorded as user demand).
524
- // Caller-supplied env still wins on conflicts.
525
- //
526
- // The agent child env is built from an allowlist, not inherited, so the
527
- // scheduler's AKM_* directory context was dropped here — an agent's `akm`
528
- // sub-commands then targeted the DEFAULT stash and DB rather than the
529
- // ones the scheduled run was configured for. The command arm keeps this
530
- // context because it inherits process.env; forward it explicitly.
531
- env: { AKM_EVENT_SOURCE: "task", ...scheduledTaskContextEnv(), ...agentOptions?.env },
532
- }, {
528
+ // ── common command target ───────────────────────────────────────────────────
529
+ async function runPreparedCommandTask(input) {
530
+ const { task, logPath, startedAt, now, agentOptions } = input;
531
+ const result = await dispatchPreparedCommandInvocation(task.invocation, {
533
532
  ...(input.runAgentImpl ? { runAgent: input.runAgentImpl } : {}),
534
- llm: async (spec, prompt, options) => {
535
- const started = Date.now();
536
- const stdout = await input.chatCompletionImpl(spec.connection, [{ role: "user", content: prompt }], {
537
- ...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),
538
- });
539
- return { ok: true, exitCode: 0, stdout, stderr: "", durationMs: Date.now() - started };
540
- },
533
+ ...(input.chatCompletionImpl ? { chat: input.chatCompletionImpl } : {}),
534
+ ...(agentOptions ? { runOptions: agentOptions } : {}),
541
535
  });
536
+ const engineName = result.engine;
542
537
  const finishedAt = finishAttempt(startedAt, now());
543
- const log = renderPromptLog({ task, engineName, result, engineAnnouncement });
538
+ const log = renderPromptLog({ task, engineName, result, notices: result.notices, warnings: result.warnings });
544
539
  persistRunLog({
545
- taskId: task.id,
540
+ taskId: task.taskId,
546
541
  startedAtIso: startedAt.toISOString(),
547
542
  finishedAtIso: finishedAt.toISOString(),
548
543
  logPath,
549
544
  fileText: log.fileText,
550
545
  dbLines: log.dbLines,
551
546
  redactNames: task.redact,
547
+ environment: task.environment,
552
548
  });
553
549
  const status = result.ok ? "completed" : "failed";
554
550
  const out = {
555
- id: task.id,
551
+ id: task.taskId,
556
552
  status,
557
553
  startedAt: startedAt.toISOString(),
558
554
  finishedAt: finishedAt.toISOString(),
@@ -561,46 +557,38 @@ async function runPromptTask(input) {
561
557
  target: { kind: "prompt", engine: engineName },
562
558
  detail: result.ok
563
559
  ? { exitCode: result.exitCode }
564
- : { reason: result.reason, error: result.error, exitCode: result.exitCode },
560
+ : {
561
+ reason: result.reason === undefined ? undefined : scrubTaskOutput(task, result.reason),
562
+ error: result.error === undefined ? undefined : scrubTaskOutput(task, result.error),
563
+ exitCode: result.exitCode,
564
+ },
565
+ ...(result.notices && result.notices.length > 0
566
+ ? {
567
+ notices: result.notices.map((notice) => ({
568
+ ...notice,
569
+ message: scrubTaskOutput(task, notice.message),
570
+ })),
571
+ }
572
+ : {}),
565
573
  };
566
574
  appendHistory(out, input.historyReserved);
567
575
  return out;
568
576
  }
569
- async function resolvePromptText(task, stashDir) {
570
- if (task.target.kind !== "prompt")
571
- throw new Error("invariant: prompt target");
572
- const src = task.target.source;
573
- if (src.kind === "inline")
574
- return src.text;
575
- if (src.kind === "file") {
576
- const taskDir = path.dirname(task.source.path);
577
- const filePath = path.isAbsolute(src.path) ? src.path : path.resolve(taskDir, src.path);
578
- if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) {
579
- throw new NotFoundError(`Prompt file not found: ${filePath}`, "FILE_NOT_FOUND");
580
- }
581
- return fs.readFileSync(filePath, "utf8");
582
- }
583
- // asset
584
- const ref = parseRefInput(src.ref);
585
- // D11 — see the matching guard in validator.ts: `resolveAssetPath`
586
- // (src/sources/resolve.ts) is placement-dir-only and cannot route an
587
- // opaque adapter conceptId, which `parseRefInput` now otherwise accepts.
588
- if (placementSpecFor(ref.type) === undefined) {
589
- throw new NotFoundError(`Task "${task.id}" prompt asset ref "${src.ref}" is not an AKM-placed asset — adapter-owned (opaque) prompt sources are not resolvable as task inputs yet.`, "ASSET_NOT_FOUND");
590
- }
591
- const assetPath = await resolveAssetPath(stashDir, ref.type, ref.name);
592
- return fs.readFileSync(assetPath, "utf8");
593
- }
594
577
  function renderPromptLog(input) {
595
578
  const lines = [];
596
579
  const dbLines = [];
597
- const header = `[akm task] task=${input.task.id} kind=prompt engine=${input.engineName}`;
580
+ const header = `[akm task] task=${input.task.taskId} kind=prompt engine=${input.engineName}`;
598
581
  const summary = `ok=${input.result.ok} exit_code=${input.result.exitCode ?? "null"} duration_ms=${input.result.durationMs}`;
599
582
  lines.push(header, summary);
600
583
  dbLines.push({ line: header }, { level: input.result.ok ? "info" : "error", line: summary });
601
- if (input.engineAnnouncement) {
602
- lines.push(input.engineAnnouncement);
603
- dbLines.push({ level: "warn", line: input.engineAnnouncement });
584
+ for (const warning of input.warnings ?? []) {
585
+ lines.push(warning);
586
+ dbLines.push({ level: "warn", line: warning });
587
+ }
588
+ for (const notice of input.notices ?? []) {
589
+ const line = `lowering_notice=${notice.code} adapter=${notice.adapter} field=${notice.field ?? ""} message=${notice.message}`;
590
+ lines.push(line);
591
+ dbLines.push({ level: notice.severity === "warning" ? "warn" : "info", line });
604
592
  }
605
593
  if (!input.result.ok) {
606
594
  const failure = `reason=${input.result.reason ?? ""} error=${input.result.error ?? ""}`;
@@ -698,10 +686,11 @@ function streamLines(text, stream, level) {
698
686
  * to "log it anyway with no redaction at all" — `redactCredentialPatterns`
699
687
  * still runs unconditionally in the caller.
700
688
  */
701
- function taskLogSensitiveValues(redactNames) {
689
+ function taskLogSensitiveValues(redactNames, environment) {
690
+ const env = { ...process.env, ...environment };
702
691
  try {
703
692
  return collectTaskLogSensitiveValues({
704
- env: process.env,
693
+ env,
705
694
  config: loadConfig(),
706
695
  declaredNames: redactNames,
707
696
  });
@@ -710,7 +699,7 @@ function taskLogSensitiveValues(redactNames) {
710
699
  rethrowIfTestIsolationError(error);
711
700
  // No config — the name heuristic and the task's own `redact:` list still apply.
712
701
  try {
713
- return collectTaskLogSensitiveValues({ env: process.env, declaredNames: redactNames });
702
+ return collectTaskLogSensitiveValues({ env, declaredNames: redactNames });
714
703
  }
715
704
  catch (fallbackError) {
716
705
  rethrowIfTestIsolationError(fallbackError);
@@ -718,6 +707,11 @@ function taskLogSensitiveValues(redactNames) {
718
707
  }
719
708
  }
720
709
  }
710
+ function scrubTaskOutput(task, text) {
711
+ const patterned = redactCredentialPatterns(text);
712
+ const sensitive = taskLogSensitiveValues(task.redact, task.environment);
713
+ return sensitive.length > 0 ? redactSensitiveText(patterned, sensitive) : patterned;
714
+ }
721
715
  function persistRunLog(input) {
722
716
  // Two arms, and both are needed. `redactCredentialPatterns` catches
723
717
  // credential SHAPES nobody listed; the exact-value pass catches configured
@@ -727,7 +721,7 @@ function persistRunLog(input) {
727
721
  // exact pass here — the one sink all three target kinds funnel through —
728
722
  // covers every arm once rather than per-arm; prompt/workflow runs already
729
723
  // scrub upstream, and redaction is idempotent, so the overlap is free.
730
- const sensitive = taskLogSensitiveValues(input.redactNames);
724
+ const sensitive = taskLogSensitiveValues(input.redactNames, input.environment);
731
725
  const scrub = (text) => sensitive.length > 0
732
726
  ? redactSensitiveText(redactCredentialPatterns(text), sensitive)
733
727
  : redactCredentialPatterns(text);
@@ -914,13 +908,7 @@ function taskHistoryRowToResult(row) {
914
908
  : row.target_kind === "command"
915
909
  ? { kind: "command" }
916
910
  : row.target_kind === "prompt"
917
- ? meta.metadataVersion === 1
918
- ? {
919
- kind: "prompt",
920
- engine: null,
921
- ...(meta.legacyProfile !== undefined ? { legacyProfile: meta.legacyProfile } : {}),
922
- }
923
- : { kind: "prompt", engine: meta.engine ?? null }
911
+ ? { kind: "prompt", engine: meta.engine ?? null }
924
912
  : { kind: "unknown" };
925
913
  return {
926
914
  id: row.task_id,