akm-cli 0.10.261012-beta → 0.10.261013-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/CHANGELOG.md +216 -0
  2. package/SECURITY.md +2 -2
  3. package/STABILITY.md +2 -2
  4. package/dist/assets/hints/cli-hints-full.md +6 -7
  5. package/dist/assets/hints/cli-hints-short.md +3 -4
  6. package/dist/commands/command/execution-source-loader.js +10 -14
  7. package/dist/commands/env/env-binding.js +47 -58
  8. package/dist/commands/env/env-cli.js +20 -22
  9. package/dist/commands/env/env.js +10 -58
  10. package/dist/commands/env/secret-cli.js +9 -27
  11. package/dist/commands/env/secret.js +3 -10
  12. package/dist/commands/health/scheduler-binary.js +1 -1
  13. package/dist/commands/improve/distill.js +0 -2
  14. package/dist/commands/improve/eligibility.js +0 -2
  15. package/dist/commands/lint/env-key-rules.js +7 -6
  16. package/dist/commands/lint/index.js +1 -1
  17. package/dist/commands/proposal/validators/proposal-validators.js +1 -4
  18. package/dist/commands/read/show.js +4 -25
  19. package/dist/commands/sources/add-cli.js +8 -63
  20. package/dist/commands/sources/bundle-cli.js +8 -9
  21. package/dist/commands/sources/installed-stashes.js +117 -61
  22. package/dist/commands/sources/schema-repair.js +0 -2
  23. package/dist/commands/sources/source-clone.js +31 -7
  24. package/dist/commands/tasks/explain.js +1 -1
  25. package/dist/commands/tasks/tasks.js +38 -56
  26. package/dist/commands/ui/redact.js +4 -4
  27. package/dist/commands/ui/workspace-actions.js +1 -1
  28. package/dist/commands/workflow/plan.js +47 -8
  29. package/dist/commands/workflow-cli.js +37 -41
  30. package/dist/core/adapter/adapters/akm-adapter.js +3 -3
  31. package/dist/core/adapter/adapters/akm-lint.js +3 -4
  32. package/dist/core/adapter/adapters/akm-metadata.js +2 -2
  33. package/dist/core/adapter/adapters/akm-task-adapter.js +5 -6
  34. package/dist/core/adapter/adapters/dotenv-adapter.js +4 -3
  35. package/dist/core/asset/resolve-ref.js +5 -0
  36. package/dist/core/bundle-rename.js +12 -4
  37. package/dist/core/common.js +4 -52
  38. package/dist/core/config/config-walker.js +1 -1
  39. package/dist/core/config/config.js +55 -18
  40. package/dist/core/config/schema/primitives.js +12 -6
  41. package/dist/core/config/schema/sources-bundles.js +2 -2
  42. package/dist/core/env-secret-ref.js +84 -73
  43. package/dist/core/errors.js +1 -1
  44. package/dist/core/paths.js +1 -6
  45. package/dist/core/redaction.js +16 -3
  46. package/dist/core/ref-token.js +82 -0
  47. package/dist/core/secret-value.js +14 -0
  48. package/dist/core/write-source.js +20 -4
  49. package/dist/indexer/indexer.js +27 -36
  50. package/dist/indexer/installations.js +1 -8
  51. package/dist/indexer/lookup/adapter-concept-owner.js +40 -14
  52. package/dist/indexer/materialize-embeddings.js +1 -1
  53. package/dist/indexer/scan/drain-dir.js +3 -3
  54. package/dist/indexer/search/search-source.js +4 -2
  55. package/dist/indexer/walk/path-resolver.js +13 -68
  56. package/dist/indexer/walk/walker.js +18 -22
  57. package/dist/integrations/agent/engine-resolution.js +9 -7
  58. package/dist/integrations/agent/runner-dispatch.js +4 -5
  59. package/dist/integrations/agent/runner.js +2 -0
  60. package/dist/llm/client.js +1 -1
  61. package/dist/llm/embedders/remote.js +4 -3
  62. package/dist/output/renderers.js +3 -4
  63. package/dist/output/shapes/helpers.js +0 -2
  64. package/dist/output/text/command-format.js +12 -2
  65. package/dist/output/text/show-directives.js +16 -28
  66. package/dist/output/text/workflow-format.js +37 -7
  67. package/dist/scripts/akm-migrate-node.js +45865 -44692
  68. package/dist/scripts/akm-migrate.js +45804 -44630
  69. package/dist/setup/steps/tasks.js +1 -2
  70. package/dist/sources/providers/provider-utils.js +12 -0
  71. package/dist/sources/resolve.js +12 -16
  72. package/dist/sources/snapshot-fetchers/secret-seam.js +2 -4
  73. package/dist/sources/snapshot-fetchers/x.js +1 -1
  74. package/dist/storage/repositories/proposals-repository.js +2 -2
  75. package/dist/storage/repositories/task-history-repository.js +2 -2
  76. package/dist/storage/repositories/workflow-runs-repository.js +38 -116
  77. package/dist/tasks/embedded.js +1 -1
  78. package/dist/tasks/prepare/prepare-support.js +3 -2
  79. package/dist/tasks/prepare/prepare.js +3 -4
  80. package/dist/tasks/prepare/script-capture.js +8 -22
  81. package/dist/tasks/run/load-task.js +46 -14
  82. package/dist/tasks/run/run-native-task.js +0 -8
  83. package/dist/tasks/run/run-workflow-task.js +2 -2
  84. package/dist/tasks/scheduler-sync.js +0 -1
  85. package/dist/tasks/source/bounded-document.js +0 -41
  86. package/dist/tasks/source/parse-task-source.js +0 -1
  87. package/dist/tasks/source/task-source-v4.js +1 -4
  88. package/dist/workflows/compile.js +1 -15
  89. package/dist/workflows/exec/child-workflow.js +1 -2
  90. package/dist/workflows/exec/dispatch-redaction.js +24 -6
  91. package/dist/workflows/exec/environment.js +11 -74
  92. package/dist/workflows/exec/exec-unit.js +31 -23
  93. package/dist/workflows/exec/frozen-judge.js +17 -3
  94. package/dist/workflows/exec/native-executor.js +70 -26
  95. package/dist/workflows/exec/run-workflow.js +31 -18
  96. package/dist/workflows/exec/step-work.js +4 -3
  97. package/dist/workflows/exec/unit-dispatch.js +1 -8
  98. package/dist/workflows/freeze/environment.js +14 -119
  99. package/dist/workflows/freeze/freeze.js +0 -1
  100. package/dist/workflows/freeze/step-values.js +1 -2
  101. package/dist/workflows/freeze/targets/child-workflow.js +3 -5
  102. package/dist/workflows/freeze/targets/command.js +6 -9
  103. package/dist/workflows/freeze/targets/shell.js +0 -3
  104. package/dist/workflows/parser.js +6 -25
  105. package/dist/workflows/plan.js +6 -2
  106. package/dist/workflows/renderer.js +77 -4
  107. package/dist/workflows/runtime/run-plan.js +37 -23
  108. package/dist/workflows/runtime/runs.js +41 -84
  109. package/dist/workflows/runtime/workflow-asset-loader.js +55 -86
  110. package/dist/workflows/source-files.js +7 -19
  111. package/docs/architecture/0.10-process-rules.md +6 -6
  112. package/docs/architecture/architecture.md +6 -6
  113. package/docs/architecture/comparisons/claude-code-vs-akm-workflows-full.md +12 -18
  114. package/docs/architecture/decisions/0003-child-env-allowlist-and-provenance.md +6 -8
  115. package/docs/architecture/internals/storage-locations.md +4 -4
  116. package/docs/architecture/persisted-data-compat.md +3 -2
  117. package/docs/architecture/specs/0.9.0-public-api-issue-backlog.md +1 -1
  118. package/docs/architecture/specs/akm-format-neutral-bundle-workspace-spec.md +3 -3
  119. package/docs/architecture/testing/manual-testing-checklist.md +46 -78
  120. package/docs/architecture/testing/task-workflow-manual-tests.md +22 -40
  121. package/docs/architecture/testing/testing-workflow.md +9 -11
  122. package/docs/architecture/workflow-engine.md +41 -27
  123. package/docs/guides/author-bundles.md +14 -17
  124. package/docs/guides/author-workflows.md +43 -9
  125. package/docs/guides/bundles.md +16 -11
  126. package/docs/guides/claude-code-vs-akm-workflows.md +1 -1
  127. package/docs/guides/run-workflows.md +25 -13
  128. package/docs/guides/scheduling.md +1 -1
  129. package/docs/maintainers/README.md +2 -3
  130. package/docs/maintainers/release-checklist.md +73 -126
  131. package/docs/migration/v0.9-to-v0.10.md +5 -1
  132. package/docs/reference/cli.md +124 -112
  133. package/docs/reference/configuration.md +16 -9
  134. package/docs/reference/data-and-telemetry.md +1 -1
  135. package/docs/reference/env-and-secrets.md +66 -15
  136. package/docs/reference/registry.md +6 -11
  137. package/docs/reference/tasks.md +11 -3
  138. package/docs/reference/ui-api.md +3 -3
  139. package/docs/reference/workflow-schema.md +109 -36
  140. package/package.json +1 -1
  141. package/schemas/akm-task.json +2 -4
  142. package/dist/commands/sources/dangerous-env-audit.js +0 -134
  143. package/dist/core/activation-policy.js +0 -67
  144. package/dist/execution/directory-identity.js +0 -78
  145. package/dist/workflows/authoring/scope-key.js +0 -79
  146. package/dist/workflows/source-semantics.js +0 -110
@@ -11,7 +11,6 @@
11
11
  import fs from "node:fs";
12
12
  import path from "node:path";
13
13
  import { isRelevantAssetFile } from "../../core/asset/asset-placement.js";
14
- import { WORKFLOW_EXTENSIONS } from "../../core/recognition-util.js";
15
14
  import { spawnSync } from "../../runtime.js";
16
15
  import { buildFileContext } from "./file-context.js";
17
16
  const ALWAYS_SKIP_DIRS = new Set([".git"]);
@@ -124,13 +123,11 @@ function walkStashGit(stashRoot, options) {
124
123
  for (const relFile of files) {
125
124
  const absPath = path.join(stashRoot, relFile);
126
125
  try {
127
- // lstat, not stat: a tracked symlink must not be dereferenced. statSync
128
- // follows the link, so a target outside stashRoot would be read and
129
- // indexed. The manual walk below already skips symlinks for exactly this
130
- // reason; the two walkers have to agree regardless of whether the stash
131
- // happens to sit inside a git repo.
126
+ // A file that is a symlink to a file anywhere is indexed like any other
127
+ // file: installing a bundle is the trust decision. git lists a symlinked
128
+ // directory as one entry, so directory symlinks are never descended.
132
129
  const stat = fs.lstatSync(absPath);
133
- if (stat.isFile() || (stat.isSymbolicLink() && isOwnedWorkflowSymlink(stashRoot, absPath, options))) {
130
+ if (stat.isFile() || (stat.isSymbolicLink() && isFileOrFileSymlink(absPath))) {
134
131
  results.push(buildFileContext(stashRoot, absPath));
135
132
  }
136
133
  }
@@ -191,8 +188,11 @@ export function walkMarkdownFiles(root) {
191
188
  }
192
189
  for (const entry of entries) {
193
190
  const full = path.join(current, entry.name);
194
- if (entry.isSymbolicLink())
191
+ if (entry.isSymbolicLink()) {
192
+ if (entry.name.toLowerCase().endsWith(".md") && isFileOrFileSymlink(full))
193
+ files.push(full);
195
194
  continue;
195
+ }
196
196
  if (entry.isDirectory()) {
197
197
  stack.push(full);
198
198
  }
@@ -225,11 +225,10 @@ function walkStashManual(stashRoot, options) {
225
225
  continue;
226
226
  const fullPath = path.join(current, entry.name);
227
227
  if (entry.isSymbolicLink()) {
228
- // Native workflow components arbitrate file symlinks explicitly in the
229
- // drain before reading them. Directory symlinks remain unfollowed.
230
- if (isOwnedWorkflowSymlink(stashRoot, fullPath, options)) {
228
+ // A file symlink is indexed like any other file; a directory symlink
229
+ // is never descended (no cycles).
230
+ if (isFileOrFileSymlink(fullPath))
231
231
  results.push(buildFileContext(stashRoot, fullPath));
232
- }
233
232
  continue;
234
233
  }
235
234
  if (entry.isDirectory()) {
@@ -245,15 +244,12 @@ function walkStashManual(stashRoot, options) {
245
244
  }
246
245
  return { files: results, complete };
247
246
  }
248
- function isOwnedWorkflowSymlink(stashRoot, candidatePath, options) {
249
- const adapterId = options.workflowSymlinkAdapter;
250
- if (!adapterId)
251
- return false;
252
- const relativePath = path.relative(path.resolve(stashRoot), path.resolve(candidatePath)).replaceAll("\\", "/");
253
- if (!relativePath || relativePath.startsWith("../") || path.posix.isAbsolute(relativePath))
254
- return false;
255
- if (adapterId === "akm" && !relativePath.startsWith("workflows/"))
247
+ /** True for a regular file, or a symlink whose target is a regular file. */
248
+ export function isFileOrFileSymlink(filePath) {
249
+ try {
250
+ return fs.statSync(filePath).isFile();
251
+ }
252
+ catch {
256
253
  return false;
257
- const extension = path.posix.extname(relativePath).toLowerCase();
258
- return WORKFLOW_EXTENSIONS.includes(extension);
254
+ }
259
255
  }
@@ -4,12 +4,12 @@
4
4
  import fs from "node:fs";
5
5
  import os from "node:os";
6
6
  import path from "node:path";
7
- import { resolveSecret } from "../../core/config/config.js";
7
+ import { liftRetiredSecretRef, resolveSecret } from "../../core/config/config.js";
8
8
  import { deepMergeConfig } from "../../core/config/deep-merge.js";
9
- import { SECRET_STORE_REFERENCE_PATTERN } from "../../core/config/schema/primitives.js";
10
9
  import { ConfigError } from "../../core/errors.js";
11
10
  import { formatExtraParamsIssue, validateExtraParams } from "../../core/extra-params.js";
12
11
  import { collectSensitiveValues } from "../../core/redaction.js";
12
+ import { isRefToken } from "../../core/ref-token.js";
13
13
  import { resolveSecretFromStore } from "../../sources/snapshot-fetchers/secret-seam.js";
14
14
  import { getHarness } from "../harnesses/index.js";
15
15
  import { resolveOpencodeBin } from "../harnesses/opencode/version.js";
@@ -68,7 +68,7 @@ export function lookupApiKeyFileValue(filePath) {
68
68
  /** Best-effort, non-throwing read of a secret-store credential for redaction and health. */
69
69
  export function lookupApiKeySecretRefValue(ref) {
70
70
  try {
71
- return resolveSecret(ref, resolveSecretFromStore);
71
+ return resolveSecret(liftRetiredSecretRef(ref) ?? ref, resolveSecretFromStore);
72
72
  }
73
73
  catch {
74
74
  return undefined;
@@ -95,7 +95,7 @@ function resolveCredential(name, engine, config) {
95
95
  if (explicit)
96
96
  return { names: [explicit], required: true };
97
97
  // A secret-store reference is carried as `apiKeySecretRef` instead.
98
- if (SECRET_STORE_REFERENCE_PATTERN.test(engine.apiKey))
98
+ if (isRefToken(engine.apiKey))
99
99
  return undefined;
100
100
  throw new ConfigError(`Engine "${name}" has an invalid symbolic apiKey reference.`, "INVALID_CONFIG_FILE");
101
101
  }
@@ -138,7 +138,9 @@ export function resolveLlmCredentialValue(engine, credential, apiKeyFile, apiKey
138
138
  return envValue;
139
139
  if (apiKeyFile !== undefined)
140
140
  return readApiKeyFile(engine, apiKeyFile);
141
- return apiKeySecretRef !== undefined ? resolveSecret(apiKeySecretRef, resolveSecretFromStore) : undefined;
141
+ return apiKeySecretRef !== undefined
142
+ ? resolveSecret(liftRetiredSecretRef(apiKeySecretRef) ?? apiKeySecretRef, resolveSecretFromStore)
143
+ : undefined;
142
144
  }
143
145
  /**
144
146
  * Non-throwing credential-presence check for `akm health` and improve's
@@ -193,7 +195,7 @@ export function collectEngineCredentialValues(config, envSource = process.env) {
193
195
  if (value)
194
196
  values.add(value);
195
197
  }
196
- if (engine.apiKey !== undefined && SECRET_STORE_REFERENCE_PATTERN.test(engine.apiKey)) {
198
+ if (engine.apiKey !== undefined && isRefToken(engine.apiKey)) {
197
199
  const value = lookupApiKeySecretRefValue(engine.apiKey);
198
200
  if (value)
199
201
  values.add(value);
@@ -237,7 +239,7 @@ export function resolveLlmEngineUse(config, layers) {
237
239
  if (connection[key] === undefined)
238
240
  delete connection[key];
239
241
  }
240
- const apiKeySecretRef = engine.apiKey !== undefined && SECRET_STORE_REFERENCE_PATTERN.test(engine.apiKey) ? engine.apiKey : undefined;
242
+ const apiKeySecretRef = engine.apiKey !== undefined && isRefToken(engine.apiKey) ? engine.apiKey : undefined;
241
243
  return {
242
244
  engine: name,
243
245
  connection: connection,
@@ -33,7 +33,10 @@ const OPERATIONAL_OPTIONS = [
33
33
  "clearTimeoutFn",
34
34
  "onEvent",
35
35
  ];
36
- /** Every value that can reach one dispatch and must never be echoed back: credentials and secret-looking env. */
36
+ /**
37
+ * Every value that can reach one dispatch and must never be echoed back: credentials and unsafe passthrough env.
38
+ * `opts.env` is configuration; a caller's secret values are redacted through its own sensitive set.
39
+ */
37
40
  export function collectDispatchSensitiveValues(runner, opts, envSource = opts.envSource ?? process.env) {
38
41
  const values = [];
39
42
  if (runner.kind === "llm") {
@@ -49,10 +52,6 @@ export function collectDispatchSensitiveValues(runner, opts, envSource = opts.en
49
52
  values.push(envSource[name]);
50
53
  }
51
54
  }
52
- for (const [name, value] of Object.entries(opts.env ?? {})) {
53
- if (!isEnvPassthroughValueSafeToExpose(name, value))
54
- values.push(value);
55
- }
56
55
  return collectSensitiveValues(values);
57
56
  }
58
57
  function redactResult(result, sensitiveValues) {
@@ -41,6 +41,8 @@ function isRecord(value) {
41
41
  /**
42
42
  * Read a journaled runner back. Tolerant: unknown keys are kept and missing
43
43
  * profile defaults are filled in; only a shape nothing could dispatch is refused.
44
+ * Credentials stay as stored (a 0.9.x plan's `secret://<name>` included) because the
45
+ * unit input hash covers them; they are read as `${ref}` where they are resolved.
44
46
  */
45
47
  export function decodeFrozenRunnerSpec(value) {
46
48
  if (!isRecord(value) || typeof value.engine !== "string") {
@@ -359,7 +359,7 @@ async function chatCompletionAttemptOnce(config, messages, options, timeoutMs, i
359
359
  throw new Error(formatExtraParamsIssue("LLM extraParams", issue));
360
360
  }
361
361
  const headers = { "Content-Type": "application/json" };
362
- // Resolve ONLY a whole-string reference ($VAR/${VAR} or secret://<name>).
362
+ // Resolve ONLY a whole-string reference ($VAR/${VAR} or ${ref}).
363
363
  // The execution boundary normally hands us a materialized credential after
364
364
  // resolving the reference upstream, so re-running the substitution over a
365
365
  // literal key mangled any credential containing `$` — `sk-live$ecret` lost
@@ -10,9 +10,10 @@
10
10
  import { abortableDelay, backoffDelay, fetchWithTimeout, isHttpUrl, readBodyWithByteCap } from "../../core/common.js";
11
11
  import { concurrentMap } from "../../core/concurrent.js";
12
12
  import { resolveSecret } from "../../core/config/config.js";
13
- import { ENV_REFERENCE_PATTERN, SECRET_STORE_REFERENCE_PATTERN } from "../../core/config/schema/primitives.js";
13
+ import { ENV_REFERENCE_PATTERN } from "../../core/config/schema/primitives.js";
14
14
  import { defaultConcurrencyForEndpoint } from "../../core/loopback.js";
15
15
  import { redactErrorBody, redactSensitiveText } from "../../core/redaction.js";
16
+ import { isRefToken } from "../../core/ref-token.js";
16
17
  import { warnVerbose } from "../../core/warn.js";
17
18
  import { resolveSecretFromStore } from "../../sources/snapshot-fetchers/secret-seam.js";
18
19
  /**
@@ -845,7 +846,7 @@ export function hasRemoteEndpoint(config) {
845
846
  /**
846
847
  * Describe WHERE an `embedding.apiKey` came from, never its value — the
847
848
  * actionable outcome of the #953 field gap: `resolveSecret` throws on an
848
- * unresolvable `secret://` reference, so a keyless request can only mean
849
+ * unresolvable `${ref}` reference, so a keyless request can only mean
849
850
  * `embedding.apiKey` was absent from the config the run actually loaded (a
850
851
  * different config root, scope, or a config edited after the run started).
851
852
  * A default-level progress line naming the credential's SOURCE (this
@@ -855,7 +856,7 @@ export function hasRemoteEndpoint(config) {
855
856
  export function describeEmbeddingCredential(apiKey) {
856
857
  if (!apiKey)
857
858
  return "none configured";
858
- if (SECRET_STORE_REFERENCE_PATTERN.test(apiKey))
859
+ if (isRefToken(apiKey))
859
860
  return `${apiKey} (store)`;
860
861
  if (ENV_REFERENCE_PATTERN.test(apiKey))
861
862
  return `${apiKey} (env)`;
@@ -213,10 +213,9 @@ const agentMdRenderer = {
213
213
  action: "Dispatch using the prompt below verbatim. Use modelHint and toolPolicy if present.",
214
214
  description: asNonEmptyString(parsedMd.data.description),
215
215
  prompt: parsedMd.content,
216
- // `tools` is self-declared frontmatter. The provenance CEILING that decides
217
- // whether this self-declared policy is honoured is applied at the show
218
- // layer (`akmShowUnified`), which knows whether the source is the operator's
219
- // own writable stash vs a read-only third-party source (07 P1-D).
216
+ // `tools` is self-declared frontmatter and is passed through as-is. The
217
+ // host's `execution.allowedTools` caps it at dispatch (agent execution),
218
+ // so `show` does not filter it by source.
220
219
  ...(toolPolicy !== undefined ? { toolPolicy } : {}),
221
220
  ...(modelHint !== undefined ? { modelHint } : {}),
222
221
  };
@@ -376,7 +376,6 @@ export function shapeShowOutput(result, detail, shape = "human") {
376
376
  "run",
377
377
  "setup",
378
378
  "cwd",
379
- "activeRun",
380
379
  "toolPolicy",
381
380
  "modelHint",
382
381
  "agent",
@@ -418,7 +417,6 @@ export function shapeShowOutput(result, detail, shape = "human") {
418
417
  "run",
419
418
  "setup",
420
419
  "cwd",
421
- "activeRun",
422
420
  "keys",
423
421
  "links",
424
422
  "evidence",
@@ -177,7 +177,7 @@ export function formatEnvListPlain(r) {
177
177
  // Multi-env listing: { envs: [{ ref, path, keys }, ...] }
178
178
  const envs = Array.isArray(r.envs) ? r.envs : [];
179
179
  if (envs.length === 0) {
180
- return "No env files. Create one with `akm env create <name>`, then edit the .env file directly.";
180
+ return "No env files. Create one with `akm env create env/<name>`, then edit the .env file directly.";
181
181
  }
182
182
  const lines = [];
183
183
  for (const v of envs) {
@@ -580,14 +580,24 @@ export function formatBundleRenamePlain(r) {
580
580
  const proposalRefs = Number(state.proposalRefs ?? 0);
581
581
  const proposalTargets = Number(state.proposalTargets ?? 0);
582
582
  const taskHistoryRefs = Number(state.taskHistoryRefs ?? 0);
583
+ const workflowRunRefs = Number(state.workflowRunRefs ?? 0);
584
+ const activeWorkflowRuns = Array.isArray(r.activeWorkflowRuns)
585
+ ? r.activeWorkflowRuns
586
+ : [];
583
587
  const entries = Number(index.entries ?? 0);
584
588
  const contentRefs = Array.isArray(r.contentRefs) ? r.contentRefs : [];
585
589
  const nativeSchedulerRows = Array.isArray(r.nativeSchedulerRows) ? r.nativeSchedulerRows : [];
586
590
  const verb = applied ? "Renamed" : "Would rename";
587
591
  const lines = [
588
592
  `${verb} bundle "${oldId}" to "${newId}" (${entries} index entries, ${schedulerRefs} scheduler ref(s), ` +
589
- `${proposalRefs + proposalTargets} proposal reference(s), ${taskHistoryRefs} task-history row(s)).`,
593
+ `${proposalRefs + proposalTargets} proposal reference(s), ${taskHistoryRefs} task-history row(s), ` +
594
+ `${workflowRunRefs} workflow run(s)).`,
590
595
  ];
596
+ if (activeWorkflowRuns.length > 0) {
597
+ lines.push("Active and blocked workflow runs keep the old bundle name in their frozen plan; continue them (`akm workflow run <id>`, after `akm workflow resume <id>` for a blocked one) or abandon them (`akm workflow abandon <id>`):");
598
+ for (const run of activeWorkflowRuns)
599
+ lines.push(` - ${run.id} (${run.workflowRef}, ${run.status})`);
600
+ }
591
601
  if (!applied) {
592
602
  lines.push("Re-run without --dry-run to apply. It also re-syncs native scheduler bindings under the new name.");
593
603
  if (nativeSchedulerRows.length > 0) {
@@ -43,36 +43,24 @@ export function appendShowDirectives(lines, r) {
43
43
  lines.push(` - If this asset does not contain what you need, run \`akm feedback '${assetRef ?? "<ref>"}' --negative\` and search for a different asset.`);
44
44
  }
45
45
  if (assetType === "skill" || assetType === "knowledge") {
46
- const activeRun = r.activeRun;
47
- if (activeRun) {
48
- lines.unshift("Read this reference, follow the active unit's instructions, and return the result to the workflow runner. Step completion is automatic.");
49
- lines.unshift(`WORKFLOW ACTIVE — schema shown as reference (run: ${activeRun.runId})`);
50
- lines.unshift("---");
51
- lines.unshift("");
52
- // Still show feedback line at the end but skip the APPLY directive
53
- lines.push("");
54
- lines.push(`Run \`akm feedback ${assetRef ? `'${assetRef}'` : "<ref>"} --positive\` if the step succeeds, or \`--negative\` if this schema did not help.`);
46
+ // Show the APPLY directive. Branch on whether this
47
+ // skill primarily teaches CLI commands (shell output) vs YAML schema.
48
+ const preApplyLines = [...lines];
49
+ lines.push("");
50
+ lines.push("---");
51
+ if (isCommandOutputSkill(preApplyLines)) {
52
+ lines.push("APPLY (only if no workflow step is required for this task):");
53
+ lines.push(" 1. Identify the output file from README.md (typically commands.txt).");
54
+ lines.push(" 2. Write the exact command syntax from the code blocks above — replace every placeholder (`<name>`, `<value>`) with a real, concrete value from your task context. Do not write placeholder text.");
55
+ lines.push(" 3. Each command should be on a single line (no backslash line continuation unless the verifier expects it).");
56
+ lines.push(`Run \`akm feedback ${assetRef ? `'${assetRef}'` : "<ref>"} --positive\` after the task succeeds, or \`--negative\` if this reference did not contain the needed command syntax.`);
55
57
  }
56
58
  else {
57
- // No active workflow: show the APPLY directive. Branch on whether this
58
- // skill primarily teaches CLI commands (shell output) vs YAML schema.
59
- const preApplyLines = [...lines];
60
- lines.push("");
61
- lines.push("---");
62
- if (isCommandOutputSkill(preApplyLines)) {
63
- lines.push("APPLY (only if no workflow step is required for this task):");
64
- lines.push(" 1. Identify the output file from README.md (typically commands.txt).");
65
- lines.push(" 2. Write the exact command syntax from the code blocks above — replace every placeholder (`<name>`, `<value>`) with a real, concrete value from your task context. Do not write placeholder text.");
66
- lines.push(" 3. Each command should be on a single line (no backslash line continuation unless the verifier expects it).");
67
- lines.push(`Run \`akm feedback ${assetRef ? `'${assetRef}'` : "<ref>"} --positive\` after the task succeeds, or \`--negative\` if this reference did not contain the needed command syntax.`);
68
- }
69
- else {
70
- lines.push("APPLY (only if no workflow step is required for this task):");
71
- lines.push(" 1. Identify the target file from README.md — write or edit it. If the file does not yet exist, CREATE it with the full structure from this schema.");
72
- lines.push(" 2. Add/edit the fields shown above using the exact field names from this schema.");
73
- lines.push(" 3. COPY the exact YAML structure and field names from the code blocks above — do not substitute synonyms or invent nesting. Replace every placeholder value with a real, concrete value from your task context. Do not leave any field as null, empty, or a placeholder.");
74
- lines.push(`Run \`akm feedback ${assetRef ? `'${assetRef}'` : "<ref>"} --positive\` after the task succeeds, or \`--negative\` if the task fails after following this guidance.`);
75
- }
59
+ lines.push("APPLY (only if no workflow step is required for this task):");
60
+ lines.push(" 1. Identify the target file from README.md — write or edit it. If the file does not yet exist, CREATE it with the full structure from this schema.");
61
+ lines.push(" 2. Add/edit the fields shown above using the exact field names from this schema.");
62
+ lines.push(" 3. COPY the exact YAML structure and field names from the code blocks above — do not substitute synonyms or invent nesting. Replace every placeholder value with a real, concrete value from your task context. Do not leave any field as null, empty, or a placeholder.");
63
+ lines.push(`Run \`akm feedback ${assetRef ? `'${assetRef}'` : "<ref>"} --positive\` after the task succeeds, or \`--negative\` if the task fails after following this guidance.`);
76
64
  }
77
65
  }
78
66
  else if (assetType === "workflow") {
@@ -12,7 +12,7 @@
12
12
  export function formatWorkflowListPlain(result) {
13
13
  const runs = Array.isArray(result.runs) ? result.runs : [];
14
14
  if (runs.length === 0) {
15
- return "No workflow runs in the current working scope. Start one with `akm workflow run workflows/<name>` or author one with `akm workflow create <name>`.";
15
+ return "No workflow runs. Start one with `akm workflow run workflows/<name>` or author one with `akm workflow create <name>`.";
16
16
  }
17
17
  return runs
18
18
  .map((run) => {
@@ -56,6 +56,40 @@ function renderChildNode(child, depth, lines) {
56
56
  for (const grandchild of nested)
57
57
  renderChildNode(grandchild, depth + 1, lines);
58
58
  }
59
+ /**
60
+ * The `show` orchestration projection as text lines: the exec argv (JSON, so
61
+ * word boundaries survive), cwd, env refs and the bundle-qualified ref each
62
+ * token resolved to. Shared by `workflow plan` steps and `status --units`.
63
+ */
64
+ function renderOrchestrationLines(value, prefix) {
65
+ if (typeof value !== "object" || value === null)
66
+ return [];
67
+ const orchestration = value;
68
+ const lines = [];
69
+ const exec = typeof orchestration.exec === "object" && orchestration.exec !== null
70
+ ? orchestration.exec
71
+ : undefined;
72
+ if (exec) {
73
+ if (Array.isArray(exec.command))
74
+ lines.push(`${prefix}argv: ${JSON.stringify(exec.command)}`);
75
+ if (typeof exec.cwd === "string")
76
+ lines.push(`${prefix}cwd: ${exec.cwd}`);
77
+ }
78
+ if (Array.isArray(orchestration.env) && orchestration.env.length > 0) {
79
+ lines.push(`${prefix}env: ${orchestration.env.join(", ")}`);
80
+ }
81
+ const refs = Array.isArray(orchestration.refs) ? orchestration.refs : [];
82
+ for (const ref of refs) {
83
+ const written = ref.in === "argv" || ref.in === "cwd" ? `\${${String(ref.ref)}}` : String(ref.ref);
84
+ lines.push(`${prefix}ref: ${written} -> ${String(ref.resolved)} (${String(ref.in)})`);
85
+ }
86
+ const unresolved = Array.isArray(orchestration.unresolved)
87
+ ? orchestration.unresolved
88
+ : [];
89
+ for (const ref of unresolved)
90
+ lines.push(`${prefix}ref: ${String(ref.ref)} -> not found (${String(ref.in)})`);
91
+ return lines;
92
+ }
59
93
  export function formatWorkflowStatusPlain(result) {
60
94
  const run = typeof result.run === "object" && result.run !== null ? result.run : undefined;
61
95
  const workflow = typeof result.workflow === "object" && result.workflow !== null
@@ -111,6 +145,7 @@ export function formatWorkflowStatusPlain(result) {
111
145
  if (typeof unit.failureReason === "string" && unit.failureReason.trim()) {
112
146
  lines.push(` failure_reason: ${unit.failureReason}`);
113
147
  }
148
+ lines.push(...renderOrchestrationLines(unit.orchestration, " "));
114
149
  if (typeof unit.diagnostic === "string" && unit.diagnostic.trim()) {
115
150
  const diagLines = unit.diagnostic.split("\n");
116
151
  lines.push(` diagnostic: ${diagLines[0]}`);
@@ -147,12 +182,6 @@ export function formatWorkflowRunPlain(result) {
147
182
  if (!run)
148
183
  return null;
149
184
  const lines = [];
150
- // #919: `workflow run <ref>` silently resuming an existing active run (the
151
- // #485 concurrency guard) is now announced up front, with the escape hatch.
152
- if (result.resumed === true) {
153
- lines.push(`resuming existing run ${String(run.id ?? "unknown")} for ${String(run.workflowRef ?? "this ref")}; ` +
154
- `pass --new to start a fresh run`);
155
- }
156
185
  lines.push(`run: ${String(run.id ?? "unknown")}`, `status: ${String(run.status ?? "unknown")}`);
157
186
  // Creation-time notices (e.g. the implicit engine fallback) must survive the
158
187
  // text renderer: JSON/YAML pass them through, and dropping them here would
@@ -252,6 +281,7 @@ function renderPlanStepLines(step, ordinal, lines, prefix = " ") {
252
281
  if (inputBindings.length > 0) {
253
282
  lines.push(`${detailPrefix}with: ${formatInputBindingsList(inputBindings)}`);
254
283
  }
284
+ lines.push(...renderOrchestrationLines(step.orchestration, detailPrefix));
255
285
  if (via === "child") {
256
286
  const childSteps = Array.isArray(expansion.steps) ? expansion.steps : [];
257
287
  for (const [index, childStep] of childSteps.entries()) {