akm-cli 0.9.0-rc.13 → 0.9.0-rc.15
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 +162 -23
- package/dist/akm-migrate +3 -1
- package/dist/assets/hints/cli-hints-full.md +3 -4
- package/dist/assets/hints/cli-hints-short.md +5 -5
- package/dist/assets/workflows/workflow-template.md +4 -3
- package/dist/cli/invocation.js +3 -2
- package/dist/cli/retired-commands.js +5 -0
- package/dist/cli/unknown-flags.js +226 -0
- package/dist/cli.js +19 -35
- package/dist/commands/agent/agent-dispatch.js +7 -3
- package/dist/commands/agent/contribute-cli.js +15 -3
- package/dist/commands/feedback-cli.js +6 -3
- package/dist/commands/health/checks.js +6 -3
- package/dist/commands/improve/collapse-detector.js +2 -3
- package/dist/commands/improve/reflect.js +5 -3
- package/dist/commands/lint/base-linter.js +4 -16
- package/dist/commands/lint/index.js +13 -13
- package/dist/commands/log.js +6 -1
- package/dist/commands/migration-tool.js +4 -5
- package/dist/commands/observability-cli.js +1 -1
- package/dist/commands/proposal/propose.js +5 -3
- package/dist/commands/proposal/repository.js +5 -5
- package/dist/commands/read/knowledge.js +2 -0
- package/dist/commands/registry-cli.js +5 -3
- package/dist/commands/sources/add-cli.js +6 -6
- package/dist/commands/sources/init.js +1 -1
- package/dist/commands/sources/self-update.js +30 -7
- package/dist/commands/sources/source-add.js +17 -2
- package/dist/commands/tasks/tasks.js +10 -10
- package/dist/commands/workflow-cli.js +133 -259
- package/dist/core/adapter/adapters/akm-lint.js +17 -13
- package/dist/core/adapter/adapters/akm-task-adapter.js +14 -11
- package/dist/core/asset/akm-markdown.js +41 -8
- package/dist/core/asset/frontmatter.js +22 -0
- package/dist/core/asset/resolve-ref.js +23 -3
- package/dist/core/common.js +45 -2
- package/dist/core/config/config-schema.js +8 -0
- package/dist/core/config/schema/experimental.js +0 -17
- package/dist/core/config/schema/sources-bundles.js +11 -0
- package/dist/core/config/schema/workflow.js +3 -3
- package/dist/core/errors.js +5 -1
- package/dist/core/logs-db.js +2 -1
- package/dist/core/parse.js +4 -1
- package/dist/core/state/migrations.js +11 -14
- package/dist/core/state-db.js +4 -6
- package/dist/core/subprocess.js +6 -4
- package/dist/core/type-presentation.js +1 -1
- package/dist/indexer/indexer.js +16 -1
- package/dist/indexer/search/search-source.js +1 -1
- package/dist/indexer/walk/matchers.js +3 -1
- package/dist/integrations/agent/config.js +2 -2
- package/dist/integrations/agent/detect.js +49 -19
- package/dist/integrations/agent/engine-fallback.js +76 -0
- package/dist/integrations/agent/profiles.js +14 -0
- package/dist/integrations/agent/spawn.js +1 -2
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +13 -4
- package/dist/integrations/lockfile.js +11 -56
- package/dist/output/shapes/passthrough.js +0 -6
- package/dist/output/text/command-format.js +3 -3
- package/dist/output/text/helpers.js +1 -1
- package/dist/output/text/show-directives.js +4 -6
- package/dist/output/text/workflow-format.js +15 -181
- package/dist/output/text/workflow.js +1 -6
- package/dist/scripts/akm-migrate-node.js +58666 -0
- package/dist/scripts/akm-migrate.js +33976 -11498
- package/dist/setup/detect.js +42 -15
- package/dist/setup/setup.js +1 -1
- package/dist/sources/providers/git-stash.js +4 -2
- package/dist/sources/providers/website.js +5 -0
- package/dist/sources/snapshot-fetchers/bluesky.js +146 -0
- package/dist/sources/snapshot-fetchers/content-extract.js +370 -0
- package/dist/sources/snapshot-fetchers/fetcher-util.js +40 -0
- package/dist/sources/snapshot-fetchers/host-guard.js +199 -0
- package/dist/sources/snapshot-fetchers/registry.js +10 -1
- package/dist/sources/snapshot-fetchers/robots.js +348 -0
- package/dist/sources/snapshot-fetchers/rss.js +279 -0
- package/dist/sources/snapshot-fetchers/secret-seam.js +42 -0
- package/dist/sources/snapshot-fetchers/website-ingest.js +488 -257
- package/dist/sources/snapshot-fetchers/x.js +193 -0
- package/dist/storage/engines/sqlite-migrations.js +22 -107
- package/dist/storage/repositories/workflow-runs-repository.js +9 -23
- package/dist/tasks/runner.js +43 -19
- package/dist/tasks/schema.js +24 -1
- package/dist/tasks/validator.js +6 -2
- package/dist/workflows/exec/frozen-judge.js +28 -2
- package/dist/workflows/exec/native-executor.js +44 -28
- package/dist/workflows/exec/param-secrets.js +9 -9
- package/dist/workflows/exec/run-workflow.js +122 -60
- package/dist/workflows/exec/step-work.js +64 -175
- package/dist/workflows/exec/unit-dispatch.js +4 -0
- package/dist/workflows/ir/compile.js +2 -2
- package/dist/workflows/ir/freeze.js +29 -9
- package/dist/workflows/ir/params.js +135 -11
- package/dist/workflows/ir/plan-hash.js +1 -1
- package/dist/workflows/ir/schema.js +6 -2
- package/dist/workflows/renderer.js +2 -2
- package/dist/workflows/runtime/checkin.js +3 -3
- package/dist/workflows/runtime/runs.js +72 -92
- package/dist/workflows/runtime/unit-checkin.js +1 -1
- package/dist/workflows/runtime/unit-phases.js +2 -2
- package/dist/workflows/validate-summary.js +30 -14
- package/docs/migration/release-notes/0.9.0.md +31 -4
- package/docs/migration/v0.8-to-v0.9.md +70 -100
- package/docs/reference/data-and-telemetry.md +5 -5
- package/package.json +7 -3
- package/schemas/akm-config.json +24 -6
- package/schemas/akm-workflow.json +1 -1
- package/dist/workflows/cli.js +0 -33
- package/dist/workflows/exec/brief.js +0 -450
- package/dist/workflows/exec/report.js +0 -1460
- package/dist/workflows/exec/workflow-engine-gate.js +0 -67
|
@@ -188,7 +188,9 @@ function classifyBySmartMd(ctx) {
|
|
|
188
188
|
return { type: "workflow", specificity: 19 };
|
|
189
189
|
}
|
|
190
190
|
if (fm) {
|
|
191
|
-
|
|
191
|
+
// `tools` is the one authoring key for an agent's tool grant. Recognition
|
|
192
|
+
// covers only the key the renderer honors.
|
|
193
|
+
if ("tools" in fm) {
|
|
192
194
|
return { type: "agent", specificity: 20 };
|
|
193
195
|
}
|
|
194
196
|
if ("agent" in fm) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
-
/** Default
|
|
5
|
-
export const DEFAULT_AGENT_TIMEOUT_MS =
|
|
4
|
+
/** Default agent CLI timeout; null means agents run until they finish. */
|
|
5
|
+
export const DEFAULT_AGENT_TIMEOUT_MS = null;
|
|
6
6
|
/** Default hard timeout for direct LLM calls when no engine/use override exists. */
|
|
7
7
|
export const DEFAULT_LLM_TIMEOUT_MS = 600_000;
|
|
@@ -15,26 +15,34 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import fs from "node:fs";
|
|
17
17
|
import path from "node:path";
|
|
18
|
-
import { getBuiltinAgentProfile, listBuiltinAgentProfiles } from "./profiles.js";
|
|
18
|
+
import { getBuiltinAgentProfile, listBuiltinAgentProfiles, OPENCODE_SDK_SERVER_BIN, } from "./profiles.js";
|
|
19
19
|
/**
|
|
20
20
|
* Default PATH lookup. Walks `process.env.PATH` and returns the first
|
|
21
21
|
* existing executable file. Returns `undefined` when the bin is not on
|
|
22
22
|
* PATH or the env is empty.
|
|
23
23
|
*
|
|
24
|
-
* `process.env.PATH` is split on the platform-correct delimiter
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* match
|
|
24
|
+
* `process.env.PATH` is split on the platform-correct delimiter. On Windows
|
|
25
|
+
* agent CLIs install as extension-bearing shims (`claude.cmd`, `q.exe`), so
|
|
26
|
+
* each PATH entry is probed for the bare name AND for `<bin><ext>` over
|
|
27
|
+
* PATHEXT — an exact-match-only probe reports every agent CLI as missing
|
|
28
|
+
* there, which silently hides the "installed CLI agent" option during setup.
|
|
28
29
|
*/
|
|
29
30
|
export function defaultWhich(bin, envSource = process.env) {
|
|
31
|
+
// Computed once per call — not per PATH entry — since it never varies.
|
|
32
|
+
const suffixes = executableSuffixes(envSource);
|
|
30
33
|
if (!bin || bin.includes("/") || bin.includes("\\")) {
|
|
31
34
|
// Absolute / relative paths: caller already specified location.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
for (const suffix of suffixes) {
|
|
36
|
+
const candidate = bin + suffix;
|
|
37
|
+
try {
|
|
38
|
+
if (fs.statSync(candidate).isFile())
|
|
39
|
+
return candidate;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
/* try the next extension */
|
|
43
|
+
}
|
|
37
44
|
}
|
|
45
|
+
return undefined;
|
|
38
46
|
}
|
|
39
47
|
const pathVar = envSource.PATH ?? envSource.Path ?? envSource.path ?? "";
|
|
40
48
|
if (!pathVar)
|
|
@@ -43,18 +51,40 @@ export function defaultWhich(bin, envSource = process.env) {
|
|
|
43
51
|
for (const dir of pathVar.split(sep)) {
|
|
44
52
|
if (!dir)
|
|
45
53
|
continue;
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
const base = path.join(dir, bin);
|
|
55
|
+
for (const suffix of suffixes) {
|
|
56
|
+
const candidate = base + suffix;
|
|
57
|
+
try {
|
|
58
|
+
const st = fs.statSync(candidate);
|
|
59
|
+
if (st.isFile())
|
|
60
|
+
return candidate;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
/* keep walking */
|
|
64
|
+
}
|
|
54
65
|
}
|
|
55
66
|
}
|
|
56
67
|
return undefined;
|
|
57
68
|
}
|
|
69
|
+
/** Windows PATHEXT default, used when the env carries no explicit list. */
|
|
70
|
+
const DEFAULT_PATHEXT = ".COM;.EXE;.BAT;.CMD";
|
|
71
|
+
/**
|
|
72
|
+
* Executable suffixes to try for each candidate path, bare name (`""`) first
|
|
73
|
+
* so POSIX resolution is byte-for-byte unchanged. Extensions are appended only
|
|
74
|
+
* on win32 or when the env supplies PATHEXT (which is also the seam tests use
|
|
75
|
+
* to cover Windows resolution from a POSIX runner).
|
|
76
|
+
*/
|
|
77
|
+
function executableSuffixes(envSource) {
|
|
78
|
+
const pathext = envSource.PATHEXT ?? envSource.Pathext ?? envSource.pathext;
|
|
79
|
+
if (process.platform !== "win32" && !pathext)
|
|
80
|
+
return [""];
|
|
81
|
+
const exts = (pathext ?? DEFAULT_PATHEXT)
|
|
82
|
+
.split(";")
|
|
83
|
+
.map((ext) => ext.trim())
|
|
84
|
+
.filter(Boolean)
|
|
85
|
+
.map((ext) => (ext.startsWith(".") ? ext : `.${ext}`));
|
|
86
|
+
return ["", ...exts];
|
|
87
|
+
}
|
|
58
88
|
let detectOverrides;
|
|
59
89
|
/** TEST-ONLY. Swap the detection implementations; pass undefined to restore. */
|
|
60
90
|
export function _setAgentDetectForTests(fakes) {
|
|
@@ -79,7 +109,7 @@ export function detectAgentCliProfiles(agent, whichFn = defaultWhich) {
|
|
|
79
109
|
profilesByName.set(name, {
|
|
80
110
|
name,
|
|
81
111
|
platform: engine.platform,
|
|
82
|
-
bin: engine.bin ?? builtin?.bin ?? (engine.platform === "opencode-sdk" ?
|
|
112
|
+
bin: engine.bin ?? builtin?.bin ?? (engine.platform === "opencode-sdk" ? OPENCODE_SDK_SERVER_BIN : engine.platform),
|
|
83
113
|
args: engine.args ?? builtin?.args ?? [],
|
|
84
114
|
stdio: "captured",
|
|
85
115
|
envPassthrough: builtin?.envPassthrough ?? [],
|
|
@@ -0,0 +1,76 @@
|
|
|
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 { defaultWhich } from "./detect.js";
|
|
5
|
+
import { OPENCODE_SDK_SERVER_BIN } from "./profiles.js";
|
|
6
|
+
/**
|
|
7
|
+
* Engine name used for the synthesized entry. Matches the platform id so it
|
|
8
|
+
* reads correctly in `akm workflow status`, frozen plans, and error messages.
|
|
9
|
+
* A user-configured engine of the same name is preferred over synthesizing.
|
|
10
|
+
*/
|
|
11
|
+
export const FALLBACK_ENGINE_NAME = "opencode-sdk";
|
|
12
|
+
/** Announcement text, surfaced once per run/dispatch. */
|
|
13
|
+
export const FALLBACK_ANNOUNCEMENT = `No engine is configured; falling back to \`${FALLBACK_ENGINE_NAME}\` via the ` +
|
|
14
|
+
`\`${OPENCODE_SDK_SERVER_BIN}\` binary on PATH — provider, model, and auth come ` +
|
|
15
|
+
"from opencode's own configuration. " +
|
|
16
|
+
"Run `akm setup`, or set `defaults.engine`, to choose explicitly.";
|
|
17
|
+
/**
|
|
18
|
+
* Failure suffix for every surface that needs an engine and found none.
|
|
19
|
+
* Owned HERE, beside the probe whose outcome it describes: the probe follows a
|
|
20
|
+
* configured `bin` when one is pinned, so a consumer restating "not on PATH"
|
|
21
|
+
* would misdescribe a missing absolute path.
|
|
22
|
+
*/
|
|
23
|
+
export const NO_ENGINE_MESSAGE_SUFFIX = `has no selected engine, and no usable \`${OPENCODE_SDK_SERVER_BIN}\` binary was found to fall back to.`;
|
|
24
|
+
/**
|
|
25
|
+
* Guidance used when the fallback itself is unavailable.
|
|
26
|
+
*
|
|
27
|
+
* The opencode route names a BINARY install, not `npm i @opencode-ai/sdk`:
|
|
28
|
+
* that package is an HTTP client with no dependencies and cannot serve
|
|
29
|
+
* anything on its own (see the module doc).
|
|
30
|
+
*/
|
|
31
|
+
export const NO_ENGINE_REMEDY = "Run `akm setup` to detect an installed agent, or set one explicitly: " +
|
|
32
|
+
'`akm config set engines.claude \'{"kind":"agent","platform":"claude"}\'` ' +
|
|
33
|
+
"then `akm config set defaults.engine claude`. " +
|
|
34
|
+
`Installing the \`${OPENCODE_SDK_SERVER_BIN}\` binary also works ` +
|
|
35
|
+
"(`npm i -g opencode-ai`) — akm falls back to it automatically.";
|
|
36
|
+
/**
|
|
37
|
+
* Return a config whose `defaults.engine` resolves, applying the implicit
|
|
38
|
+
* `opencode-sdk` fallback when it does not and an opencode binary is present.
|
|
39
|
+
*
|
|
40
|
+
* Pure with respect to the input: never mutates `config`. Returns the input
|
|
41
|
+
* object identity unchanged when no fallback is needed, so callers can cheaply
|
|
42
|
+
* detect the common case.
|
|
43
|
+
*/
|
|
44
|
+
export function withEngineFallback(config, whichFn = defaultWhich) {
|
|
45
|
+
if (config.defaults?.engine)
|
|
46
|
+
return { config };
|
|
47
|
+
// An operator-configured engine of this name wins over a synthesized one —
|
|
48
|
+
// theirs may carry a model, an llmEngine fallback, or a pinned bin. Probe
|
|
49
|
+
// THAT bin rather than the bare command: a configured absolute path outside
|
|
50
|
+
// PATH is still a usable engine, and reporting it as missing would contradict
|
|
51
|
+
// the operator-configured-wins rule.
|
|
52
|
+
const existing = config.engines?.[FALLBACK_ENGINE_NAME];
|
|
53
|
+
if (!whichFn(existing?.bin ?? OPENCODE_SDK_SERVER_BIN))
|
|
54
|
+
return { config };
|
|
55
|
+
const engines = existing
|
|
56
|
+
? config.engines
|
|
57
|
+
: { ...(config.engines ?? {}), [FALLBACK_ENGINE_NAME]: { kind: "agent", platform: "opencode-sdk" } };
|
|
58
|
+
return {
|
|
59
|
+
config: {
|
|
60
|
+
...config,
|
|
61
|
+
engines,
|
|
62
|
+
defaults: { ...(config.defaults ?? {}), engine: FALLBACK_ENGINE_NAME },
|
|
63
|
+
},
|
|
64
|
+
fallbackEngineName: FALLBACK_ENGINE_NAME,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* The announcement, but only when the fallback candidate is the engine that
|
|
69
|
+
* actually won selection. Returns `undefined` otherwise, so an explicitly
|
|
70
|
+
* selected engine never triggers a claim that opencode supplied the model.
|
|
71
|
+
*/
|
|
72
|
+
export function fallbackAnnouncement(fallbackEngineName, selectedEngineName) {
|
|
73
|
+
if (!fallbackEngineName || selectedEngineName !== fallbackEngineName)
|
|
74
|
+
return undefined;
|
|
75
|
+
return FALLBACK_ANNOUNCEMENT;
|
|
76
|
+
}
|
|
@@ -91,6 +91,20 @@ const BUILTINS = {
|
|
|
91
91
|
parseOutput: "text",
|
|
92
92
|
},
|
|
93
93
|
};
|
|
94
|
+
/**
|
|
95
|
+
* Binary the `opencode-sdk` harness needs on PATH.
|
|
96
|
+
*
|
|
97
|
+
* The embedded SDK is not self-contained, and that is the SDK's own design
|
|
98
|
+
* rather than a consequence of how akm drives it: `@opencode-ai/sdk` ships
|
|
99
|
+
* with `"dependencies": {}` and its `createOpencodeServer` is itself a
|
|
100
|
+
* `spawn("opencode", ["serve", ...])`. akm's runner spawns the same binary
|
|
101
|
+
* directly and talks HTTP to it (see `harnesses/opencode-sdk/sdk-runner.ts`),
|
|
102
|
+
* so the `opencode` binary gates the SDK path exactly as it gates the CLI
|
|
103
|
+
* path — a host with the npm package but no binary can dispatch neither.
|
|
104
|
+
* `opencode-sdk` deliberately has no {@link BUILTINS} entry — it dispatches
|
|
105
|
+
* without argv construction — so this is the one place that pairing lives.
|
|
106
|
+
*/
|
|
107
|
+
export const OPENCODE_SDK_SERVER_BIN = "opencode";
|
|
94
108
|
/** Names of the canonical built-in harness descriptors. Stable, sorted. */
|
|
95
109
|
export const BUILTIN_AGENT_PROFILE_NAMES = Object.freeze(Object.keys(BUILTINS).sort());
|
|
96
110
|
/** Returns the built-in descriptor for a canonical harness id. */
|
|
@@ -21,7 +21,6 @@ import { parseEmbeddedJsonResponse } from "../../core/parse.js";
|
|
|
21
21
|
import { runManagedSubprocess, } from "../../core/subprocess.js";
|
|
22
22
|
import { getCommandBuilder } from "./builders.js";
|
|
23
23
|
import { DEFAULT_AGENT_TIMEOUT_MS } from "./config.js";
|
|
24
|
-
const DEFAULT_TIMEOUT_MS = DEFAULT_AGENT_TIMEOUT_MS;
|
|
25
24
|
/**
|
|
26
25
|
* Supplement `existingPath` with well-known user binary directories when
|
|
27
26
|
* running in a scheduler context (cron/launchd) where PATH is stripped.
|
|
@@ -144,7 +143,7 @@ function streamFailureMessage(profileName, stdout, stderr) {
|
|
|
144
143
|
export async function runAgent(profile, prompt, options = {}) {
|
|
145
144
|
const stdioMode = options.stdio ?? profile.stdio;
|
|
146
145
|
// null = explicitly disabled (no kill timer). undefined = runtime default.
|
|
147
|
-
const timeoutMs = options.timeoutMs !== undefined ? options.timeoutMs :
|
|
146
|
+
const timeoutMs = options.timeoutMs !== undefined ? options.timeoutMs : DEFAULT_AGENT_TIMEOUT_MS;
|
|
148
147
|
const parseOutput = options.parseOutput ?? profile.parseOutput;
|
|
149
148
|
const setTimeoutImpl = options.setTimeoutFn ?? setTimeout;
|
|
150
149
|
const clearTimeoutImpl = options.clearTimeoutFn ?? clearTimeout;
|
|
@@ -4,9 +4,13 @@
|
|
|
4
4
|
/**
|
|
5
5
|
* OpenCode SDK agent runner (migrated from `agent/sdk-runner.ts`, #564).
|
|
6
6
|
*
|
|
7
|
-
* Uses the embedded `@opencode-ai/sdk` instead of `Bun.spawn
|
|
8
|
-
* agent CLI
|
|
9
|
-
*
|
|
7
|
+
* Uses the embedded `@opencode-ai/sdk` instead of `Bun.spawn` for dispatch, so
|
|
8
|
+
* no agent CLI argv is ever constructed — but the SDK is not self-contained:
|
|
9
|
+
* {@link createManagedOpencode} spawns `opencode serve` and talks HTTP to it,
|
|
10
|
+
* so the `opencode` binary must be on PATH (which is why setup's
|
|
11
|
+
* `detectHarness` gates this harness on that probe). The user provides an
|
|
12
|
+
* OpenAI-compatible endpoint (or inherits from the selected fallback LLM
|
|
13
|
+
* engine) for the SDK.
|
|
10
14
|
*
|
|
11
15
|
* This is the runtime surface of the {@link OpencodeSdkHarness} (`id =
|
|
12
16
|
* 'opencode-sdk'`). It is the dispatch path for SDK runner specs; it exposes
|
|
@@ -70,7 +74,12 @@
|
|
|
70
74
|
* until the child ACTUALLY exits — and a real `opencode serve` (a live HTTP
|
|
71
75
|
* server with provider children) can outlive SIGTERM long enough to hang the
|
|
72
76
|
* caller indefinitely. {@link createManagedOpencode} therefore owns the spawn
|
|
73
|
-
* (the SDK package is used only for `createOpencodeClient`)
|
|
77
|
+
* (the SDK package is used only for `createOpencodeClient`).
|
|
78
|
+
*
|
|
79
|
+
* Owning the spawn changes the LIFECYCLE, not the REQUIREMENT: the SDK's
|
|
80
|
+
* `createOpencodeServer` is itself a `spawn("opencode", ["serve", ...])`, and
|
|
81
|
+
* the package declares `"dependencies": {}`, so `opencode` must be on PATH
|
|
82
|
+
* either way. akm's version differs only in how it supervises the child:
|
|
74
83
|
*
|
|
75
84
|
* - after the URL handshake, the child and its stdio are `unref()`ed /
|
|
76
85
|
* destroyed, so the handle can never hold akm open;
|
|
@@ -169,9 +169,7 @@ export async function upsertLockEntry(entry) {
|
|
|
169
169
|
* migrate-apply's config lock + maintenance barrier) whose synchronous body
|
|
170
170
|
* cannot await the sentinel's retry loop. No-op for an empty list.
|
|
171
171
|
*/
|
|
172
|
-
|
|
173
|
-
if (entries.length === 0)
|
|
174
|
-
return;
|
|
172
|
+
function readLockEntriesForMigration() {
|
|
175
173
|
let existing = [];
|
|
176
174
|
const lockfilePath = getLockfilePath();
|
|
177
175
|
if (fs.existsSync(lockfilePath)) {
|
|
@@ -187,6 +185,16 @@ export function mergeLockEntriesSync(entries) {
|
|
|
187
185
|
}
|
|
188
186
|
existing = raw;
|
|
189
187
|
}
|
|
188
|
+
return existing;
|
|
189
|
+
}
|
|
190
|
+
/** Validate the current lockfile before migrate-apply creates its backup or sentinel. */
|
|
191
|
+
export function assertMigrationLockfileReadable() {
|
|
192
|
+
readLockEntriesForMigration();
|
|
193
|
+
}
|
|
194
|
+
export function mergeLockEntriesSync(entries) {
|
|
195
|
+
const existing = readLockEntriesForMigration();
|
|
196
|
+
if (entries.length === 0)
|
|
197
|
+
return;
|
|
190
198
|
// MERGE per id, never replace: the migrator's entries are sparse (id/source/
|
|
191
199
|
// ref/localRoot), while an existing row may carry `resolvedVersion`,
|
|
192
200
|
// `resolvedRevision`, `integrity`, `installedAt`, … from a real install.
|
|
@@ -202,59 +210,6 @@ export function mergeLockEntriesSync(entries) {
|
|
|
202
210
|
const incomingIds = new Set(entries.map((e) => e.id));
|
|
203
211
|
writeLockfileUnlocked([...existing.filter((e) => !incomingIds.has(e.id)), ...merged]);
|
|
204
212
|
}
|
|
205
|
-
/** Freeze exact old/desired lockfile bytes while the migration lifecycle lock is held. */
|
|
206
|
-
export function planLockMergeSync(entries) {
|
|
207
|
-
const lockfilePath = getLockfilePath();
|
|
208
|
-
let oldBytes = null;
|
|
209
|
-
let existing = [];
|
|
210
|
-
try {
|
|
211
|
-
oldBytes = fs.readFileSync(lockfilePath, "utf8");
|
|
212
|
-
}
|
|
213
|
-
catch (error) {
|
|
214
|
-
if (error.code !== "ENOENT")
|
|
215
|
-
throw error;
|
|
216
|
-
}
|
|
217
|
-
if (oldBytes !== null) {
|
|
218
|
-
let parsed;
|
|
219
|
-
try {
|
|
220
|
-
parsed = JSON.parse(oldBytes);
|
|
221
|
-
}
|
|
222
|
-
catch (error) {
|
|
223
|
-
throw new ConfigError(`Cannot plan migration lock entries from unreadable lockfile ${lockfilePath}: ${error instanceof Error ? error.message : String(error)}.`, "INVALID_CONFIG_FILE");
|
|
224
|
-
}
|
|
225
|
-
if (!Array.isArray(parsed) || !parsed.every(isValidLockfileEntry)) {
|
|
226
|
-
throw new ConfigError(`Cannot plan migration lock entries from malformed lockfile ${lockfilePath}.`, "INVALID_CONFIG_FILE");
|
|
227
|
-
}
|
|
228
|
-
existing = parsed;
|
|
229
|
-
}
|
|
230
|
-
if (entries.length === 0)
|
|
231
|
-
return { oldBytes, desiredBytes: oldBytes };
|
|
232
|
-
const byId = new Map(existing.map((entry) => [entry.id, entry]));
|
|
233
|
-
for (const entry of entries)
|
|
234
|
-
byId.set(entry.id, { ...byId.get(entry.id), ...entry });
|
|
235
|
-
return { oldBytes, desiredBytes: `${JSON.stringify([...byId.values()], null, 2)}\n` };
|
|
236
|
-
}
|
|
237
|
-
/** Publish a frozen migration lock generation, accepting only exact old or exact desired bytes. */
|
|
238
|
-
export function publishPlannedLockMergeSync(plan) {
|
|
239
|
-
const lockfilePath = getLockfilePath();
|
|
240
|
-
let current = null;
|
|
241
|
-
try {
|
|
242
|
-
current = fs.readFileSync(lockfilePath, "utf8");
|
|
243
|
-
}
|
|
244
|
-
catch (error) {
|
|
245
|
-
if (error.code !== "ENOENT")
|
|
246
|
-
throw error;
|
|
247
|
-
}
|
|
248
|
-
if (current === plan.desiredBytes)
|
|
249
|
-
return;
|
|
250
|
-
if (current !== plan.oldBytes) {
|
|
251
|
-
throw new ConfigError(`Refusing migration lockfile publication because ${lockfilePath} is a third generation, neither the journaled old nor desired bytes.`, "INVALID_CONFIG_FILE");
|
|
252
|
-
}
|
|
253
|
-
if (plan.desiredBytes === null)
|
|
254
|
-
return;
|
|
255
|
-
fs.mkdirSync(path.dirname(lockfilePath), { recursive: true });
|
|
256
|
-
writeFileAtomic(lockfilePath, plan.desiredBytes, 0o600);
|
|
257
|
-
}
|
|
258
213
|
export async function removeLockEntry(id) {
|
|
259
214
|
if (!fs.existsSync(getDataDir()))
|
|
260
215
|
return;
|
|
@@ -57,16 +57,10 @@ const PASSTHROUGH_COMMANDS = [
|
|
|
57
57
|
"update",
|
|
58
58
|
"upgrade",
|
|
59
59
|
"workflow-abandon",
|
|
60
|
-
"workflow-brief",
|
|
61
|
-
"workflow-complete",
|
|
62
|
-
"workflow-complete-rejected",
|
|
63
60
|
"workflow-create",
|
|
64
61
|
"workflow-list",
|
|
65
|
-
"workflow-next",
|
|
66
|
-
"workflow-report",
|
|
67
62
|
"workflow-resume",
|
|
68
63
|
"workflow-run",
|
|
69
|
-
"workflow-start",
|
|
70
64
|
"workflow-status",
|
|
71
65
|
];
|
|
72
66
|
export const passthroughShapes = PASSTHROUGH_COMMANDS.map((command) => ({
|
|
@@ -289,12 +289,12 @@ export function formatSearchPlain(r, detail) {
|
|
|
289
289
|
if (hasWorkflowHit) {
|
|
290
290
|
const workflowRef = hits.find((h) => h.type === "workflow");
|
|
291
291
|
const wfRef = workflowRef && typeof workflowRef.ref === "string" ? workflowRef.ref : topRef;
|
|
292
|
-
lines.push(`Next: akm show '${topRef}' | To
|
|
293
|
-
lines.push("
|
|
292
|
+
lines.push(`Next: akm show '${topRef}' | To execute a workflow: akm workflow run '${wfRef}'`);
|
|
293
|
+
lines.push("Inspect the workflow before running it; `workflow run` executes and verifies its steps automatically.");
|
|
294
294
|
}
|
|
295
295
|
else {
|
|
296
296
|
lines.push(`Next: akm show '${topRef}'`);
|
|
297
|
-
lines.push("After reading the asset: check whether a workflow applies before editing — if so, use `akm workflow
|
|
297
|
+
lines.push("After reading the asset: check whether a workflow applies before editing — if so, inspect it and use `akm workflow run`.");
|
|
298
298
|
}
|
|
299
299
|
}
|
|
300
300
|
}
|
|
@@ -18,4 +18,4 @@
|
|
|
18
18
|
export { formatAddPlain, formatBundleShowPlain, formatClonePlain, formatConfigPlain, formatCuratePlain, formatEnvCreatePlain, formatEnvExportPlain, formatEnvListPlain, formatEnvRemovePlain, formatEventLine, formatEventsPlain, formatFeedbackPlain, formatImportPlain, formatIndexPlain, formatInfoPlain, formatInitPlain, formatListPlain, formatRegistryAddPlain, formatRegistryListPlain, formatRegistryRemovePlain, formatRegistrySearchPlain, formatRememberPlain, formatRemovePlain, formatSearchPlain, formatSyncPlain, formatUpdatePlain, formatUpgradePlain, } from "./command-format.js";
|
|
19
19
|
export { formatGateDecisionSummary, formatProposalAcceptPlain, formatProposalDiffPlain, formatProposalDrainPlain, formatProposalListPlain, formatProposalProducerPlain, formatProposalRejectPlain, formatProposalShowPlain, } from "./proposal-format.js";
|
|
20
20
|
export { formatShowPlain } from "./show-format.js";
|
|
21
|
-
export {
|
|
21
|
+
export { formatWorkflowCreatePlain, formatWorkflowListPlain, formatWorkflowResumePlain, formatWorkflowRunPlain, formatWorkflowStatusPlain, } from "./workflow-format.js";
|
|
@@ -45,9 +45,7 @@ export function appendShowDirectives(lines, r) {
|
|
|
45
45
|
if (assetType === "skill" || assetType === "knowledge") {
|
|
46
46
|
const activeRun = r.activeRun;
|
|
47
47
|
if (activeRun) {
|
|
48
|
-
|
|
49
|
-
lines.unshift(` akm workflow complete '${activeRun.runId}'${activeRun.stepId ? ` --step '${activeRun.stepId}'` : ""}`);
|
|
50
|
-
lines.unshift("Read this schema, then follow your workflow step's instructions to edit the workspace file. When done, mark the step complete:");
|
|
48
|
+
lines.unshift("Read this reference, follow the active unit's instructions, and return the result to the workflow runner. Step completion is automatic.");
|
|
51
49
|
lines.unshift(`WORKFLOW ACTIVE — schema shown as reference (run: ${activeRun.runId})`);
|
|
52
50
|
lines.unshift("---");
|
|
53
51
|
lines.unshift("");
|
|
@@ -88,14 +86,14 @@ export function appendShowDirectives(lines, r) {
|
|
|
88
86
|
const insertIdx = separatorIdx >= 0 ? separatorIdx + 1 : r.type || r.name ? 1 : 0;
|
|
89
87
|
const actionDirective = [
|
|
90
88
|
`ACTION REQUIRED: Do not execute steps manually from this output.`,
|
|
91
|
-
`Run \`akm workflow
|
|
89
|
+
`Run \`akm workflow run '${workflowRef}'\` to execute the workflow through its declared gates.`,
|
|
92
90
|
"---",
|
|
93
91
|
];
|
|
94
92
|
lines.splice(insertIdx, 0, "", ...actionDirective);
|
|
95
93
|
lines.push("");
|
|
96
94
|
lines.push("---");
|
|
97
|
-
lines.push(`NEXT STEP: Run \`akm workflow
|
|
98
|
-
lines.push("
|
|
95
|
+
lines.push(`NEXT STEP: Run \`akm workflow run '${workflowRef}'\` to execute the workflow.`);
|
|
96
|
+
lines.push("Inspect the workflow first; the runner dispatches and completes each step automatically.");
|
|
99
97
|
}
|
|
100
98
|
}
|
|
101
99
|
/**
|