akm-cli 0.9.0-rc.0 → 0.9.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +339 -2
- package/SECURITY.md +23 -24
- package/dist/assets/help/help-improve.md +10 -10
- package/dist/assets/hints/cli-hints-full.md +44 -10
- package/dist/assets/hints/cli-hints-short.md +6 -2
- package/dist/assets/{profiles → improve-strategies}/default.json +1 -0
- package/dist/assets/{profiles → improve-strategies}/graph-refresh.json +1 -1
- package/dist/assets/{profiles → improve-strategies}/proactive-maintenance.json +2 -3
- package/dist/assets/{profiles → improve-strategies}/reflect-distill.json +3 -4
- package/dist/assets/prompts/workflow-unit-preamble.md +26 -0
- package/dist/assets/stash-skeleton/README.md +28 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +6 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +6 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +12 -1
- package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +11 -1
- package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +6 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +9 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +7 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +7 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +6 -0
- package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +98 -0
- package/dist/assets/stash-skeleton/facts/conventions/domains.md +63 -0
- package/dist/assets/stash-skeleton/facts/conventions/organization.md +127 -0
- package/dist/assets/tasks/core/backup.yml +1 -0
- package/dist/assets/tasks/core/extract.yml +1 -0
- package/dist/assets/tasks/core/improve.yml +1 -0
- package/dist/assets/tasks/core/index-refresh.yml +1 -0
- package/dist/assets/tasks/core/sync.yml +1 -0
- package/dist/assets/tasks/core/version-check.yml +1 -0
- package/dist/assets/tasks/graph-refresh-weekly.yml +4 -4
- package/dist/assets/templates/html/health.html +5 -1
- package/dist/cli/config-migrate.js +31 -138
- package/dist/cli/config-validate.js +10 -8
- package/dist/cli.js +48 -14
- package/dist/commands/agent/agent-dispatch.js +17 -16
- package/dist/commands/agent/agent-support.js +0 -24
- package/dist/commands/agent/contribute-cli.js +5 -15
- package/dist/commands/backup-cli.js +54 -0
- package/dist/commands/config-cli.js +45 -159
- package/dist/commands/env/env-binding.js +95 -0
- package/dist/commands/env/env-cli.js +8 -65
- package/dist/commands/env/secret.js +8 -5
- package/dist/commands/health/checks.js +130 -83
- package/dist/commands/health/html-report.js +4 -0
- package/dist/commands/health/improve-metrics.js +30 -32
- package/dist/commands/health/llm-usage.js +19 -19
- package/dist/commands/health/md-report.js +4 -0
- package/dist/commands/health/metrics.js +2 -1
- package/dist/commands/health/surfaces.js +5 -4
- package/dist/commands/health.js +1 -1
- package/dist/commands/improve/consolidate/chunking.js +2 -2
- package/dist/commands/improve/consolidate.js +28 -25
- package/dist/commands/improve/distill/promote-memory.js +5 -12
- package/dist/commands/improve/distill/quality-gate.js +5 -7
- package/dist/commands/improve/distill.js +16 -5
- package/dist/commands/improve/eligibility.js +22 -12
- package/dist/commands/improve/extract-cli.js +47 -19
- package/dist/commands/improve/extract.js +110 -62
- package/dist/commands/improve/improve-cli.js +38 -16
- package/dist/commands/improve/improve-result-file.js +30 -24
- package/dist/commands/improve/improve-strategies.js +137 -0
- package/dist/commands/improve/improve.js +60 -30
- package/dist/commands/improve/locks.js +66 -45
- package/dist/commands/improve/loop-stages.js +75 -33
- package/dist/commands/improve/memory/memory-belief.js +79 -7
- package/dist/commands/improve/memory/memory-contradiction-detect.js +12 -4
- package/dist/commands/improve/preparation.js +71 -73
- package/dist/commands/improve/procedural.js +3 -2
- package/dist/commands/improve/recombine.js +2 -1
- package/dist/commands/improve/reflect.js +119 -214
- package/dist/commands/improve/shared.js +11 -5
- package/dist/commands/lint/base-linter.js +152 -42
- package/dist/commands/mv-cli.js +809 -0
- package/dist/commands/proposal/proposal-cli.js +18 -8
- package/dist/commands/proposal/propose.js +64 -69
- package/dist/commands/read/knowledge.js +436 -4
- package/dist/commands/read/remember-cli.js +39 -2
- package/dist/commands/read/search-cli.js +6 -1
- package/dist/commands/registry-cli.js +29 -14
- package/dist/commands/remember.js +2 -0
- package/dist/commands/sources/init.js +13 -14
- package/dist/commands/sources/migration-help.js +7 -4
- package/dist/commands/sources/schema-repair.js +2 -4
- package/dist/commands/sources/source-add.js +62 -73
- package/dist/commands/sources/source-manage.js +50 -46
- package/dist/commands/sources/stash-cli.js +41 -4
- package/dist/commands/tasks/default-tasks.js +12 -12
- package/dist/commands/tasks/tasks-cli.js +7 -3
- package/dist/commands/tasks/tasks.js +113 -18
- package/dist/commands/wiki-cli.js +9 -10
- package/dist/commands/workflow-cli.js +276 -12
- package/dist/core/asset/asset-spec.js +58 -1
- package/dist/core/asset/frontmatter.js +12 -2
- package/dist/core/common.js +5 -3
- package/dist/core/config/config-io.js +28 -17
- package/dist/core/config/config-schema.js +379 -66
- package/dist/core/config/config-types.js +3 -3
- package/dist/core/config/config-version.js +29 -0
- package/dist/core/config/config-walker.js +98 -27
- package/dist/core/config/config.js +132 -266
- package/dist/core/config/deep-merge.js +41 -0
- package/dist/core/config/engine-semantics.js +32 -0
- package/dist/core/errors.js +2 -2
- package/dist/core/extra-params.js +61 -0
- package/dist/core/file-lock.js +201 -56
- package/dist/core/improve-result.js +178 -0
- package/dist/core/json-schema.js +142 -0
- package/dist/core/maintenance-barrier.js +119 -0
- package/dist/core/migration-backup.js +416 -0
- package/dist/core/paths.js +3 -0
- package/dist/core/redaction.js +358 -0
- package/dist/core/state/migrations.js +17 -2
- package/dist/core/state-db.js +44 -1
- package/dist/indexer/db/db.js +118 -2
- package/dist/indexer/graph/graph-extraction.js +28 -16
- package/dist/indexer/index-writer-lock.js +31 -24
- package/dist/indexer/index-written-assets.js +15 -6
- package/dist/indexer/indexer.js +47 -2
- package/dist/indexer/passes/memory-inference.js +10 -6
- package/dist/indexer/passes/metadata.js +250 -0
- package/dist/indexer/search/db-search.js +111 -44
- package/dist/indexer/search/fts-query.js +41 -0
- package/dist/indexer/search/ranking-contributors.js +48 -0
- package/dist/indexer/search/ranking.js +36 -23
- package/dist/indexer/search/search-fields.js +11 -1
- package/dist/indexer/walk/matchers.js +39 -0
- package/dist/integrations/agent/builder-shared.js +7 -0
- package/dist/integrations/agent/builders.js +5 -50
- package/dist/integrations/agent/config.js +3 -143
- package/dist/integrations/agent/detect.js +17 -2
- package/dist/integrations/agent/engine-resolution.js +202 -0
- package/dist/integrations/agent/index.js +1 -2
- package/dist/integrations/agent/model-aliases.js +16 -2
- package/dist/integrations/agent/profiles.js +36 -62
- package/dist/integrations/agent/runner-dispatch.js +91 -4
- package/dist/integrations/agent/runner.js +76 -207
- package/dist/integrations/agent/spawn.js +141 -20
- package/dist/integrations/harnesses/aider/agent-builder.js +112 -0
- package/dist/integrations/harnesses/aider/index.js +57 -0
- package/dist/integrations/harnesses/aider/result-extractor.js +53 -0
- package/dist/integrations/harnesses/amazonq/agent-builder.js +152 -0
- package/dist/integrations/harnesses/amazonq/index.js +58 -0
- package/dist/integrations/harnesses/amazonq/result-extractor.js +48 -0
- package/dist/integrations/harnesses/claude/agent-builder.js +46 -8
- package/dist/integrations/harnesses/claude/index.js +25 -25
- package/dist/integrations/harnesses/claude/result-extractor.js +52 -0
- package/dist/integrations/harnesses/codex/agent-builder.js +136 -0
- package/dist/integrations/harnesses/codex/index.js +62 -0
- package/dist/integrations/harnesses/codex/result-extractor.js +73 -0
- package/dist/integrations/harnesses/copilot/agent-builder.js +121 -0
- package/dist/integrations/harnesses/copilot/index.js +59 -0
- package/dist/integrations/harnesses/copilot/result-extractor.js +151 -0
- package/dist/integrations/harnesses/gemini/agent-builder.js +120 -0
- package/dist/integrations/harnesses/gemini/index.js +59 -0
- package/dist/integrations/harnesses/gemini/result-extractor.js +121 -0
- package/dist/integrations/harnesses/index.js +27 -28
- package/dist/integrations/harnesses/opencode/agent-builder.js +2 -3
- package/dist/integrations/harnesses/opencode/index.js +15 -22
- package/dist/integrations/harnesses/opencode-sdk/harness.js +60 -0
- package/dist/integrations/harnesses/opencode-sdk/index.js +8 -32
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +681 -108
- package/dist/integrations/harnesses/openhands/agent-builder.js +125 -0
- package/dist/integrations/harnesses/openhands/index.js +57 -0
- package/dist/integrations/harnesses/openhands/result-extractor.js +103 -0
- package/dist/integrations/harnesses/pi/agent-builder.js +103 -0
- package/dist/integrations/harnesses/pi/index.js +57 -0
- package/dist/integrations/harnesses/pi/result-extractor.js +135 -0
- package/dist/integrations/harnesses/types.js +8 -32
- package/dist/integrations/lockfile.js +32 -21
- package/dist/integrations/session-logs/index.js +24 -11
- package/dist/llm/client.js +48 -14
- package/dist/llm/feature-gate.js +15 -47
- package/dist/llm/graph-extract.js +1 -1
- package/dist/llm/index-passes.js +8 -42
- package/dist/llm/memory-infer-impl.js +1 -1
- package/dist/llm/usage-persist.js +4 -0
- package/dist/llm/usage-telemetry.js +35 -5
- package/dist/output/renderers.js +3 -2
- package/dist/output/shapes/helpers.js +2 -1
- package/dist/output/shapes/passthrough.js +6 -0
- package/dist/output/text/helpers.js +215 -2
- package/dist/output/text/workflow.js +3 -1
- package/dist/schemas/akm-config.json +16638 -0
- package/dist/schemas/akm-task.json +87 -0
- package/dist/schemas/akm-workflow.json +372 -0
- package/dist/scripts/migrate-storage.js +10944 -8801
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +9247 -350
- package/dist/setup/detected-engines.js +142 -0
- package/dist/setup/engine-config.js +89 -0
- package/dist/setup/setup.js +236 -132
- package/dist/setup/steps/connection.js +61 -32
- package/dist/setup/steps/platforms.js +4 -4
- package/dist/setup/steps.js +3 -2
- package/dist/storage/database.js +13 -1
- package/dist/storage/engines/sqlite-migrations.js +1 -0
- package/dist/storage/repositories/improve-runs-repository.js +5 -5
- package/dist/storage/repositories/task-history-repository.js +78 -0
- package/dist/storage/repositories/workflow-runs-repository.js +190 -1
- package/dist/tasks/parser.js +138 -52
- package/dist/tasks/runner.js +71 -75
- package/dist/tasks/schema.js +1 -1
- package/dist/tasks/validator.js +11 -6
- package/dist/text-import-hook.mjs +1 -1
- package/dist/wiki/wiki.js +9 -8
- package/dist/workflows/authoring/authoring.js +123 -10
- package/dist/workflows/authoring/workflow-program-template.yaml +31 -0
- package/dist/workflows/cli.js +4 -0
- package/dist/workflows/concurrency-policy.js +15 -0
- package/dist/workflows/db.js +200 -13
- package/dist/workflows/exec/brief.js +478 -0
- package/dist/workflows/exec/frozen-judge.js +47 -0
- package/dist/workflows/exec/native-executor.js +1034 -0
- package/dist/workflows/exec/param-secrets.js +115 -0
- package/dist/workflows/exec/report.js +1355 -0
- package/dist/workflows/exec/run-workflow.js +609 -0
- package/dist/workflows/exec/scheduler.js +71 -0
- package/dist/workflows/exec/step-work.js +1212 -0
- package/dist/workflows/exec/unit-writer.js +23 -0
- package/dist/workflows/exec/watch.js +116 -0
- package/dist/workflows/exec/worktree.js +171 -0
- package/dist/workflows/ir/compile.js +375 -0
- package/dist/workflows/ir/freeze.js +243 -0
- package/dist/workflows/ir/params.js +54 -0
- package/dist/workflows/ir/plan-hash.js +68 -0
- package/dist/workflows/ir/schema.js +545 -0
- package/dist/workflows/parser.js +10 -1
- package/dist/workflows/program/expressions.js +369 -0
- package/dist/workflows/program/parser.js +869 -0
- package/dist/workflows/program/project.js +104 -0
- package/dist/workflows/program/schema.js +54 -0
- package/dist/workflows/renderer.js +82 -5
- package/dist/workflows/resource-limits.js +20 -0
- package/dist/workflows/runtime/agent-identity.js +59 -14
- package/dist/workflows/runtime/plan-classifier.js +187 -0
- package/dist/workflows/runtime/runs.js +246 -69
- package/dist/workflows/runtime/unit-checkin.js +45 -0
- package/dist/workflows/runtime/workflow-asset-loader.js +42 -1
- package/dist/workflows/validate-summary.js +24 -3
- package/dist/workflows/validator.js +26 -1
- package/docs/data-and-telemetry.md +4 -3
- package/docs/migration/release-notes/0.6.0.md +1 -1
- package/docs/migration/release-notes/0.7.0.md +5 -4
- package/docs/migration/release-notes/0.9.0-beta.60.md +19 -0
- package/docs/migration/v0.8-to-v0.9.md +401 -0
- package/package.json +4 -2
- package/schemas/akm-config.json +16638 -0
- package/schemas/akm-task.json +87 -0
- package/schemas/akm-workflow.json +372 -0
- package/dist/commands/improve/improve-profiles.js +0 -168
- package/dist/core/config/config-migration.js +0 -602
- package/dist/core/deep-merge.js +0 -38
- package/dist/llm/call-ai.js +0 -62
- package/dist/setup/legacy-config.js +0 -106
- package/docs/README.md +0 -104
- /package/dist/assets/{profiles → improve-strategies}/catchup.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/consolidate.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/frequent.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/memory-focus.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/quick.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/recombine-only.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/synthesize.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/thorough.json +0 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
/**
|
|
5
|
+
* Amazon Q Developer CLI harness (P2 integration, plan §"The adapter
|
|
6
|
+
* contract").
|
|
7
|
+
*
|
|
8
|
+
* Per-harness barrel gathering the Amazon Q integration surfaces:
|
|
9
|
+
* - agent command builder → ./agent-builder.ts (amazonqBuilder)
|
|
10
|
+
* - result extractor → ./result-extractor.ts (amazonqResultExtractor)
|
|
11
|
+
*
|
|
12
|
+
* It also defines {@link AmazonqHarness}, the {@link AkmHarness} descriptor
|
|
13
|
+
* that `HARNESS_REGISTRY` registers. Dispatch-only: no native session-log
|
|
14
|
+
* reader or config importer yet.
|
|
15
|
+
*/
|
|
16
|
+
import { BaseHarness } from "../types.js";
|
|
17
|
+
import { AMAZONQ_RESUME_FLAG, amazonqBuilder } from "./agent-builder.js";
|
|
18
|
+
import { amazonqResultExtractor } from "./result-extractor.js";
|
|
19
|
+
export { AMAZONQ_PLATFORM, AMAZONQ_RESUME_FLAG, amazonqBuilder } from "./agent-builder.js";
|
|
20
|
+
export { amazonqResultExtractor, stripTerminalFraming } from "./result-extractor.js";
|
|
21
|
+
function caps(c) {
|
|
22
|
+
return {
|
|
23
|
+
sessionLogs: false,
|
|
24
|
+
agentDispatch: false,
|
|
25
|
+
detection: false,
|
|
26
|
+
configImport: false,
|
|
27
|
+
runtimeIdentity: false,
|
|
28
|
+
...c,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Amazon Q Developer CLI (`q`).
|
|
33
|
+
*
|
|
34
|
+
* Canonical id is `'amazonq'`; no alias or distinct runtime identity.
|
|
35
|
+
*/
|
|
36
|
+
export class AmazonqHarness extends BaseHarness {
|
|
37
|
+
id = "amazonq";
|
|
38
|
+
displayName = "Amazon Q Developer CLI";
|
|
39
|
+
aliases = [];
|
|
40
|
+
agentBuilder = amazonqBuilder;
|
|
41
|
+
resultExtractor = amazonqResultExtractor;
|
|
42
|
+
// ── Workflow-engine descriptor (plan §"Capability matrix", P2) ────────────
|
|
43
|
+
// akm spawns `q chat` locally per unit ⇒ local-runner.
|
|
44
|
+
pattern = "local-runner";
|
|
45
|
+
// No documented structured output: akm injects the schema into the prompt
|
|
46
|
+
// and extracts embedded JSON from plain-text stdout.
|
|
47
|
+
structuredOutput = "none";
|
|
48
|
+
// Q's `--resume` is a BARE flag (takesSessionId: false): it replays the
|
|
49
|
+
// previous conversation of the working directory and takes no session id
|
|
50
|
+
// (see AMAZONQ_RESUME_FLAG in ./agent-builder.ts).
|
|
51
|
+
resume = { flag: AMAZONQ_RESUME_FLAG, takesSessionId: false };
|
|
52
|
+
// No `identityEnv`: the matrix lists Q's identity markers as uncertain, and
|
|
53
|
+
// Q stamps no session var onto child processes.
|
|
54
|
+
capabilities = caps({
|
|
55
|
+
agentDispatch: true,
|
|
56
|
+
detection: true,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
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
|
+
const ESC = "\u001B";
|
|
5
|
+
const BEL = "\u0007";
|
|
6
|
+
/**
|
|
7
|
+
* Matches ANSI CSI sequences (colors, cursor movement, erase-line) and OSC
|
|
8
|
+
* sequences (title, hyperlink) terminated by BEL or ST. Built via the RegExp
|
|
9
|
+
* constructor so the control characters live in named string constants
|
|
10
|
+
* instead of regex-literal escapes.
|
|
11
|
+
*/
|
|
12
|
+
const ANSI_SEQUENCE = new RegExp(`${ESC}\\[[0-9;?]*[ -/]*[@-~]` + `|${ESC}\\][^${BEL}${ESC}]*(?:${BEL}|${ESC}\\\\)`, "g");
|
|
13
|
+
/**
|
|
14
|
+
* Resolve carriage-return overwrites within one physical line: a terminal
|
|
15
|
+
* would redraw from column 0 at each `\r`, so only the segment after the last
|
|
16
|
+
* `\r` survives (spinner frames like "⠋ Thinking..." disappear exactly as
|
|
17
|
+
* they do on screen). A trailing bare `\r` (CRLF line endings) is handled by
|
|
18
|
+
* splitting on `\r?\n` before this runs.
|
|
19
|
+
*/
|
|
20
|
+
function resolveCarriageReturns(line) {
|
|
21
|
+
const lastCr = line.lastIndexOf("\r");
|
|
22
|
+
return lastCr === -1 ? line : line.slice(lastCr + 1);
|
|
23
|
+
}
|
|
24
|
+
/** Strip terminal framing from raw captured stdout. See module doc, rule 1. */
|
|
25
|
+
export function stripTerminalFraming(raw) {
|
|
26
|
+
const lines = raw.split(/\r?\n/).map((line) => resolveCarriageReturns(line).replace(ANSI_SEQUENCE, ""));
|
|
27
|
+
// Drop Q's "> " response marker from the first non-empty line only — deeper
|
|
28
|
+
// occurrences may be legitimate content (markdown blockquotes).
|
|
29
|
+
for (let i = 0; i < lines.length; i++) {
|
|
30
|
+
const line = lines[i] ?? "";
|
|
31
|
+
if (line.trim().length === 0)
|
|
32
|
+
continue;
|
|
33
|
+
if (line.startsWith("> "))
|
|
34
|
+
lines[i] = line.slice(2);
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
return lines.join("\n").trim();
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Normalize a raw Amazon Q run result into `{ text, sessionId? }`.
|
|
41
|
+
* See the module doc for the rules.
|
|
42
|
+
*/
|
|
43
|
+
export const amazonqResultExtractor = (result) => {
|
|
44
|
+
const sessionId = result.sessionId;
|
|
45
|
+
// Rule 2: a pre-parsed whole-stdout JSON *string* is the answer itself.
|
|
46
|
+
const text = typeof result.parsed === "string" ? result.parsed.trim() : stripTerminalFraming(result.stdout);
|
|
47
|
+
return { text, ...(sessionId ? { sessionId } : {}) };
|
|
48
|
+
};
|
|
@@ -11,15 +11,48 @@
|
|
|
11
11
|
* in `agent/builders.ts`, which imports this builder back into
|
|
12
12
|
* `BUILTIN_BUILDERS`.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* ## Structured output (Codex round-3 finding A)
|
|
15
|
+
*
|
|
16
|
+
* The headless `claude -p` (`--print`) CLI has NO native output-SCHEMA flag
|
|
17
|
+
* (unlike Codex's `--output-schema <file>`). Its documented structured path is
|
|
18
|
+
* `--output-format json`, which wraps the run in a RESULT ENVELOPE
|
|
19
|
+
* (`{"type":"result","result":"<final answer>","session_id":"…", …}`) — the
|
|
20
|
+
* "native-json" tier, NOT "native-schema". (The registry's earlier
|
|
21
|
+
* `native-schema` claim described Claude Code's IN-HARNESS `Workflow`/`agent()`
|
|
22
|
+
* tool-input-schema path, which is a different execution surface than the
|
|
23
|
+
* agentBuilder dispatch akm's local-runner uses; the descriptor is aligned to
|
|
24
|
+
* `native-json` to match this builder honestly.)
|
|
25
|
+
*
|
|
26
|
+
* So for a schema-bearing unit this builder emits `--output-format json` and
|
|
27
|
+
* appends the SAME schema directive the engine's prompt assembly uses
|
|
28
|
+
* (`step-work.ts` `buildUnitPrompt`) so a direct (non-workflow) dispatch is
|
|
29
|
+
* self-sufficient — matching the copilot/gemini native-json builders. The
|
|
30
|
+
* result envelope is unwrapped by `./result-extractor.ts`, and the engine's
|
|
31
|
+
* shared `runStructured` retry-until-valid loop still validates the extracted
|
|
32
|
+
* text against the node schema (constrained/hinted output is trusted but
|
|
33
|
+
* verified). Without a schema the argv is byte-identical to the pre-fix shape.
|
|
34
|
+
*
|
|
35
|
+
* The builder's `platform` stays `'claude'` (the canonical harness id).
|
|
17
36
|
*/
|
|
18
|
-
import { assertNotFlag, normalizeTools } from "../../agent/builder-shared.js";
|
|
19
|
-
|
|
37
|
+
import { assertNotFlag, normalizeTools, resolveDispatchModel, } from "../../agent/builder-shared.js";
|
|
38
|
+
/**
|
|
39
|
+
* Assemble the positional prompt: the task prompt and — when a schema is
|
|
40
|
+
* requested — the same schema directive the workflow engine's prompt assembly
|
|
41
|
+
* uses (`step-work.ts` `buildUnitPrompt`), so both dispatch paths speak one
|
|
42
|
+
* dialect. Claude Code takes the system prompt as a `--system-prompt` FLAG (it
|
|
43
|
+
* has one, unlike copilot/gemini), so only the schema directive is folded in
|
|
44
|
+
* here.
|
|
45
|
+
*/
|
|
46
|
+
function buildPromptPayload(req) {
|
|
47
|
+
if (!req.schema)
|
|
48
|
+
return req.prompt;
|
|
49
|
+
return `${req.prompt}\n\nRespond with ONLY a JSON value matching this JSON Schema (no prose, no code fences):\n${JSON.stringify(req.schema)}`;
|
|
50
|
+
}
|
|
20
51
|
/**
|
|
21
52
|
* Claude Code builder.
|
|
22
|
-
* Command shape:
|
|
53
|
+
* Command shape:
|
|
54
|
+
* claude [--system-prompt "..."] [--model <m>] [--allowedTools <t>]
|
|
55
|
+
* [--output-format json] --print -- "<prompt (+ schema directive)>"
|
|
23
56
|
*
|
|
24
57
|
* --print switches Claude Code to non-interactive captured output mode.
|
|
25
58
|
*/
|
|
@@ -33,16 +66,21 @@ export const claudeBuilder = {
|
|
|
33
66
|
args.push("--system-prompt", req.systemPrompt);
|
|
34
67
|
}
|
|
35
68
|
if (req.model) {
|
|
36
|
-
const resolved =
|
|
69
|
+
const resolved = resolveDispatchModel(req, profile, "claude");
|
|
37
70
|
args.push("--model", resolved);
|
|
38
71
|
}
|
|
39
72
|
if (req.tools) {
|
|
40
73
|
args.push("--allowedTools", normalizeTools(req.tools));
|
|
41
74
|
}
|
|
75
|
+
if (req.schema) {
|
|
76
|
+
// Structured unit: request the documented JSON result envelope so
|
|
77
|
+
// `./result-extractor.ts` can pull the final answer + session id.
|
|
78
|
+
args.push("--output-format", "json");
|
|
79
|
+
}
|
|
42
80
|
// --print = non-interactive, outputs to stdout — required for captured mode
|
|
43
81
|
args.push("--print");
|
|
44
82
|
args.push("--");
|
|
45
|
-
args.push(req
|
|
83
|
+
args.push(buildPromptPayload(req));
|
|
46
84
|
return { argv: [profile.bin, ...args] };
|
|
47
85
|
},
|
|
48
86
|
};
|
|
@@ -1,33 +1,13 @@
|
|
|
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
|
-
/**
|
|
5
|
-
* Claude Code harness (#563).
|
|
6
|
-
*
|
|
7
|
-
* This is the per-harness barrel that gathers the Claude Code integration
|
|
8
|
-
* surfaces that were previously scattered across the codebase:
|
|
9
|
-
* - session-log reader → ./session-log.ts (ClaudeCodeProvider)
|
|
10
|
-
* - agent command builder → ./agent-builder.ts (claudeBuilder)
|
|
11
|
-
* - config importer → ./config-import.ts (claudeCodeImporter)
|
|
12
|
-
*
|
|
13
|
-
* It also defines {@link ClaudeHarness}, the {@link AkmHarness} descriptor that
|
|
14
|
-
* `HARNESS_REGISTRY` registers.
|
|
15
|
-
*
|
|
16
|
-
* ## id normalization bridge ('claude' vs 'claude-code')
|
|
17
|
-
*
|
|
18
|
-
* The canonical, persisted id is `'claude'` (used by the agent runner, agent
|
|
19
|
-
* profiles, the Zod config schema and `--type` resolution after normalization).
|
|
20
|
-
* `'claude-code'` is the historical RUNTIME identity — the string stamped on
|
|
21
|
-
* session-log events/refs, the extracted-session dedup key, and the value
|
|
22
|
-
* `resolveAgentIdentity` reports. It is registered as an `alias` and exposed as
|
|
23
|
-
* `runtimeId` so BOTH directions round-trip via `normalizeHarnessId()` /
|
|
24
|
-
* `denormalizeRuntimeIdentity()`. Existing persisted configs and session logs
|
|
25
|
-
* that say `'claude-code'` keep working unchanged.
|
|
26
|
-
*/
|
|
27
4
|
import { BaseHarness } from "../types.js";
|
|
28
5
|
import { claudeBuilder } from "./agent-builder.js";
|
|
6
|
+
import { claudeResultExtractor } from "./result-extractor.js";
|
|
7
|
+
import { ClaudeCodeProvider } from "./session-log.js";
|
|
29
8
|
export { claudeBuilder } from "./agent-builder.js";
|
|
30
9
|
export { claudeCodeImporter } from "./config-import.js";
|
|
10
|
+
export { claudeResultExtractor } from "./result-extractor.js";
|
|
31
11
|
export { ClaudeCodeProvider } from "./session-log.js";
|
|
32
12
|
function caps(c) {
|
|
33
13
|
return {
|
|
@@ -36,7 +16,6 @@ function caps(c) {
|
|
|
36
16
|
detection: false,
|
|
37
17
|
configImport: false,
|
|
38
18
|
runtimeIdentity: false,
|
|
39
|
-
v1Migration: false,
|
|
40
19
|
...c,
|
|
41
20
|
};
|
|
42
21
|
}
|
|
@@ -55,12 +34,33 @@ export class ClaudeHarness extends BaseHarness {
|
|
|
55
34
|
// session-log provider, so offering it as a stash source is functional.
|
|
56
35
|
setupDetectionDir = ".claude";
|
|
57
36
|
agentBuilder = claudeBuilder;
|
|
37
|
+
resultExtractor = claudeResultExtractor;
|
|
38
|
+
// ── Workflow-engine descriptor (plan §"Capability matrix", P2) ────────────
|
|
39
|
+
// Claude Code is the in-harness pattern: the orchestrating session itself
|
|
40
|
+
// drives units via the `akm workflow` gate spine (`claude -p` headless
|
|
41
|
+
// dispatch also exists via `agentBuilder`, but the pattern classification
|
|
42
|
+
// follows the matrix row).
|
|
43
|
+
pattern = "in-harness";
|
|
44
|
+
// Structured output tier for the AGENT-DISPATCH (`claude -p`) path akm's
|
|
45
|
+
// local runner uses (Codex round-3 finding A). The headless CLI has NO
|
|
46
|
+
// output-schema flag — its documented structured path is `--output-format
|
|
47
|
+
// json`, a RESULT ENVELOPE akm parses (`./result-extractor.ts`) and then
|
|
48
|
+
// validates against the node schema ⇒ the "native-json" tier. (Claude Code's
|
|
49
|
+
// in-harness `Workflow`/`agent()` tool-input-schema path IS native-schema,
|
|
50
|
+
// but that is a different surface than the dispatch builder — the descriptor
|
|
51
|
+
// is aligned to what the builder honestly does.)
|
|
52
|
+
structuredOutput = "native-json";
|
|
53
|
+
// `claude --resume <sessionId>` replays a previous session in headless mode.
|
|
54
|
+
resume = { flag: "--resume", takesSessionId: true };
|
|
55
|
+
// Session-id env marker: presence of a concrete session id (not the bare
|
|
56
|
+
// "running under Claude Code" flag) attributes a run to this harness.
|
|
57
|
+
identityEnv = ["CLAUDE_SESSION_ID"];
|
|
58
|
+
sessionLogProvider = () => new ClaudeCodeProvider();
|
|
58
59
|
capabilities = caps({
|
|
59
60
|
sessionLogs: true,
|
|
60
61
|
agentDispatch: true,
|
|
61
62
|
detection: true,
|
|
62
63
|
configImport: true,
|
|
63
64
|
runtimeIdentity: true,
|
|
64
|
-
v1Migration: true,
|
|
65
65
|
});
|
|
66
66
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
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
|
+
function isRecord(value) {
|
|
5
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6
|
+
}
|
|
7
|
+
function asNonEmptyString(value) {
|
|
8
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
9
|
+
}
|
|
10
|
+
/** JSON.parse that returns undefined instead of throwing. */
|
|
11
|
+
function tryParseJson(raw) {
|
|
12
|
+
try {
|
|
13
|
+
return JSON.parse(raw);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Is this parsed object Claude Code's `--output-format json` RESULT ENVELOPE
|
|
21
|
+
* (rather than a bare JSON answer a schema unit produced without the flag)? A
|
|
22
|
+
* genuine envelope declares `type: "result"`, or carries the two envelope-only
|
|
23
|
+
* fields together (a string `result` AND a `session_id`). A bare structured
|
|
24
|
+
* answer — `{"result":"ok"}` — carries neither marker, so it is passed through
|
|
25
|
+
* raw and the engine's schema validator sees the whole object.
|
|
26
|
+
*/
|
|
27
|
+
function isResultEnvelope(value) {
|
|
28
|
+
if (value.type === "result")
|
|
29
|
+
return true;
|
|
30
|
+
return typeof value.result === "string" && asNonEmptyString(value.session_id) !== undefined;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Normalize a raw claude run result into `{ text, sessionId? }`. See the module
|
|
34
|
+
* doc for the two stdout shapes (JSON result envelope vs plain text).
|
|
35
|
+
*/
|
|
36
|
+
export const claudeResultExtractor = (result) => {
|
|
37
|
+
const fallbackSessionId = result.sessionId;
|
|
38
|
+
const raw = result.stdout;
|
|
39
|
+
const trimmed = raw.trim();
|
|
40
|
+
// Only attempt an envelope parse when stdout looks like a single JSON object
|
|
41
|
+
// (`--output-format json`). A plain-text run is passed through UNCHANGED
|
|
42
|
+
// (untrimmed) so schemaless dispatch stays byte-identical to today.
|
|
43
|
+
if (trimmed.startsWith("{")) {
|
|
44
|
+
const whole = result.parsed !== undefined ? result.parsed : tryParseJson(trimmed);
|
|
45
|
+
if (isRecord(whole) && isResultEnvelope(whole)) {
|
|
46
|
+
const text = typeof whole.result === "string" ? whole.result : trimmed;
|
|
47
|
+
const sessionId = asNonEmptyString(whole.session_id) ?? fallbackSessionId;
|
|
48
|
+
return { text, ...(sessionId ? { sessionId } : {}) };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return { text: raw, ...(fallbackSessionId ? { sessionId: fallbackSessionId } : {}) };
|
|
52
|
+
};
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
/**
|
|
5
|
+
* OpenAI Codex CLI agent command builder (P2, plan §"The adapter contract" /
|
|
6
|
+
* §"Capability matrix").
|
|
7
|
+
*
|
|
8
|
+
* Translates a platform-agnostic {@link AgentDispatchRequest} into the exact
|
|
9
|
+
* headless argv the `codex` CLI expects:
|
|
10
|
+
*
|
|
11
|
+
* codex exec --sandbox workspace-write [--model <m>] --json [--output-schema <file>] -- "<prompt>"
|
|
12
|
+
*
|
|
13
|
+
* Capability-matrix facts this builder encodes (July 2026 research):
|
|
14
|
+
* - Headless invocation is the `exec` subcommand (`codex exec "<p>"`), not a
|
|
15
|
+
* flag. The built-in codex profiles carry `args: []`, so the builder
|
|
16
|
+
* prepends `exec` itself; a user profile that already pins `exec` as its
|
|
17
|
+
* first arg is not doubled.
|
|
18
|
+
* - `--json` switches stdout to a JSONL event stream — the input contract of
|
|
19
|
+
* `./result-extractor.ts`. Always emitted, mirroring how the Claude builder
|
|
20
|
+
* always emits `--print`: dispatch is the captured, non-interactive path.
|
|
21
|
+
* - Codex is the NATIVE-SCHEMA tier (plan §"Structured-output
|
|
22
|
+
* normalization"): `req.schema` is written to a temp file and passed via
|
|
23
|
+
* `--output-schema <file>`. The file is tiny, uniquely named under the OS
|
|
24
|
+
* temp dir, and intentionally NOT cleaned up here — `BuiltCommand` has no
|
|
25
|
+
* post-run hook, and the spawned process reads the file after `build()`
|
|
26
|
+
* returns. OS temp reaping owns the lifecycle. The engine still validates
|
|
27
|
+
* the output defensively (the constrained output is trusted but verified).
|
|
28
|
+
* - `codex exec` has no system-prompt flag; `req.systemPrompt` is folded
|
|
29
|
+
* into the prompt payload (system text first, blank line, then the task),
|
|
30
|
+
* after the `--` end-of-options separator so it can never be parsed as a
|
|
31
|
+
* flag.
|
|
32
|
+
* - Tool policy is omitted, but the builder DOES inject `--sandbox
|
|
33
|
+
* workspace-write` so dispatched units can write to their working
|
|
34
|
+
* directory. `codex exec` defaults to a read-only sandbox that silently
|
|
35
|
+
* blocks file writes; without this flag the unit returns "workspace is
|
|
36
|
+
* read-only" and the engine marks the step complete with no real mutation.
|
|
37
|
+
* `--ask-for-approval` is NOT injected — that flag only exists on the
|
|
38
|
+
* interactive `codex` command, not on `codex exec`, and exec mode is
|
|
39
|
+
* already non-approval-blocking. Profile-supplied `--sandbox` flags
|
|
40
|
+
* (long or short form) are preserved (not duplicated).
|
|
41
|
+
* - Resume is the `codex exec resume <id>` SUBCOMMAND, not a flag, so it is
|
|
42
|
+
* not expressible through `AgentDispatchRequest` (which has no session
|
|
43
|
+
* field yet); {@link codexResumeArgs} exposes the argv prefix for the
|
|
44
|
+
* integration task that wires session-id reuse from `workflow_run_units`.
|
|
45
|
+
* - `req.effort` stays unconsumed (reserved; codex would take it as
|
|
46
|
+
* `-c model_reasoning_effort=<v>` — left to the integration task so the
|
|
47
|
+
* shared request contract's "no builder consumes it yet" note stays true).
|
|
48
|
+
*
|
|
49
|
+
* NOT registered anywhere yet: `HARNESS_REGISTRY` / `BUILTIN_BUILDERS` wiring
|
|
50
|
+
* is a follow-up integration task. Exported cleanly for that task to import.
|
|
51
|
+
*/
|
|
52
|
+
import { mkdtempSync, writeFileSync } from "node:fs";
|
|
53
|
+
import { tmpdir } from "node:os";
|
|
54
|
+
import { join } from "node:path";
|
|
55
|
+
import { assertNotFlag, resolveDispatchModel } from "../../agent/builder-shared.js";
|
|
56
|
+
/**
|
|
57
|
+
* Write a node's JSON Schema to a fresh temp file for `--output-schema`.
|
|
58
|
+
*
|
|
59
|
+
* A unique `mkdtemp` directory per build avoids collisions between concurrent
|
|
60
|
+
* fan-out units dispatching in the same process. Returns the absolute file
|
|
61
|
+
* path (the value handed to the flag).
|
|
62
|
+
*/
|
|
63
|
+
export function writeCodexOutputSchemaFile(schema) {
|
|
64
|
+
const dir = mkdtempSync(join(tmpdir(), "akm-codex-schema-"));
|
|
65
|
+
const file = join(dir, "output-schema.json");
|
|
66
|
+
writeFileSync(file, `${JSON.stringify(schema, null, 2)}\n`, "utf8");
|
|
67
|
+
return file;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Argv prefix that resumes a previous codex session: `exec resume <id>`.
|
|
71
|
+
* Codex resume is a subcommand chain, not a flag — kept here so the flag-shaped
|
|
72
|
+
* `HarnessResumeSupport` seam is not force-fitted. The harness-native session
|
|
73
|
+
* id comes from the unit row (stored opportunistically by the result
|
|
74
|
+
* extractor); akm never depends on it (plan §"Session, MCP, and identity").
|
|
75
|
+
*/
|
|
76
|
+
export function codexResumeArgs(sessionId) {
|
|
77
|
+
assertNotFlag(sessionId, "sessionId");
|
|
78
|
+
return ["exec", "resume", sessionId];
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Return `base` plus the `--sandbox workspace-write` flag that makes `codex exec`
|
|
82
|
+
* able to write to its working directory. `codex exec` defaults to a read-only
|
|
83
|
+
* sandbox that silently blocks file writes — without this flag the unit returns
|
|
84
|
+
* "workspace is read-only" and the engine marks the step complete with no real
|
|
85
|
+
* mutation. If the profile already pins `--sandbox` (long or short form) the
|
|
86
|
+
* default is not duplicated.
|
|
87
|
+
*
|
|
88
|
+
* Note: `--ask-for-approval` is an *interactive* codex flag only — `codex exec`
|
|
89
|
+
* does not accept it (it errors with "unexpected argument"). Non-interactive
|
|
90
|
+
* exec mode is implicitly non-approval-blocking; `--sandbox workspace-write`
|
|
91
|
+
* alone is sufficient for dispatched units.
|
|
92
|
+
*/
|
|
93
|
+
function ensureSandboxFlags(base) {
|
|
94
|
+
const out = [...base];
|
|
95
|
+
if (!out.includes("--sandbox") && !out.includes("-s")) {
|
|
96
|
+
out.push("--sandbox", "workspace-write");
|
|
97
|
+
}
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* OpenAI Codex builder.
|
|
102
|
+
* Command shape: codex exec --sandbox workspace-write [--model <m>] --json [--output-schema <file>] -- "<prompt>"
|
|
103
|
+
*/
|
|
104
|
+
export const codexBuilder = {
|
|
105
|
+
platform: "codex",
|
|
106
|
+
build(profile, req) {
|
|
107
|
+
assertNotFlag(req.systemPrompt, "systemPrompt");
|
|
108
|
+
assertNotFlag(req.model, "model");
|
|
109
|
+
// Built-in codex profiles ship `args: []`; headless dispatch is the `exec`
|
|
110
|
+
// subcommand. Don't double it when a user profile already pins it.
|
|
111
|
+
const extra = profile.args[0] === "exec" ? profile.args.slice(1) : [...profile.args];
|
|
112
|
+
// `codex exec` defaults to a read-only sandbox that silently blocks file
|
|
113
|
+
// writes — dispatched units would return "workspace is read-only" and the
|
|
114
|
+
// engine would mark them complete with no real mutation. Force
|
|
115
|
+
// `workspace-write` (writes scoped to cwd) unless the profile already pins
|
|
116
|
+
// its own --sandbox flag (`--ask-for-approval` is not injectable here —
|
|
117
|
+
// see ensureSandboxFlags).
|
|
118
|
+
const sandboxArgs = ensureSandboxFlags(extra);
|
|
119
|
+
const args = ["exec", ...sandboxArgs];
|
|
120
|
+
if (req.model) {
|
|
121
|
+
const resolved = resolveDispatchModel(req, profile, "codex");
|
|
122
|
+
args.push("--model", resolved);
|
|
123
|
+
}
|
|
124
|
+
// JSONL event stream on stdout — the codex result extractor's input.
|
|
125
|
+
args.push("--json");
|
|
126
|
+
if (req.schema) {
|
|
127
|
+
// Native-schema tier: pass the node schema straight through.
|
|
128
|
+
args.push("--output-schema", writeCodexOutputSchemaFile(req.schema));
|
|
129
|
+
}
|
|
130
|
+
// No system-prompt flag exists on `codex exec` — fold it into the prompt.
|
|
131
|
+
const prompt = req.systemPrompt ? `${req.systemPrompt}\n\n${req.prompt}` : req.prompt;
|
|
132
|
+
args.push("--");
|
|
133
|
+
args.push(prompt);
|
|
134
|
+
return { argv: [profile.bin, ...args] };
|
|
135
|
+
},
|
|
136
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
/**
|
|
5
|
+
* OpenAI Codex CLI harness (P2 integration, plan §"The adapter contract").
|
|
6
|
+
*
|
|
7
|
+
* Per-harness barrel gathering the Codex integration surfaces:
|
|
8
|
+
* - agent command builder → ./agent-builder.ts (codexBuilder)
|
|
9
|
+
* - result extractor → ./result-extractor.ts (codexResultExtractor)
|
|
10
|
+
*
|
|
11
|
+
* It also defines {@link CodexHarness}, the {@link AkmHarness} descriptor that
|
|
12
|
+
* `HARNESS_REGISTRY` registers. Dispatch-only: no native session-log reader or
|
|
13
|
+
* config importer yet.
|
|
14
|
+
*/
|
|
15
|
+
import { BaseHarness } from "../types.js";
|
|
16
|
+
import { codexBuilder } from "./agent-builder.js";
|
|
17
|
+
import { codexResultExtractor } from "./result-extractor.js";
|
|
18
|
+
export { codexBuilder, codexResumeArgs, writeCodexOutputSchemaFile } from "./agent-builder.js";
|
|
19
|
+
export { codexResultExtractor } from "./result-extractor.js";
|
|
20
|
+
function caps(c) {
|
|
21
|
+
return {
|
|
22
|
+
sessionLogs: false,
|
|
23
|
+
agentDispatch: false,
|
|
24
|
+
detection: false,
|
|
25
|
+
configImport: false,
|
|
26
|
+
runtimeIdentity: false,
|
|
27
|
+
...c,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* OpenAI Codex CLI.
|
|
32
|
+
*
|
|
33
|
+
* Canonical id is `'codex'`; no alias or distinct runtime identity.
|
|
34
|
+
*/
|
|
35
|
+
export class CodexHarness extends BaseHarness {
|
|
36
|
+
id = "codex";
|
|
37
|
+
displayName = "OpenAI Codex CLI";
|
|
38
|
+
aliases = [];
|
|
39
|
+
agentBuilder = codexBuilder;
|
|
40
|
+
resultExtractor = codexResultExtractor;
|
|
41
|
+
// ── Workflow-engine descriptor (plan §"Capability matrix", P2) ────────────
|
|
42
|
+
// akm spawns `codex exec` locally per unit ⇒ local-runner.
|
|
43
|
+
pattern = "local-runner";
|
|
44
|
+
// `--output-schema <file>` enforces a caller-supplied JSON schema natively.
|
|
45
|
+
structuredOutput = "native-schema";
|
|
46
|
+
// No flag-shaped resume: codex resume is the `exec resume <id>` SUBCOMMAND
|
|
47
|
+
// chain (see `codexResumeArgs` in ./agent-builder.ts), which the flag-shaped
|
|
48
|
+
// `HarnessResumeSupport` seam deliberately does not force-fit.
|
|
49
|
+
// Presence flag: CODEX_SANDBOX is stamped only on processes codex itself
|
|
50
|
+
// spawns inside its sandbox, so it genuinely means "running under codex" —
|
|
51
|
+
// but its VALUE (e.g. "seatbelt") is a sandbox mode, not a session id, so it
|
|
52
|
+
// is registered as `presenceEnv` (harness inference only), never
|
|
53
|
+
// `identityEnv` (whose values persist as agent_session_id). CODEX_HOME (the
|
|
54
|
+
// matrix's other candidate) is deliberately NOT registered anywhere: it is a
|
|
55
|
+
// user config-dir var commonly exported in shell profiles, so it would stamp
|
|
56
|
+
// identity onto manual runs (see `AkmHarness.presenceEnv`).
|
|
57
|
+
presenceEnv = ["CODEX_SANDBOX"];
|
|
58
|
+
capabilities = caps({
|
|
59
|
+
agentDispatch: true,
|
|
60
|
+
detection: true,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
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
|
+
function isRecord(value) {
|
|
5
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6
|
+
}
|
|
7
|
+
function asNonEmptyString(value) {
|
|
8
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
9
|
+
}
|
|
10
|
+
/** Interpret one parsed JSONL event in either codex event dialect. */
|
|
11
|
+
function interpretEvent(event) {
|
|
12
|
+
// Legacy protocol: {"id":..., "msg":{"type": ...}}
|
|
13
|
+
const msg = event.msg;
|
|
14
|
+
if (isRecord(msg)) {
|
|
15
|
+
switch (msg.type) {
|
|
16
|
+
case "session_configured":
|
|
17
|
+
return { sessionId: asNonEmptyString(msg.session_id) };
|
|
18
|
+
case "agent_message":
|
|
19
|
+
return { agentMessage: asNonEmptyString(msg.message) };
|
|
20
|
+
case "task_complete":
|
|
21
|
+
return { finalMessage: asNonEmptyString(msg.last_agent_message) };
|
|
22
|
+
default:
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
// Newer protocol: flat {"type": "thread.started" | "item.completed" | ...}
|
|
27
|
+
switch (event.type) {
|
|
28
|
+
case "thread.started":
|
|
29
|
+
return { sessionId: asNonEmptyString(event.thread_id) };
|
|
30
|
+
case "item.completed": {
|
|
31
|
+
const item = event.item;
|
|
32
|
+
if (isRecord(item) && item.type === "agent_message") {
|
|
33
|
+
return { agentMessage: asNonEmptyString(item.text) };
|
|
34
|
+
}
|
|
35
|
+
return {};
|
|
36
|
+
}
|
|
37
|
+
default:
|
|
38
|
+
return {};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Codex result extractor: JSONL event stream (either dialect) → final agent
|
|
43
|
+
* message + opportunistic session id, with a plain-stdout fallback.
|
|
44
|
+
*/
|
|
45
|
+
export const codexResultExtractor = (result) => {
|
|
46
|
+
let sessionId = result.sessionId;
|
|
47
|
+
let lastAgentMessage;
|
|
48
|
+
let finalMessage;
|
|
49
|
+
for (const line of result.stdout.split("\n")) {
|
|
50
|
+
const trimmed = line.trim();
|
|
51
|
+
// Every codex event line is a JSON object; skip banner/noise lines cheaply.
|
|
52
|
+
if (!trimmed.startsWith("{"))
|
|
53
|
+
continue;
|
|
54
|
+
let parsed;
|
|
55
|
+
try {
|
|
56
|
+
parsed = JSON.parse(trimmed);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
continue; // not a complete JSON line (e.g. wrapped text) — ignore
|
|
60
|
+
}
|
|
61
|
+
if (!isRecord(parsed))
|
|
62
|
+
continue;
|
|
63
|
+
const contribution = interpretEvent(parsed);
|
|
64
|
+
if (contribution.sessionId)
|
|
65
|
+
sessionId = contribution.sessionId;
|
|
66
|
+
if (contribution.agentMessage)
|
|
67
|
+
lastAgentMessage = contribution.agentMessage;
|
|
68
|
+
if (contribution.finalMessage)
|
|
69
|
+
finalMessage = contribution.finalMessage;
|
|
70
|
+
}
|
|
71
|
+
const text = finalMessage ?? lastAgentMessage ?? result.stdout.trim();
|
|
72
|
+
return sessionId === undefined ? { text } : { text, sessionId };
|
|
73
|
+
};
|