akm-cli 0.9.0-beta.9 → 0.9.0-rc.1
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 +715 -0
- package/README.md +12 -4
- package/dist/akm +38 -0
- package/dist/akm-migrate-storage +38 -0
- package/dist/assets/help/help-improve.md +9 -6
- package/dist/assets/hints/cli-hints-full.md +6 -5
- package/dist/assets/profiles/default.json +9 -4
- package/dist/assets/profiles/frequent.json +1 -1
- package/dist/assets/profiles/memory-focus.json +1 -1
- package/dist/assets/profiles/proactive-maintenance.json +25 -0
- package/dist/assets/profiles/quick.json +1 -1
- package/dist/assets/profiles/recombine-only.json +21 -0
- package/dist/assets/profiles/reflect-distill.json +30 -0
- package/dist/assets/profiles/synthesize.json +15 -0
- package/dist/assets/profiles/thorough.json +1 -1
- package/dist/assets/prompts/consolidate-system.md +23 -0
- package/dist/assets/prompts/contradiction-judge.md +33 -0
- package/dist/assets/prompts/distill-knowledge-system.md +22 -0
- package/dist/assets/prompts/distill-lesson-system.md +36 -0
- package/dist/assets/prompts/extract-session.md +11 -3
- package/dist/assets/prompts/graph-extract-system.md +1 -0
- package/dist/assets/prompts/graph-extract-user-prompt.md +1 -1
- package/dist/assets/prompts/memory-infer-system.md +1 -0
- package/dist/assets/prompts/memory-infer-user.md +5 -0
- package/dist/assets/prompts/metadata-enhance-system.md +1 -0
- package/dist/assets/prompts/procedural-system.md +44 -0
- package/dist/assets/prompts/recombine-system.md +40 -0
- package/dist/assets/prompts/staleness-detect-system.md +6 -0
- package/dist/assets/prompts/validate-summary-judge.md +1 -0
- package/dist/assets/prompts/workflow-unit-preamble.md +26 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +38 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +38 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +39 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +40 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +43 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +38 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +43 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +40 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +43 -0
- package/dist/assets/templates/html/health.html +281 -111
- package/dist/assets/wiki/ingest-workflow-template.md +45 -16
- package/dist/assets/wiki/schema-template.md +4 -4
- package/dist/cli/clack.js +56 -0
- package/dist/cli/config-migrate.js +7 -1
- package/dist/cli/confirm.js +1 -1
- package/dist/cli/parse-args.js +46 -1
- package/dist/cli/shared.js +28 -0
- package/dist/cli.js +25 -21
- package/dist/commands/agent/agent-dispatch.js +3 -2
- package/dist/commands/agent/agent-support.js +0 -7
- package/dist/commands/agent/contribute-cli.js +26 -7
- package/dist/commands/config-cli.js +26 -13
- package/dist/commands/env/child-env.js +47 -0
- package/dist/commands/env/env-binding.js +95 -0
- package/dist/commands/env/env-cli.js +228 -292
- package/dist/commands/env/env.js +14 -67
- package/dist/commands/env/secret-cli.js +140 -138
- package/dist/commands/feedback-cli.js +156 -155
- package/dist/commands/graph/graph-cli.js +5 -13
- package/dist/commands/graph/graph.js +3 -3
- package/dist/commands/health/advisories.js +151 -0
- package/dist/commands/health/checks.js +103 -16
- package/dist/commands/health/html-report.js +447 -81
- package/dist/commands/health/improve-metrics.js +771 -0
- package/dist/commands/health/llm-usage.js +65 -0
- package/dist/commands/health/md-report.js +103 -0
- package/dist/commands/health/metrics.js +278 -0
- package/dist/commands/health/stash-exposure.js +46 -0
- package/dist/commands/health/surfaces.js +216 -0
- package/dist/commands/health/task-runs.js +135 -0
- package/dist/commands/health/types.js +26 -0
- package/dist/commands/health/windows.js +195 -0
- package/dist/commands/health.js +91 -1091
- package/dist/commands/improve/anti-collapse.js +170 -0
- package/dist/commands/improve/calibration.js +161 -0
- package/dist/commands/improve/collapse-detector.js +421 -0
- package/dist/commands/improve/consolidate/chunking.js +141 -0
- package/dist/commands/improve/consolidate/eligibility.js +64 -0
- package/dist/commands/improve/consolidate/merge.js +145 -0
- package/dist/commands/improve/consolidate/sanitize.js +231 -0
- package/dist/commands/{lint.js → improve/consolidate/types.js} +1 -1
- package/dist/commands/improve/consolidate.js +1295 -1277
- package/dist/commands/improve/dedup.js +482 -0
- package/dist/commands/improve/distill/content-repair.js +202 -0
- package/dist/commands/improve/distill/promote-memory.js +229 -0
- package/dist/commands/improve/distill/quality-gate.js +236 -0
- package/dist/commands/improve/distill-guards.js +127 -0
- package/dist/commands/improve/distill-promotion-policy.js +826 -167
- package/dist/commands/improve/distill.js +228 -605
- package/dist/commands/improve/eligibility.js +434 -0
- package/dist/commands/improve/encoding-salience.js +205 -0
- package/dist/commands/improve/extract-cli.js +179 -59
- package/dist/commands/improve/extract-prompt.js +54 -3
- package/dist/commands/improve/extract-watch.js +140 -0
- package/dist/commands/improve/extract.js +409 -43
- package/dist/commands/improve/feedback-valence.js +54 -0
- package/dist/commands/improve/hot-probation.js +45 -0
- package/dist/commands/improve/improve-auto-accept.js +157 -10
- package/dist/commands/improve/improve-cli.js +115 -73
- package/dist/commands/improve/improve-profiles.js +28 -8
- package/dist/commands/improve/improve-result-file.js +15 -25
- package/dist/commands/improve/improve-session.js +58 -0
- package/dist/commands/improve/improve.js +485 -2764
- package/dist/commands/improve/locks.js +154 -0
- package/dist/commands/improve/loop-stages.js +1100 -0
- package/dist/commands/improve/memory/memory-belief.js +14 -15
- package/dist/commands/improve/memory/memory-contradiction-detect.js +83 -60
- package/dist/commands/improve/memory/memory-improve.js +27 -27
- package/dist/commands/improve/outcome-loop.js +270 -0
- package/dist/commands/improve/preparation.js +2002 -0
- package/dist/commands/improve/proactive-maintenance.js +37 -35
- package/dist/commands/improve/procedural.js +398 -0
- package/dist/commands/improve/recombine.js +818 -0
- package/dist/commands/improve/reflect-noise.js +0 -0
- package/dist/commands/improve/reflect.js +206 -45
- package/dist/commands/improve/salience.js +455 -0
- package/dist/commands/improve/schema-similarity-gate.js +168 -0
- package/dist/commands/improve/shared.js +51 -0
- package/dist/commands/improve/triage.js +93 -0
- package/dist/commands/lint/agent-linter.js +19 -24
- package/dist/commands/lint/base-linter.js +173 -60
- package/dist/commands/lint/command-linter.js +19 -24
- package/dist/commands/lint/env-key-rules.js +38 -1
- package/dist/commands/lint/fact-linter.js +39 -0
- package/dist/commands/lint/index.js +31 -13
- package/dist/commands/lint/memory-linter.js +1 -1
- package/dist/commands/lint/registry.js +7 -2
- package/dist/commands/lint/task-linter.js +3 -3
- package/dist/commands/lint/workflow-linter.js +26 -1
- package/dist/commands/observability-cli.js +4 -4
- package/dist/commands/proposal/drain-policies.js +13 -4
- package/dist/commands/proposal/drain.js +45 -51
- package/dist/commands/proposal/legacy-import.js +115 -0
- package/dist/commands/proposal/proposal-cli.js +24 -34
- package/dist/commands/proposal/proposal.js +2 -1
- package/dist/commands/proposal/propose.js +8 -3
- package/dist/commands/proposal/repository.js +829 -0
- package/dist/commands/proposal/validators/proposal-quality-validators.js +9 -8
- package/dist/commands/proposal/validators/proposals.js +93 -895
- package/dist/commands/read/curate.js +410 -111
- package/dist/commands/read/knowledge.js +10 -3
- package/dist/commands/read/remember-cli.js +133 -138
- package/dist/commands/read/search-cli.js +15 -8
- package/dist/commands/read/search.js +22 -11
- package/dist/commands/read/show.js +106 -14
- package/dist/commands/registry-cli.js +76 -87
- package/dist/commands/remember.js +11 -12
- package/dist/commands/sources/add-cli.js +91 -95
- package/dist/commands/sources/history.js +1 -1
- package/dist/commands/sources/init.js +66 -18
- package/dist/commands/sources/installed-stashes.js +11 -3
- package/dist/commands/sources/migration-help.js +7 -4
- package/dist/commands/sources/schema-repair.js +44 -46
- package/dist/commands/sources/self-update.js +2 -2
- package/dist/commands/sources/source-add.js +7 -3
- package/dist/commands/sources/sources-cli.js +3 -3
- package/dist/commands/sources/stash-cli.js +19 -39
- package/dist/commands/sources/stash-skeleton.js +57 -8
- package/dist/commands/tasks/default-tasks.js +15 -2
- package/dist/commands/tasks/tasks-cli.js +20 -29
- package/dist/commands/tasks/tasks.js +39 -11
- package/dist/commands/wiki-cli.js +23 -38
- package/dist/commands/workflow-cli.js +291 -13
- package/dist/core/asset/asset-registry.js +3 -1
- package/dist/core/asset/asset-spec.js +79 -5
- package/dist/core/asset/frontmatter.js +188 -167
- package/dist/core/asset/markdown.js +8 -0
- package/dist/core/authoring-rules.js +92 -0
- package/dist/core/common.js +4 -23
- package/dist/core/concurrent.js +10 -1
- package/dist/core/config/config-io.js +10 -1
- package/dist/core/config/config-migration.js +18 -40
- package/dist/core/config/config-schema.js +403 -62
- package/dist/core/config/config-types.js +3 -3
- package/dist/core/config/config.js +67 -22
- package/dist/core/deep-merge.js +38 -0
- package/dist/core/errors.js +1 -0
- package/dist/core/eval/rank-metrics.js +113 -0
- package/dist/core/events.js +4 -7
- package/dist/core/improve-types.js +47 -8
- package/dist/core/json-schema.js +142 -0
- package/dist/core/logs-db.js +14 -75
- package/dist/core/parse.js +36 -16
- package/dist/core/paths.js +18 -18
- package/dist/core/standards/resolve-standards-context.js +87 -0
- package/dist/core/standards/resolve-stash-standards.js +99 -0
- package/dist/core/standards/resolve-type-conventions.js +66 -0
- package/dist/core/state/migrations.js +770 -0
- package/dist/core/state-db.js +132 -1126
- package/dist/core/structured.js +69 -0
- package/dist/core/time.js +53 -0
- package/dist/core/warn.js +21 -0
- package/dist/core/write-source.js +37 -0
- package/dist/indexer/db/db.js +261 -770
- package/dist/indexer/db/entry-mapper.js +41 -0
- package/dist/indexer/db/graph-db.js +129 -86
- package/dist/indexer/db/llm-cache.js +2 -2
- package/dist/indexer/db/schema.js +516 -0
- package/dist/indexer/ensure-index.js +36 -92
- package/dist/indexer/feedback/utility-policy.js +75 -0
- package/dist/indexer/graph/graph-boost.js +51 -41
- package/dist/indexer/graph/graph-extraction.js +207 -4
- package/dist/indexer/index-writer-lock.js +18 -11
- package/dist/indexer/index-written-assets.js +105 -0
- package/dist/indexer/indexer.js +182 -204
- package/dist/indexer/passes/dir-staleness.js +114 -0
- package/dist/indexer/passes/memory-inference.js +13 -5
- package/dist/indexer/passes/metadata.js +20 -0
- package/dist/indexer/read-preflight.js +23 -0
- package/dist/indexer/search/db-search.js +89 -13
- package/dist/indexer/search/fts-query.js +51 -0
- package/dist/indexer/search/ranking-contributors.js +95 -9
- package/dist/indexer/search/ranking.js +79 -3
- package/dist/indexer/search/search-fields.js +6 -0
- package/dist/indexer/search/search-source.js +32 -21
- package/dist/indexer/search/semantic-status.js +4 -0
- package/dist/indexer/walk/matchers.js +48 -0
- package/dist/indexer/walk/walker.js +21 -13
- package/dist/integrations/agent/builders.js +41 -13
- package/dist/integrations/agent/config.js +20 -59
- package/dist/integrations/agent/detect.js +9 -0
- package/dist/integrations/agent/index.js +3 -19
- package/dist/integrations/agent/model-aliases.js +16 -2
- package/dist/integrations/agent/profiles.js +79 -6
- package/dist/integrations/agent/prompts.js +75 -9
- package/dist/integrations/agent/runner-dispatch.js +83 -0
- package/dist/integrations/agent/runner.js +13 -9
- package/dist/integrations/agent/spawn.js +206 -81
- package/dist/integrations/harnesses/aider/agent-builder.js +113 -0
- package/dist/integrations/harnesses/aider/index.js +58 -0
- package/dist/integrations/harnesses/aider/result-extractor.js +53 -0
- package/dist/integrations/harnesses/amazonq/agent-builder.js +153 -0
- package/dist/integrations/harnesses/amazonq/index.js +59 -0
- package/dist/integrations/harnesses/amazonq/result-extractor.js +48 -0
- package/dist/integrations/harnesses/claude/agent-builder.js +46 -7
- package/dist/integrations/harnesses/claude/index.js +27 -23
- package/dist/integrations/harnesses/claude/result-extractor.js +52 -0
- package/dist/integrations/harnesses/claude/session-log.js +10 -0
- package/dist/integrations/harnesses/codex/agent-builder.js +137 -0
- package/dist/integrations/harnesses/codex/index.js +63 -0
- package/dist/integrations/harnesses/codex/result-extractor.js +73 -0
- package/dist/integrations/harnesses/copilot/agent-builder.js +122 -0
- package/dist/integrations/harnesses/copilot/index.js +60 -0
- package/dist/integrations/harnesses/copilot/result-extractor.js +151 -0
- package/dist/integrations/harnesses/gemini/agent-builder.js +121 -0
- package/dist/integrations/harnesses/gemini/index.js +60 -0
- package/dist/integrations/harnesses/gemini/result-extractor.js +121 -0
- package/dist/integrations/harnesses/index.js +28 -7
- package/dist/integrations/harnesses/opencode/agent-builder.js +1 -1
- package/dist/integrations/harnesses/opencode/index.js +17 -16
- package/dist/integrations/harnesses/opencode/session-log.js +173 -3
- package/dist/integrations/harnesses/opencode-sdk/harness.js +65 -0
- package/dist/integrations/harnesses/opencode-sdk/index.js +10 -34
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +642 -71
- package/dist/integrations/harnesses/openhands/agent-builder.js +126 -0
- package/dist/integrations/harnesses/openhands/index.js +58 -0
- package/dist/integrations/harnesses/openhands/result-extractor.js +103 -0
- package/dist/integrations/harnesses/pi/agent-builder.js +104 -0
- package/dist/integrations/harnesses/pi/index.js +58 -0
- package/dist/integrations/harnesses/pi/result-extractor.js +135 -0
- package/dist/integrations/harnesses/types.js +8 -0
- package/dist/integrations/session-logs/index.js +40 -11
- package/dist/llm/call-ai.js +2 -2
- package/dist/llm/client.js +34 -11
- package/dist/llm/embedder.js +67 -4
- package/dist/llm/embedders/cache.js +3 -1
- package/dist/llm/embedders/deterministic.js +66 -0
- package/dist/llm/embedders/local.js +73 -3
- package/dist/llm/feature-gate.js +16 -15
- package/dist/llm/graph-extract.js +67 -44
- package/dist/llm/memory-infer-impl.js +138 -0
- package/dist/llm/memory-infer.js +1 -127
- package/dist/llm/metadata-enhance.js +44 -31
- package/dist/llm/structured-call.js +49 -0
- package/dist/migrate-storage-node.mjs +8 -0
- package/dist/output/context.js +5 -5
- package/dist/output/renderers.js +87 -15
- package/dist/output/shapes/curate.js +14 -2
- package/dist/output/shapes/helpers.js +0 -3
- package/dist/output/shapes/passthrough.js +6 -1
- package/dist/output/text/helpers.js +241 -2
- package/dist/output/text/workflow.js +4 -1
- package/dist/registry/providers/skills-sh.js +21 -147
- package/dist/registry/providers/static-index.js +15 -157
- package/dist/registry/resolve.js +27 -9
- package/dist/runtime.js +25 -1
- package/dist/schemas/akm-config.json +14225 -0
- package/dist/schemas/akm-workflow.json +328 -0
- package/dist/scripts/migrate-storage.js +2743 -8390
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +1652 -607
- package/dist/setup/detect.js +9 -0
- package/dist/setup/legacy-config.js +106 -0
- package/dist/setup/prompt.js +57 -0
- package/dist/setup/providers.js +14 -0
- package/dist/setup/registry-stash-loader.js +12 -0
- package/dist/setup/semantic-assets.js +124 -0
- package/dist/setup/setup.js +52 -1614
- package/dist/setup/steps/connection.js +734 -0
- package/dist/setup/steps/output.js +31 -0
- package/dist/setup/steps/platforms.js +124 -0
- package/dist/setup/steps/semantic.js +27 -0
- package/dist/setup/steps/sources.js +222 -0
- package/dist/setup/steps/stashdir.js +42 -0
- package/dist/setup/steps/tasks.js +152 -0
- package/dist/sources/include.js +6 -2
- package/dist/sources/providers/filesystem.js +0 -1
- package/dist/sources/providers/git-install.js +210 -0
- package/dist/sources/providers/git-provider.js +234 -0
- package/dist/sources/providers/git-stash.js +248 -0
- package/dist/sources/providers/git.js +10 -661
- package/dist/sources/providers/npm.js +2 -6
- package/dist/sources/providers/provider-utils.js +13 -7
- package/dist/sources/providers/sync-from-ref.js +9 -1
- package/dist/sources/providers/website.js +9 -5
- package/dist/sources/website-ingest.js +187 -29
- package/dist/sources/wiki-fetchers/registry.js +53 -0
- package/dist/sources/wiki-fetchers/youtube.js +239 -0
- package/dist/storage/database.js +45 -10
- package/dist/storage/managed-db.js +82 -0
- package/dist/storage/repositories/canaries-repository.js +107 -0
- package/dist/storage/repositories/consolidation-repository.js +38 -0
- package/dist/storage/repositories/embeddings-repository.js +72 -0
- package/dist/storage/repositories/events-repository.js +187 -0
- package/dist/storage/repositories/extract-sessions-repository.js +96 -0
- package/dist/storage/repositories/improve-runs-repository.js +146 -0
- package/dist/storage/repositories/index-db.js +14 -8
- package/dist/storage/repositories/proposals-repository.js +220 -0
- package/dist/storage/repositories/recombine-repository.js +213 -0
- package/dist/storage/repositories/registry-cache.js +93 -0
- package/dist/storage/repositories/registry-index-cache-repository.js +46 -0
- package/dist/storage/repositories/task-history-repository.js +93 -0
- package/dist/storage/repositories/workflow-runs-repository.js +189 -1
- package/dist/storage/sqlite-pragmas.js +146 -0
- package/dist/tasks/backends/cron.js +1 -1
- package/dist/tasks/backends/index.js +9 -0
- package/dist/tasks/backends/launchd.js +1 -1
- package/dist/tasks/backends/schtasks.js +1 -1
- package/dist/tasks/{resolveAkmBin.js → resolve-akm-bin.js} +2 -2
- package/dist/tasks/runner.js +15 -13
- package/dist/text-import-hook.mjs +1 -1
- package/dist/wiki/wiki.js +52 -11
- package/dist/workflows/authoring/authoring.js +123 -10
- package/dist/workflows/authoring/workflow-program-template.yaml +31 -0
- package/dist/workflows/cli.js +5 -0
- package/dist/workflows/db.js +138 -4
- package/dist/workflows/exec/brief.js +484 -0
- package/dist/workflows/exec/native-executor.js +975 -0
- package/dist/workflows/exec/param-secrets.js +115 -0
- package/dist/workflows/exec/report.js +1295 -0
- package/dist/workflows/exec/run-workflow.js +596 -0
- package/dist/workflows/exec/scheduler.js +100 -0
- package/dist/workflows/exec/step-work.js +1156 -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 +388 -0
- package/dist/workflows/ir/params.js +54 -0
- package/dist/workflows/ir/plan-hash.js +33 -0
- package/dist/workflows/ir/schema.js +4 -0
- package/dist/workflows/parser.js +3 -1
- package/dist/workflows/program/expressions.js +369 -0
- package/dist/workflows/program/parser.js +760 -0
- package/dist/workflows/program/project.js +105 -0
- package/dist/workflows/program/schema.js +54 -0
- package/dist/workflows/renderer.js +82 -5
- package/dist/workflows/runtime/agent-identity.js +59 -14
- package/dist/workflows/runtime/runs.js +248 -153
- package/dist/workflows/runtime/unit-checkin.js +45 -0
- package/dist/workflows/runtime/workflow-asset-loader.js +188 -0
- package/dist/workflows/validate-summary.js +26 -10
- package/dist/workflows/validator.js +1 -1
- package/docs/README.md +69 -18
- package/docs/data-and-telemetry.md +7 -5
- package/docs/migration/release-notes/0.7.0.md +1 -1
- package/docs/migration/release-notes/0.9.0-beta.60.md +19 -0
- package/docs/migration/release-notes/0.9.0.md +39 -0
- package/package.json +10 -10
- package/dist/assets/tasks/core/update-stashes.yml +0 -4
- package/dist/commands/db-cli.js +0 -23
- package/dist/indexer/db/db-backup.js +0 -376
- package/dist/indexer/passes/staleness-detect.js +0 -488
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import fs from "node:fs";
|
|
18
18
|
import os from "node:os";
|
|
19
19
|
import path from "node:path";
|
|
20
|
+
import { parseEmbeddedJsonResponse } from "../../core/parse.js";
|
|
20
21
|
import { spawn as runtimeSpawn } from "../../runtime.js";
|
|
21
22
|
import { getCommandBuilder } from "./builders.js";
|
|
22
23
|
import { DEFAULT_AGENT_TIMEOUT_MS } from "./config.js";
|
|
@@ -142,21 +143,93 @@ function buildChildEnv(profile, options) {
|
|
|
142
143
|
}
|
|
143
144
|
return env;
|
|
144
145
|
}
|
|
146
|
+
const STREAM_READ_TIMEOUT = Symbol("stream-read-timeout");
|
|
145
147
|
async function readStream(stream, opts) {
|
|
146
148
|
if (!stream)
|
|
147
|
-
return "";
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
149
|
+
return { text: "", timedOut: false };
|
|
150
|
+
const reader = stream.getReader();
|
|
151
|
+
const decoder = new TextDecoder();
|
|
152
|
+
let text = "";
|
|
153
|
+
if (!opts?.timeoutMs) {
|
|
154
|
+
try {
|
|
155
|
+
while (true) {
|
|
156
|
+
const chunk = await reader.read();
|
|
157
|
+
if (chunk.done)
|
|
158
|
+
break;
|
|
159
|
+
text += decoder.decode(chunk.value, { stream: true });
|
|
160
|
+
}
|
|
161
|
+
text += decoder.decode();
|
|
162
|
+
return { text, timedOut: false };
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
return { text, timedOut: false, error };
|
|
166
|
+
}
|
|
167
|
+
finally {
|
|
168
|
+
try {
|
|
169
|
+
reader.releaseLock();
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
/* ignore */
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
151
176
|
// Race the stream read against a timeout so a process that is killed via
|
|
152
177
|
// SIGTERM/SIGKILL but whose pipe endpoints stay open (e.g. background
|
|
153
178
|
// threads still holding the fd) cannot block the caller indefinitely.
|
|
154
|
-
// On timeout we return whatever
|
|
155
|
-
|
|
179
|
+
// On timeout we return whatever was decoded before the pipe stopped draining.
|
|
180
|
+
const setTimeoutImpl = opts.setTimeoutFn ?? setTimeout;
|
|
181
|
+
const clearTimeoutImpl = opts.clearTimeoutFn ?? clearTimeout;
|
|
182
|
+
let timer;
|
|
156
183
|
const timeoutPromise = new Promise((resolve) => {
|
|
157
|
-
|
|
184
|
+
timer = setTimeoutImpl(() => {
|
|
185
|
+
timer = undefined;
|
|
186
|
+
resolve(STREAM_READ_TIMEOUT);
|
|
187
|
+
}, opts.timeoutMs);
|
|
188
|
+
if (typeof timer !== "number")
|
|
189
|
+
timer.unref?.();
|
|
158
190
|
});
|
|
159
|
-
|
|
191
|
+
try {
|
|
192
|
+
while (true) {
|
|
193
|
+
const chunk = await Promise.race([reader.read(), timeoutPromise]);
|
|
194
|
+
if (chunk === STREAM_READ_TIMEOUT) {
|
|
195
|
+
void reader.cancel().catch(() => { });
|
|
196
|
+
return { text, timedOut: true };
|
|
197
|
+
}
|
|
198
|
+
if (chunk.done)
|
|
199
|
+
break;
|
|
200
|
+
text += decoder.decode(chunk.value, { stream: true });
|
|
201
|
+
}
|
|
202
|
+
text += decoder.decode();
|
|
203
|
+
return { text, timedOut: false };
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
return { text, timedOut: false, error };
|
|
207
|
+
}
|
|
208
|
+
finally {
|
|
209
|
+
if (timer !== undefined) {
|
|
210
|
+
clearTimeoutImpl(timer);
|
|
211
|
+
}
|
|
212
|
+
try {
|
|
213
|
+
reader.releaseLock();
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
/* ignore */
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
function streamFailureMessage(profileName, stdout, stderr) {
|
|
221
|
+
const failures = [];
|
|
222
|
+
if (stdout.error)
|
|
223
|
+
failures.push(`stdout read failed: ${stdout.error instanceof Error ? stdout.error.message : String(stdout.error)}`);
|
|
224
|
+
if (stderr.error)
|
|
225
|
+
failures.push(`stderr read failed: ${stderr.error instanceof Error ? stderr.error.message : String(stderr.error)}`);
|
|
226
|
+
if (stdout.timedOut)
|
|
227
|
+
failures.push("stdout drain timed out");
|
|
228
|
+
if (stderr.timedOut)
|
|
229
|
+
failures.push("stderr drain timed out");
|
|
230
|
+
if (failures.length === 0)
|
|
231
|
+
return undefined;
|
|
232
|
+
return `agent CLI "${profileName}" output capture failed: ${failures.join("; ")}`;
|
|
160
233
|
}
|
|
161
234
|
/**
|
|
162
235
|
* Spawn the agent CLI described by `profile` with `prompt` (forwarded as
|
|
@@ -183,6 +256,17 @@ export async function runAgent(profile, prompt, options = {}) {
|
|
|
183
256
|
const parseOutput = options.parseOutput ?? profile.parseOutput;
|
|
184
257
|
const setTimeoutImpl = options.setTimeoutFn ?? setTimeout;
|
|
185
258
|
const clearTimeoutImpl = options.clearTimeoutFn ?? clearTimeout;
|
|
259
|
+
// Observability seam — ids/status only, best-effort (see RunAgentOptions.onEvent).
|
|
260
|
+
const emitSpawnEvent = (type, data) => {
|
|
261
|
+
if (!options.onEvent)
|
|
262
|
+
return;
|
|
263
|
+
try {
|
|
264
|
+
options.onEvent({ type, data });
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
// Observability must never break the dispatch.
|
|
268
|
+
}
|
|
269
|
+
};
|
|
186
270
|
// Build argv via the platform-specific builder when dispatch params are
|
|
187
271
|
// provided; fall back to the legacy positional-prompt form otherwise.
|
|
188
272
|
let builtArgv;
|
|
@@ -203,6 +287,19 @@ export async function runAgent(profile, prompt, options = {}) {
|
|
|
203
287
|
const finalArgv = [...builtArgv, ...(options.dispatch ? (options.args ?? []) : [])];
|
|
204
288
|
const env = { ...buildChildEnv(profile, options), ...(builtEnv ?? {}) };
|
|
205
289
|
const start = Date.now();
|
|
290
|
+
// Cooperative cancel: refuse to spawn at all when the caller's signal is
|
|
291
|
+
// already aborted (e.g. the run's budget was exhausted before this unit).
|
|
292
|
+
if (options.signal?.aborted) {
|
|
293
|
+
return {
|
|
294
|
+
ok: false,
|
|
295
|
+
exitCode: null,
|
|
296
|
+
stdout: "",
|
|
297
|
+
stderr: "",
|
|
298
|
+
durationMs: 0,
|
|
299
|
+
reason: "aborted",
|
|
300
|
+
error: `agent CLI "${profile.name}" not started: caller signal already aborted`,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
206
303
|
let proc;
|
|
207
304
|
try {
|
|
208
305
|
const spawnFn = resolveSpawnFn(options);
|
|
@@ -211,7 +308,9 @@ export async function runAgent(profile, prompt, options = {}) {
|
|
|
211
308
|
stdout: stdioMode === "captured" ? "pipe" : "inherit",
|
|
212
309
|
stderr: stdioMode === "captured" ? "pipe" : "inherit",
|
|
213
310
|
env,
|
|
214
|
-
|
|
311
|
+
// options.cwd wins; dispatch.cwd is the request-level fallback (it was
|
|
312
|
+
// declared on AgentDispatchRequest but consumed by nothing — P0.5 fix).
|
|
313
|
+
...((options.cwd ?? options.dispatch?.cwd) ? { cwd: options.cwd ?? options.dispatch?.cwd } : {}),
|
|
215
314
|
// Spawn in its own process group so killGroup(-pid, signal) reaches all
|
|
216
315
|
// descendants (e.g. the .opencode binary that opencode's node wrapper forks).
|
|
217
316
|
// Only applied in captured mode — interactive mode inherits the parent
|
|
@@ -231,6 +330,10 @@ export async function runAgent(profile, prompt, options = {}) {
|
|
|
231
330
|
error: err instanceof Error ? err.message : String(err),
|
|
232
331
|
};
|
|
233
332
|
}
|
|
333
|
+
emitSpawnEvent("spawn_start", {
|
|
334
|
+
profile: profile.name,
|
|
335
|
+
...(typeof proc.pid === "number" ? { pid: proc.pid } : {}),
|
|
336
|
+
});
|
|
234
337
|
// Hard timeout. We prefer SIGTERM, then SIGKILL if SIGTERM is ignored,
|
|
235
338
|
// but the subprocess only exposes a single .kill() — one signal is enough
|
|
236
339
|
// for the structured-failure contract.
|
|
@@ -251,13 +354,40 @@ export async function runAgent(profile, prompt, options = {}) {
|
|
|
251
354
|
timedOut = true;
|
|
252
355
|
killGroup(proc, "SIGTERM");
|
|
253
356
|
// Follow up with SIGKILL after 5 s in case the process ignores SIGTERM.
|
|
254
|
-
setTimeoutImpl(() => {
|
|
357
|
+
const sigkillTimer = setTimeoutImpl(() => {
|
|
255
358
|
if (!proc || proc.exitCode !== null)
|
|
256
359
|
return;
|
|
257
360
|
killGroup(proc, "SIGKILL");
|
|
258
361
|
}, 5000);
|
|
362
|
+
if (typeof sigkillTimer !== "number")
|
|
363
|
+
sigkillTimer.unref?.();
|
|
259
364
|
}, timeoutMs);
|
|
260
365
|
}
|
|
366
|
+
// Cooperative cancel: same SIGTERM→SIGKILL discipline as the timeout, but
|
|
367
|
+
// flagged separately so the result carries `reason: "aborted"`.
|
|
368
|
+
let aborted = false;
|
|
369
|
+
const abortSignal = options.signal;
|
|
370
|
+
const onAbort = () => {
|
|
371
|
+
if (!proc || proc.exitCode !== null)
|
|
372
|
+
return;
|
|
373
|
+
aborted = true;
|
|
374
|
+
killGroup(proc, "SIGTERM");
|
|
375
|
+
const sigkillTimer = setTimeoutImpl(() => {
|
|
376
|
+
if (!proc || proc.exitCode !== null)
|
|
377
|
+
return;
|
|
378
|
+
killGroup(proc, "SIGKILL");
|
|
379
|
+
}, 5000);
|
|
380
|
+
if (typeof sigkillTimer !== "number")
|
|
381
|
+
sigkillTimer.unref?.();
|
|
382
|
+
};
|
|
383
|
+
if (abortSignal) {
|
|
384
|
+
// A signal that aborted between the pre-spawn check and here is handled
|
|
385
|
+
// by calling the listener directly.
|
|
386
|
+
if (abortSignal.aborted)
|
|
387
|
+
onAbort();
|
|
388
|
+
else
|
|
389
|
+
abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
390
|
+
}
|
|
261
391
|
// Stream-drain timeout: the overall wall-clock budget plus a 2 s grace
|
|
262
392
|
// period. When a process is killed via SIGTERM/SIGKILL (from our timeout
|
|
263
393
|
// handler or from outside) some runtimes keep the pipe write-end open in
|
|
@@ -267,11 +397,19 @@ export async function runAgent(profile, prompt, options = {}) {
|
|
|
267
397
|
// When there is no kill timer, allow up to 30 s for streams to drain.
|
|
268
398
|
const streamDrainTimeoutMs = timeoutMs !== null ? timeoutMs + 2_000 : 30_000;
|
|
269
399
|
const stdoutPromise = stdioMode === "captured"
|
|
270
|
-
? readStream(proc.stdout ?? null, {
|
|
271
|
-
|
|
400
|
+
? readStream(proc.stdout ?? null, {
|
|
401
|
+
timeoutMs: streamDrainTimeoutMs,
|
|
402
|
+
setTimeoutFn: setTimeoutImpl,
|
|
403
|
+
clearTimeoutFn: clearTimeoutImpl,
|
|
404
|
+
})
|
|
405
|
+
: Promise.resolve({ text: "", timedOut: false });
|
|
272
406
|
const stderrPromise = stdioMode === "captured"
|
|
273
|
-
? readStream(proc.stderr ?? null, {
|
|
274
|
-
|
|
407
|
+
? readStream(proc.stderr ?? null, {
|
|
408
|
+
timeoutMs: streamDrainTimeoutMs,
|
|
409
|
+
setTimeoutFn: setTimeoutImpl,
|
|
410
|
+
clearTimeoutFn: clearTimeoutImpl,
|
|
411
|
+
})
|
|
412
|
+
: Promise.resolve({ text: "", timedOut: false });
|
|
275
413
|
// Optional stdin payload (captured mode only).
|
|
276
414
|
//
|
|
277
415
|
// BUG-H1: race the stdin write/close against `proc.exited` and the
|
|
@@ -304,12 +442,19 @@ export async function runAgent(profile, prompt, options = {}) {
|
|
|
304
442
|
catch (err) {
|
|
305
443
|
if (timer !== undefined)
|
|
306
444
|
clearTimeoutImpl(timer);
|
|
445
|
+
abortSignal?.removeEventListener("abort", onAbort);
|
|
307
446
|
// BUG-H2: drain stream readers before the early return so they don't
|
|
308
447
|
// surface as unhandled rejections after the function resolves.
|
|
309
448
|
// The streams already carry a built-in drain timeout so this allSettled
|
|
310
449
|
// will not block indefinitely.
|
|
311
450
|
await Promise.allSettled([stdoutPromise, stderrPromise]);
|
|
312
451
|
const durationMs = Date.now() - start;
|
|
452
|
+
emitSpawnEvent("spawn_exit", {
|
|
453
|
+
profile: profile.name,
|
|
454
|
+
...(typeof proc.pid === "number" ? { pid: proc.pid } : {}),
|
|
455
|
+
exitCode: null,
|
|
456
|
+
status: "spawn_failed",
|
|
457
|
+
});
|
|
313
458
|
return {
|
|
314
459
|
ok: false,
|
|
315
460
|
exitCode: null,
|
|
@@ -321,8 +466,28 @@ export async function runAgent(profile, prompt, options = {}) {
|
|
|
321
466
|
};
|
|
322
467
|
}
|
|
323
468
|
clearTimeoutImpl(timer);
|
|
324
|
-
|
|
469
|
+
abortSignal?.removeEventListener("abort", onAbort);
|
|
470
|
+
emitSpawnEvent("spawn_exit", {
|
|
471
|
+
profile: profile.name,
|
|
472
|
+
...(typeof proc.pid === "number" ? { pid: proc.pid } : {}),
|
|
473
|
+
exitCode,
|
|
474
|
+
status: aborted ? "aborted" : timedOut ? "timeout" : exitCode !== 0 ? "non_zero_exit" : "ok",
|
|
475
|
+
});
|
|
476
|
+
const [stdoutRead, stderrRead] = await Promise.all([stdoutPromise, stderrPromise]);
|
|
477
|
+
const stdout = stdoutRead.text;
|
|
478
|
+
const stderr = stderrRead.text;
|
|
325
479
|
const durationMs = Date.now() - start;
|
|
480
|
+
if (aborted) {
|
|
481
|
+
return {
|
|
482
|
+
ok: false,
|
|
483
|
+
exitCode,
|
|
484
|
+
stdout,
|
|
485
|
+
stderr,
|
|
486
|
+
durationMs,
|
|
487
|
+
reason: "aborted",
|
|
488
|
+
error: `agent CLI "${profile.name}" aborted by caller signal`,
|
|
489
|
+
};
|
|
490
|
+
}
|
|
326
491
|
if (timedOut) {
|
|
327
492
|
return {
|
|
328
493
|
ok: false,
|
|
@@ -334,6 +499,18 @@ export async function runAgent(profile, prompt, options = {}) {
|
|
|
334
499
|
error: `agent CLI "${profile.name}" timed out after ${timeoutMs ?? 0}ms`,
|
|
335
500
|
};
|
|
336
501
|
}
|
|
502
|
+
const captureFailure = streamFailureMessage(profile.name, stdoutRead, stderrRead);
|
|
503
|
+
if (captureFailure) {
|
|
504
|
+
return {
|
|
505
|
+
ok: false,
|
|
506
|
+
exitCode,
|
|
507
|
+
stdout,
|
|
508
|
+
stderr,
|
|
509
|
+
durationMs,
|
|
510
|
+
reason: "spawn_failed",
|
|
511
|
+
error: captureFailure,
|
|
512
|
+
};
|
|
513
|
+
}
|
|
337
514
|
if (exitCode !== 0) {
|
|
338
515
|
return {
|
|
339
516
|
ok: false,
|
|
@@ -346,72 +523,20 @@ export async function runAgent(profile, prompt, options = {}) {
|
|
|
346
523
|
};
|
|
347
524
|
}
|
|
348
525
|
if (parseOutput === "json" && stdioMode === "captured") {
|
|
349
|
-
// Strip <think> blocks and code fences, then
|
|
350
|
-
//
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
// Fallback: extract the first balanced {…} from prose output.
|
|
364
|
-
let found;
|
|
365
|
-
for (let s = 0; s < cleaned.length; s++) {
|
|
366
|
-
if (cleaned[s] !== "{")
|
|
367
|
-
continue;
|
|
368
|
-
let depth = 0, inStr = false, esc = false;
|
|
369
|
-
for (let i = s; i < cleaned.length; i++) {
|
|
370
|
-
const c = cleaned[i];
|
|
371
|
-
if (inStr) {
|
|
372
|
-
if (esc) {
|
|
373
|
-
esc = false;
|
|
374
|
-
}
|
|
375
|
-
else if (c === "\\") {
|
|
376
|
-
esc = true;
|
|
377
|
-
}
|
|
378
|
-
else if (c === '"') {
|
|
379
|
-
inStr = false;
|
|
380
|
-
}
|
|
381
|
-
continue;
|
|
382
|
-
}
|
|
383
|
-
if (c === '"') {
|
|
384
|
-
inStr = true;
|
|
385
|
-
continue;
|
|
386
|
-
}
|
|
387
|
-
if (c === "{")
|
|
388
|
-
depth++;
|
|
389
|
-
if (c === "}") {
|
|
390
|
-
depth--;
|
|
391
|
-
if (depth === 0) {
|
|
392
|
-
try {
|
|
393
|
-
found = JSON.parse(cleaned.slice(s, i + 1));
|
|
394
|
-
}
|
|
395
|
-
catch { }
|
|
396
|
-
break;
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
if (found !== undefined)
|
|
401
|
-
break;
|
|
402
|
-
}
|
|
403
|
-
if (found === undefined) {
|
|
404
|
-
return {
|
|
405
|
-
ok: false,
|
|
406
|
-
exitCode,
|
|
407
|
-
stdout,
|
|
408
|
-
stderr,
|
|
409
|
-
durationMs,
|
|
410
|
-
reason: "parse_error",
|
|
411
|
-
error: "no JSON object found in agent output",
|
|
412
|
-
};
|
|
413
|
-
}
|
|
414
|
-
parsed = found;
|
|
526
|
+
// Strip <think> blocks and code fences, then parse with embedded-JSON
|
|
527
|
+
// fallback for local LLMs that emit prose around the payload. Handles
|
|
528
|
+
// both top-level `{…}` and `[…]` structures.
|
|
529
|
+
const parsed = parseEmbeddedJsonResponse(stdout);
|
|
530
|
+
if (parsed === undefined) {
|
|
531
|
+
return {
|
|
532
|
+
ok: false,
|
|
533
|
+
exitCode,
|
|
534
|
+
stdout,
|
|
535
|
+
stderr,
|
|
536
|
+
durationMs,
|
|
537
|
+
reason: "parse_error",
|
|
538
|
+
error: "no JSON structure found in agent output",
|
|
539
|
+
};
|
|
415
540
|
}
|
|
416
541
|
return { ok: true, exitCode, stdout, stderr, durationMs, parsed };
|
|
417
542
|
}
|
|
@@ -0,0 +1,113 @@
|
|
|
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
|
+
* Aider CLI agent command builder (P2, plan §"The adapter contract" step 2 /
|
|
6
|
+
* §"Capability matrix").
|
|
7
|
+
*
|
|
8
|
+
* Translates a platform-agnostic {@link AgentDispatchRequest} into the exact
|
|
9
|
+
* headless argv the `aider` CLI expects. Per the capability matrix the
|
|
10
|
+
* headless invocation is:
|
|
11
|
+
*
|
|
12
|
+
* aider -m "<p>" --yes-always
|
|
13
|
+
*
|
|
14
|
+
* with `--model <m>` for model selection, NO structured-output mode (Aider is
|
|
15
|
+
* the "none" tier — prompt-injected schema + embedded-JSON extraction), and
|
|
16
|
+
* NO session flag (resume is chat-history files, not an id).
|
|
17
|
+
*
|
|
18
|
+
* Platform-specific mapping decisions (all localized here, per the adapter
|
|
19
|
+
* contract):
|
|
20
|
+
*
|
|
21
|
+
* - **prompt** — `--message` is Aider's one-shot non-interactive mode (the
|
|
22
|
+
* matrix's `-m`; the long form is used for self-documenting argv). Unlike
|
|
23
|
+
* claude/codex/pi there is NO positional prompt and NO `--` end-of-options
|
|
24
|
+
* separator to hide behind: the prompt is a *flag value*. The glued
|
|
25
|
+
* `--message=<payload>` form is therefore used so a payload that begins
|
|
26
|
+
* with `-`/`--` binds to the flag lexically and can never be parsed as a
|
|
27
|
+
* separate option by Aider's argument parser.
|
|
28
|
+
* - **--yes-always** — always emitted: auto-confirms every interactive
|
|
29
|
+
* prompt (create file? run command? …), which is what makes a captured,
|
|
30
|
+
* unattended run possible. This is the matrix's documented headless shape.
|
|
31
|
+
* - **--no-pretty** — always emitted: disables colored/pretty terminal
|
|
32
|
+
* rendering so captured stdout is clean text for `./result-extractor.ts`
|
|
33
|
+
* (the same "dispatch is the captured path" reasoning as the Claude
|
|
34
|
+
* builder's unconditional `--print` and Codex's unconditional `--json`).
|
|
35
|
+
* - **systemPrompt** — Aider has no system-prompt flag (its nearest concept
|
|
36
|
+
* is conventions files via `--read`, which take a path, not text). Folded
|
|
37
|
+
* into the message payload — system text first, blank line, then the task —
|
|
38
|
+
* mirroring the codex builder's treatment.
|
|
39
|
+
* - **schema** — the matrix places Aider in the "via prompt+validate" tier
|
|
40
|
+
* with *no* structured output mode at all (plan §"Structured-output
|
|
41
|
+
* normalization", tier "none"): there is no schema flag and no JSON output
|
|
42
|
+
* flag, so the JSON Schema is injected into the message payload using the
|
|
43
|
+
* exact directive wording of the engine's prompt assembly
|
|
44
|
+
* (`step-work.ts` `buildUnitPrompt`) and the pi builder, so all
|
|
45
|
+
* dispatch paths speak one dialect. Downstream, embedded-JSON extraction +
|
|
46
|
+
* the engine's shared retry-until-valid loop supply the validation Aider
|
|
47
|
+
* lacks. No temp schema file is written — that is Codex's native-schema
|
|
48
|
+
* mechanism (`--output-schema`), which Aider does not have.
|
|
49
|
+
* - **tools** — deliberately unconsumed. Aider has no per-tool allowlist
|
|
50
|
+
* flag; tool-ish behaviour is governed by its own switches (`--yes-always`,
|
|
51
|
+
* git integration, shell-command confirmation). A restrictive policy is
|
|
52
|
+
* therefore dropped rather than approximated — never silently widened.
|
|
53
|
+
* - **resume/session** — NOT expressible: Aider persists context in
|
|
54
|
+
* chat-history files (`.aider.chat.history.md`), not session ids, so there
|
|
55
|
+
* is no flag-shaped `HarnessResumeSupport` to export and the extractor
|
|
56
|
+
* never yields a `sessionId`. akm's `workflow_run_units` remains the
|
|
57
|
+
* durable source of truth; resume works even against a harness with no
|
|
58
|
+
* session model (plan §"Session, MCP, and identity across harnesses" —
|
|
59
|
+
* Aider is the plan's named example).
|
|
60
|
+
* - **effort** — stays unconsumed (reserved; the shared request contract's
|
|
61
|
+
* "no builder consumes it yet" note stays true).
|
|
62
|
+
*
|
|
63
|
+
* NOT registered anywhere: `builders.ts` / `harnesses/index.ts` wiring is a
|
|
64
|
+
* follow-up integration task (as is the registry entry declaring
|
|
65
|
+
* `structuredOutput: "none"` and no `resume`). Exported standalone so that
|
|
66
|
+
* task only adds a registry entry.
|
|
67
|
+
*/
|
|
68
|
+
import { assertNotFlag } from "../../agent/builder-shared.js";
|
|
69
|
+
import { resolveModel } from "../../agent/model-aliases.js";
|
|
70
|
+
/** Canonical harness/platform id used for model-alias resolution. */
|
|
71
|
+
export const AIDER_PLATFORM = "aider";
|
|
72
|
+
/**
|
|
73
|
+
* Assemble the `--message` payload: optional system text, the task prompt,
|
|
74
|
+
* and — when a schema is requested — the same schema directive the workflow
|
|
75
|
+
* engine's prompt assembly uses (Aider has no native structured output, so
|
|
76
|
+
* the prompt is the only channel; plan §"Structured-output normalization",
|
|
77
|
+
* tier "none").
|
|
78
|
+
*/
|
|
79
|
+
function buildMessagePayload(req) {
|
|
80
|
+
const sections = [];
|
|
81
|
+
if (req.systemPrompt)
|
|
82
|
+
sections.push(req.systemPrompt);
|
|
83
|
+
sections.push(req.prompt);
|
|
84
|
+
if (req.schema) {
|
|
85
|
+
sections.push(`Respond with ONLY a JSON value matching this JSON Schema (no prose, no code fences):\n${JSON.stringify(req.schema)}`);
|
|
86
|
+
}
|
|
87
|
+
return sections.join("\n\n");
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Aider builder.
|
|
91
|
+
* Command shape:
|
|
92
|
+
* aider [--model <m>] --yes-always --no-pretty --message=<[system\n\n]prompt[\n\nschema directive]>
|
|
93
|
+
*/
|
|
94
|
+
export const aiderBuilder = {
|
|
95
|
+
platform: AIDER_PLATFORM,
|
|
96
|
+
build(profile, req) {
|
|
97
|
+
assertNotFlag(req.systemPrompt, "systemPrompt");
|
|
98
|
+
assertNotFlag(req.model, "model");
|
|
99
|
+
const args = [...profile.args];
|
|
100
|
+
if (req.model) {
|
|
101
|
+
const resolved = resolveModel(req.model, AIDER_PLATFORM, profile.modelAliases, profile.globalModelAliases);
|
|
102
|
+
args.push("--model", resolved);
|
|
103
|
+
}
|
|
104
|
+
// Headless essentials (matrix shape): auto-confirm everything, and keep
|
|
105
|
+
// captured stdout free of pretty/ANSI rendering for the extractor.
|
|
106
|
+
args.push("--yes-always");
|
|
107
|
+
args.push("--no-pretty");
|
|
108
|
+
// Glued form: the payload is a flag VALUE (no positional prompt exists),
|
|
109
|
+
// so `=` binding keeps a dash-leading payload from parsing as an option.
|
|
110
|
+
args.push(`--message=${buildMessagePayload(req)}`);
|
|
111
|
+
return { argv: [profile.bin, ...args] };
|
|
112
|
+
},
|
|
113
|
+
};
|
|
@@ -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
|
+
* Aider CLI harness (P2 integration, plan §"The adapter contract").
|
|
6
|
+
*
|
|
7
|
+
* Per-harness barrel gathering the Aider integration surfaces:
|
|
8
|
+
* - agent command builder → ./agent-builder.ts (aiderBuilder)
|
|
9
|
+
* - result extractor → ./result-extractor.ts (aiderResultExtractor)
|
|
10
|
+
*
|
|
11
|
+
* It also defines {@link AiderHarness}, 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 { aiderBuilder } from "./agent-builder.js";
|
|
17
|
+
import { aiderResultExtractor } from "./result-extractor.js";
|
|
18
|
+
export { AIDER_PLATFORM, aiderBuilder } from "./agent-builder.js";
|
|
19
|
+
export { aiderResultExtractor } 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
|
+
v1Migration: false,
|
|
28
|
+
...c,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Aider.
|
|
33
|
+
*
|
|
34
|
+
* Canonical id is `'aider'`; no alias or distinct runtime identity.
|
|
35
|
+
*/
|
|
36
|
+
export class AiderHarness extends BaseHarness {
|
|
37
|
+
id = "aider";
|
|
38
|
+
displayName = "Aider";
|
|
39
|
+
aliases = [];
|
|
40
|
+
agentBuilder = aiderBuilder;
|
|
41
|
+
resultExtractor = aiderResultExtractor;
|
|
42
|
+
// ── Workflow-engine descriptor (plan §"Capability matrix", P2) ────────────
|
|
43
|
+
// akm spawns the `aider` CLI locally per unit ⇒ local-runner.
|
|
44
|
+
pattern = "local-runner";
|
|
45
|
+
// No structured-output mode at all (the matrix's "none — parse output"):
|
|
46
|
+
// akm injects the schema into the prompt and extracts embedded JSON.
|
|
47
|
+
structuredOutput = "none";
|
|
48
|
+
// No `resume`: Aider persists context in chat-history files
|
|
49
|
+
// (`.aider.chat.history.md`), not session ids — the plan's named example of
|
|
50
|
+
// a harness with no session model. akm's `workflow_run_units` remains the
|
|
51
|
+
// durable resume source of truth.
|
|
52
|
+
// No `identityEnv`: the matrix lists Aider's identity markers as uncertain,
|
|
53
|
+
// and Aider stamps no session var onto child processes.
|
|
54
|
+
capabilities = caps({
|
|
55
|
+
agentDispatch: true,
|
|
56
|
+
detection: true,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
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
|
+
* ANSI escape sequences (CSI color/cursor codes). Defensive only: the builder
|
|
6
|
+
* always passes `--no-pretty`, but a user profile may override that.
|
|
7
|
+
* Constructed from a char code so no literal control character appears in
|
|
8
|
+
* source (lint-friendly, same bytes).
|
|
9
|
+
*/
|
|
10
|
+
const ANSI_ESCAPE_RE = new RegExp(`${String.fromCharCode(27)}\\[[0-9;?]*[ -/]*[@-~]`, "g");
|
|
11
|
+
/**
|
|
12
|
+
* Aider's own announcement lines — banner, model/repo configuration echo,
|
|
13
|
+
* chat-history notices, edit/commit/usage reports. All patterns are anchored
|
|
14
|
+
* at line start and match documented phrasings only; anything else is treated
|
|
15
|
+
* as reply content.
|
|
16
|
+
*/
|
|
17
|
+
const NOISE_LINE_PATTERNS = [
|
|
18
|
+
/^aider v\d/, // version banner
|
|
19
|
+
/^(Main model|Weak model|Editor model|Model):\s/, // model announcements
|
|
20
|
+
/^Git repo:\s/, // repo detection
|
|
21
|
+
/^Repo-map:\s/, // repo-map status
|
|
22
|
+
/^Added .+ to the chat\.?$/, // file add notices
|
|
23
|
+
/^Restored previous conversation history\.?$/, // chat-history reload notice
|
|
24
|
+
/^Use \/help\b/, // onboarding hint line
|
|
25
|
+
/^Tokens: .+/, // usage/cost footer
|
|
26
|
+
/^Cost: .+/, // standalone cost footer
|
|
27
|
+
/^Applied edit to .+/, // edit application notices
|
|
28
|
+
/^Commit [0-9a-f]{6,}\b/, // auto-commit notices
|
|
29
|
+
/^You can use \/undo\b/, // undo hint after auto-commit
|
|
30
|
+
/^[─━-]{4,}$/, // ── / ━━ / ---- separator rules
|
|
31
|
+
];
|
|
32
|
+
function isNoiseLine(line) {
|
|
33
|
+
return NOISE_LINE_PATTERNS.some((pattern) => pattern.test(line));
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Normalize a raw aider run result into `{ text, sessionId? }`.
|
|
37
|
+
* See the module doc for the stdout shape handled.
|
|
38
|
+
*/
|
|
39
|
+
export const aiderResultExtractor = (result) => {
|
|
40
|
+
const raw = result.stdout.replace(ANSI_ESCAPE_RE, "").trim();
|
|
41
|
+
const kept = raw
|
|
42
|
+
.split("\n")
|
|
43
|
+
.filter((line) => !isNoiseLine(line.trim()))
|
|
44
|
+
.join("\n")
|
|
45
|
+
.trim();
|
|
46
|
+
// Never return empty text when stdout had content: an all-noise filter
|
|
47
|
+
// outcome means the patterns over-matched for this capture, so fall back to
|
|
48
|
+
// the full trimmed stdout (downstream parsing still gets material).
|
|
49
|
+
const text = kept.length > 0 ? kept : raw;
|
|
50
|
+
// No session model (chat-history files, not ids): only a spawn-layer id
|
|
51
|
+
// passes through; output never supplies one.
|
|
52
|
+
return result.sessionId === undefined ? { text } : { text, sessionId: result.sessionId };
|
|
53
|
+
};
|