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
|
@@ -0,0 +1,126 @@
|
|
|
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
|
+
* OpenHands CLI agent command builder (P2, plan §"The adapter contract"
|
|
6
|
+
* step 2 / §"Capability matrix").
|
|
7
|
+
*
|
|
8
|
+
* Translates a platform-agnostic {@link AgentDispatchRequest} into the exact
|
|
9
|
+
* headless argv the `openhands` CLI expects. Per the capability matrix the
|
|
10
|
+
* headless invocation is:
|
|
11
|
+
*
|
|
12
|
+
* openhands --headless -t "<p>" --json
|
|
13
|
+
*
|
|
14
|
+
* with `--json` producing the documented JSONL event stream (structured tier
|
|
15
|
+
* "via prompt+validate"), workspace-state resume (no session-id flag), and
|
|
16
|
+
* native MCP support.
|
|
17
|
+
*
|
|
18
|
+
* Platform-specific mapping decisions (all localized here, per the adapter
|
|
19
|
+
* contract):
|
|
20
|
+
*
|
|
21
|
+
* - **prompt** — `--task` is the long form of the matrix's `-t` (used for
|
|
22
|
+
* self-documenting argv, mirroring the aider builder's `--message`). Like
|
|
23
|
+
* aider — and unlike claude/codex/pi — there is NO positional prompt and NO
|
|
24
|
+
* `--` end-of-options separator to hide behind: the task is a *flag value*.
|
|
25
|
+
* The glued `--task=<payload>` form is therefore used so a payload that
|
|
26
|
+
* begins with `-`/`--` binds to the flag lexically and can never be parsed
|
|
27
|
+
* as a separate option by the CLI's argument parser.
|
|
28
|
+
* - **--headless / --json** — always emitted: `--headless` is what makes a
|
|
29
|
+
* captured, unattended run possible, and `--json` switches stdout to the
|
|
30
|
+
* JSONL event stream that `./result-extractor.ts` normalizes (the same
|
|
31
|
+
* "dispatch is the captured path" reasoning as the Claude builder's
|
|
32
|
+
* unconditional `--print`; both flags are the matrix's documented headless
|
|
33
|
+
* shape).
|
|
34
|
+
* - **systemPrompt** — OpenHands headless mode has no documented
|
|
35
|
+
* system-prompt flag (agent behaviour is shaped by its own agent configs
|
|
36
|
+
* and microagents). Folded into the task payload — system text first, blank
|
|
37
|
+
* line, then the task — mirroring the aider/codex treatment.
|
|
38
|
+
* - **model** — OpenHands does not take the model on the headless command
|
|
39
|
+
* line; its documented configuration channel is the `LLM_MODEL` environment
|
|
40
|
+
* variable (config.toml `[llm] model` equivalent). The alias is resolved via
|
|
41
|
+
* `resolveModel` with this platform id and returned on `BuiltCommand.env`
|
|
42
|
+
* (the seam exists precisely for platform-specific extras), NOT as an
|
|
43
|
+
* invented `--model` flag that would produce a silently broken command.
|
|
44
|
+
* - **schema** — the matrix places OpenHands in the "via prompt+validate"
|
|
45
|
+
* tier (plan §"Structured-output normalization", tier "native-json"): no
|
|
46
|
+
* Codex-style `--output-schema` flag exists, so NO temp schema file is
|
|
47
|
+
* written; the JSON Schema is injected into the task payload using the
|
|
48
|
+
* exact directive wording of the engine's prompt assembly
|
|
49
|
+
* (`step-work.ts` `buildUnitPrompt`) and the pi/aider builders, so
|
|
50
|
+
* all dispatch paths speak one dialect. Downstream, the extractor pulls the
|
|
51
|
+
* final message out of the JSONL stream and the engine's shared
|
|
52
|
+
* retry-until-valid loop performs the actual validation.
|
|
53
|
+
* - **tools** — deliberately unconsumed. OpenHands has no per-tool allowlist
|
|
54
|
+
* flag; tool access is governed by its own runtime/sandbox configuration
|
|
55
|
+
* and (natively supported) MCP config. A restrictive policy is therefore
|
|
56
|
+
* dropped rather than approximated — never silently widened.
|
|
57
|
+
* - **resume/session** — NOT expressible: per the matrix OpenHands resumes
|
|
58
|
+
* from *workspace state*, not a session-id flag, so there is no flag-shaped
|
|
59
|
+
* `HarnessResumeSupport` to export. The extractor still captures a
|
|
60
|
+
* conversation/session id opportunistically when the stream reveals one;
|
|
61
|
+
* akm's `workflow_run_units` remains the durable source of truth either way
|
|
62
|
+
* (plan §"Session, MCP, and identity across harnesses").
|
|
63
|
+
* - **effort** — stays unconsumed (reserved; the shared request contract's
|
|
64
|
+
* "no builder consumes it yet" note stays true).
|
|
65
|
+
*
|
|
66
|
+
* NOT registered anywhere: `builders.ts` / `harnesses/index.ts` wiring is a
|
|
67
|
+
* follow-up integration task (as is the registry entry declaring
|
|
68
|
+
* `pattern: "local-runner"`, `structuredOutput: "native-json"`, and no
|
|
69
|
+
* `resume`). Exported standalone so that task only adds a registry entry.
|
|
70
|
+
*/
|
|
71
|
+
import { assertNotFlag } from "../../agent/builder-shared.js";
|
|
72
|
+
import { resolveModel } from "../../agent/model-aliases.js";
|
|
73
|
+
/** Canonical harness/platform id used for model-alias resolution. */
|
|
74
|
+
export const OPENHANDS_PLATFORM = "openhands";
|
|
75
|
+
/**
|
|
76
|
+
* Env var OpenHands reads its LLM model from — the platform's documented
|
|
77
|
+
* model-selection channel for headless runs (there is no headless model
|
|
78
|
+
* flag). Exported so the follow-up integration task and tests share the one
|
|
79
|
+
* constant.
|
|
80
|
+
*/
|
|
81
|
+
export const OPENHANDS_MODEL_ENV = "LLM_MODEL";
|
|
82
|
+
/**
|
|
83
|
+
* Assemble the `--task` payload: optional system text, the task prompt, and —
|
|
84
|
+
* when a schema is requested — the same schema directive the workflow
|
|
85
|
+
* engine's prompt assembly uses (OpenHands has no native schema flag, so the
|
|
86
|
+
* prompt is the schema's only channel; plan §"Structured-output
|
|
87
|
+
* normalization").
|
|
88
|
+
*/
|
|
89
|
+
function buildTaskPayload(req) {
|
|
90
|
+
const sections = [];
|
|
91
|
+
if (req.systemPrompt)
|
|
92
|
+
sections.push(req.systemPrompt);
|
|
93
|
+
sections.push(req.prompt);
|
|
94
|
+
if (req.schema) {
|
|
95
|
+
sections.push(`Respond with ONLY a JSON value matching this JSON Schema (no prose, no code fences):\n${JSON.stringify(req.schema)}`);
|
|
96
|
+
}
|
|
97
|
+
return sections.join("\n\n");
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* OpenHands builder.
|
|
101
|
+
* Command shape:
|
|
102
|
+
* openhands --headless --json --task=<[system\n\n]prompt[\n\nschema directive]>
|
|
103
|
+
* with the resolved model (if any) carried on env as LLM_MODEL.
|
|
104
|
+
*/
|
|
105
|
+
export const openhandsBuilder = {
|
|
106
|
+
platform: OPENHANDS_PLATFORM,
|
|
107
|
+
build(profile, req) {
|
|
108
|
+
assertNotFlag(req.systemPrompt, "systemPrompt");
|
|
109
|
+
assertNotFlag(req.model, "model");
|
|
110
|
+
const args = [...profile.args];
|
|
111
|
+
// Headless essentials (matrix shape): non-interactive run, JSONL stdout
|
|
112
|
+
// for the extractor.
|
|
113
|
+
args.push("--headless");
|
|
114
|
+
args.push("--json");
|
|
115
|
+
// Glued form: the payload is a flag VALUE (no positional prompt exists),
|
|
116
|
+
// so `=` binding keeps a dash-leading payload from parsing as an option.
|
|
117
|
+
args.push(`--task=${buildTaskPayload(req)}`);
|
|
118
|
+
let env;
|
|
119
|
+
if (req.model) {
|
|
120
|
+
const resolved = resolveModel(req.model, OPENHANDS_PLATFORM, profile.modelAliases, profile.globalModelAliases);
|
|
121
|
+
// Model travels via env, not argv — OpenHands' documented channel.
|
|
122
|
+
env = { [OPENHANDS_MODEL_ENV]: resolved };
|
|
123
|
+
}
|
|
124
|
+
return { argv: [profile.bin, ...args], ...(env ? { env } : {}) };
|
|
125
|
+
},
|
|
126
|
+
};
|
|
@@ -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
|
+
* OpenHands CLI harness (P2 integration, plan §"The adapter contract").
|
|
6
|
+
*
|
|
7
|
+
* Per-harness barrel gathering the OpenHands integration surfaces:
|
|
8
|
+
* - agent command builder → ./agent-builder.ts (openhandsBuilder)
|
|
9
|
+
* - result extractor → ./result-extractor.ts (openhandsResultExtractor)
|
|
10
|
+
*
|
|
11
|
+
* It also defines {@link OpenhandsHarness}, the {@link AkmHarness} descriptor
|
|
12
|
+
* that `HARNESS_REGISTRY` registers. Dispatch-only: no native session-log
|
|
13
|
+
* reader or config importer yet.
|
|
14
|
+
*/
|
|
15
|
+
import { BaseHarness } from "../types.js";
|
|
16
|
+
import { openhandsBuilder } from "./agent-builder.js";
|
|
17
|
+
import { openhandsResultExtractor } from "./result-extractor.js";
|
|
18
|
+
export { OPENHANDS_MODEL_ENV, OPENHANDS_PLATFORM, openhandsBuilder } from "./agent-builder.js";
|
|
19
|
+
export { openhandsResultExtractor } 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
|
+
* OpenHands CLI.
|
|
33
|
+
*
|
|
34
|
+
* Canonical id is `'openhands'`; no alias or distinct runtime identity.
|
|
35
|
+
*/
|
|
36
|
+
export class OpenhandsHarness extends BaseHarness {
|
|
37
|
+
id = "openhands";
|
|
38
|
+
displayName = "OpenHands";
|
|
39
|
+
aliases = [];
|
|
40
|
+
agentBuilder = openhandsBuilder;
|
|
41
|
+
resultExtractor = openhandsResultExtractor;
|
|
42
|
+
// ── Workflow-engine descriptor (plan §"Capability matrix", P2) ────────────
|
|
43
|
+
// akm spawns `openhands --headless` locally per unit ⇒ local-runner.
|
|
44
|
+
pattern = "local-runner";
|
|
45
|
+
// `--json` emits a documented JSONL event stream akm parses, then validates
|
|
46
|
+
// against the node schema ⇒ native-json tier.
|
|
47
|
+
structuredOutput = "native-json";
|
|
48
|
+
// No `resume`: per the matrix OpenHands resumes from workspace state, not a
|
|
49
|
+
// session-id flag. The extractor still captures a conversation id
|
|
50
|
+
// opportunistically; akm's `workflow_run_units` remains the durable source
|
|
51
|
+
// of truth.
|
|
52
|
+
// No `identityEnv`: the matrix lists OpenHands' identity markers as
|
|
53
|
+
// uncertain, and it stamps no session var onto child processes.
|
|
54
|
+
capabilities = caps({
|
|
55
|
+
agentDispatch: true,
|
|
56
|
+
detection: true,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
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
|
+
/** Keys that may carry the harness-native session id, in precedence order. */
|
|
8
|
+
const SESSION_KEYS = ["session_id", "sessionId", "sid", "conversation_id"];
|
|
9
|
+
/** Return a non-empty string, else undefined. */
|
|
10
|
+
function nonEmptyString(value) {
|
|
11
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Extract the agent's answer text from one parsed OpenHands event, if the
|
|
15
|
+
* event is one of the two message-bearing kinds (see module doc). Everything
|
|
16
|
+
* else — user echoes, observations, tool actions — yields undefined.
|
|
17
|
+
*/
|
|
18
|
+
function extractAgentText(event) {
|
|
19
|
+
// Only agent-authored events count; a missing `source` is tolerated for
|
|
20
|
+
// finish/message actions from older serializations, but an explicit
|
|
21
|
+
// non-agent source (user echo, environment) never contributes.
|
|
22
|
+
if (event.source !== undefined && event.source !== "agent")
|
|
23
|
+
return undefined;
|
|
24
|
+
if (typeof event.action !== "string")
|
|
25
|
+
return undefined;
|
|
26
|
+
const args = isRecord(event.args) ? event.args : undefined;
|
|
27
|
+
if (event.action === "message") {
|
|
28
|
+
return nonEmptyString(args?.content) ?? nonEmptyString(event.message);
|
|
29
|
+
}
|
|
30
|
+
if (event.action === "finish") {
|
|
31
|
+
return nonEmptyString(args?.final_thought) ?? nonEmptyString(args?.thought) ?? nonEmptyString(event.message);
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
/** Extract a harness-native session id from one parsed JSON value, if any. */
|
|
36
|
+
function extractSessionId(event) {
|
|
37
|
+
for (const key of SESSION_KEYS) {
|
|
38
|
+
const candidate = nonEmptyString(event[key]);
|
|
39
|
+
if (candidate)
|
|
40
|
+
return candidate;
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
/** JSON.parse that returns undefined instead of throwing. */
|
|
45
|
+
function tryParseJson(raw) {
|
|
46
|
+
try {
|
|
47
|
+
return JSON.parse(raw);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Normalize a raw openhands run result into `{ text, sessionId? }`.
|
|
55
|
+
* See the module doc for the stdout shapes handled.
|
|
56
|
+
*/
|
|
57
|
+
export const openhandsResultExtractor = (result) => {
|
|
58
|
+
// The raw result's own sessionId (SDK-style paths) is the fallback; an id
|
|
59
|
+
// found in the output stream wins because it is fresher.
|
|
60
|
+
const fallbackSessionId = result.sessionId;
|
|
61
|
+
const raw = result.stdout.trim();
|
|
62
|
+
// Shape 1 — whole stdout is one JSON document (spawn may have pre-parsed it
|
|
63
|
+
// when the profile requested parseOutput: "json"). A multi-event JSONL
|
|
64
|
+
// stream never parses as a single document, so this cannot shadow shape 2.
|
|
65
|
+
const whole = result.parsed !== undefined ? result.parsed : raw.length > 0 ? tryParseJson(raw) : undefined;
|
|
66
|
+
if (whole !== undefined) {
|
|
67
|
+
if (typeof whole === "string") {
|
|
68
|
+
return { text: whole, ...(fallbackSessionId ? { sessionId: fallbackSessionId } : {}) };
|
|
69
|
+
}
|
|
70
|
+
if (isRecord(whole)) {
|
|
71
|
+
const sessionId = extractSessionId(whole) ?? fallbackSessionId;
|
|
72
|
+
// Unknown envelope (no agent message): fall back to raw stdout so
|
|
73
|
+
// downstream embedded-JSON parsing still has material to work with.
|
|
74
|
+
const text = extractAgentText(whole) ?? raw;
|
|
75
|
+
return { text, ...(sessionId ? { sessionId } : {}) };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Shape 2 — JSONL event stream (`--json`): last message-bearing agent event
|
|
79
|
+
// wins; first id-bearing event supplies the session id. Non-JSON banner
|
|
80
|
+
// lines are skipped.
|
|
81
|
+
let lastText;
|
|
82
|
+
let streamSessionId;
|
|
83
|
+
let sawJsonLine = false;
|
|
84
|
+
for (const line of raw.split("\n")) {
|
|
85
|
+
const trimmed = line.trim();
|
|
86
|
+
if (trimmed.length === 0)
|
|
87
|
+
continue;
|
|
88
|
+
const event = tryParseJson(trimmed);
|
|
89
|
+
if (event === undefined || !isRecord(event))
|
|
90
|
+
continue;
|
|
91
|
+
sawJsonLine = true;
|
|
92
|
+
streamSessionId ??= extractSessionId(event);
|
|
93
|
+
const text = extractAgentText(event);
|
|
94
|
+
if (text !== undefined)
|
|
95
|
+
lastText = text;
|
|
96
|
+
}
|
|
97
|
+
if (sawJsonLine) {
|
|
98
|
+
const sessionId = streamSessionId ?? fallbackSessionId;
|
|
99
|
+
return { text: lastText ?? raw, ...(sessionId ? { sessionId } : {}) };
|
|
100
|
+
}
|
|
101
|
+
// Shape 3 — plain text passthrough.
|
|
102
|
+
return { text: raw, ...(fallbackSessionId ? { sessionId: fallbackSessionId } : {}) };
|
|
103
|
+
};
|
|
@@ -0,0 +1,104 @@
|
|
|
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
|
+
* Pi coding-agent CLI command builder (P2, plan §"The adapter contract"
|
|
6
|
+
* step 2 / §"Capability matrix").
|
|
7
|
+
*
|
|
8
|
+
* Translates a platform-agnostic {@link AgentDispatchRequest} into the exact
|
|
9
|
+
* headless argv the `pi` CLI expects. Per the capability matrix the headless
|
|
10
|
+
* invocation is:
|
|
11
|
+
*
|
|
12
|
+
* pi -p "<prompt>"
|
|
13
|
+
*
|
|
14
|
+
* with `--mode json` for structured (JSONL) output, `--model <m>` for model
|
|
15
|
+
* selection, and `-c`/`-r`/`--session <id>` for resume. Resume is
|
|
16
|
+
* registry-side (`AkmHarness.resume`, flag-shaped: {@link PI_RESUME_FLAG}) —
|
|
17
|
+
* not built here because `AgentDispatchRequest` carries no session id.
|
|
18
|
+
*
|
|
19
|
+
* Platform-specific mapping decisions (all localized here, per the adapter
|
|
20
|
+
* contract):
|
|
21
|
+
*
|
|
22
|
+
* - **prompt** — `-p` is Pi's non-interactive print mode (same convention as
|
|
23
|
+
* Claude Code's `--print`); the prompt is the trailing positional message.
|
|
24
|
+
* The `--` end-of-options separator precedes it, mirroring the claude/codex
|
|
25
|
+
* builders, so a prompt whose text begins with `-`/`--` can never be parsed
|
|
26
|
+
* as flags.
|
|
27
|
+
* - **systemPrompt** — passed via `--system-prompt` (Pi follows the Claude
|
|
28
|
+
* Code flag conventions), guarded by `assertNotFlag`.
|
|
29
|
+
* - **schema** — the matrix places Pi in the "via prompt+validate" tier (no
|
|
30
|
+
* native `--output-schema` equivalent, unlike Codex), so the JSON Schema is
|
|
31
|
+
* passed through the prompt: a directive matching the engine's wording
|
|
32
|
+
* (`step-work.ts` `buildUnitPrompt`) is appended to the prompt
|
|
33
|
+
* payload, and `--mode json` is emitted so stdout is the documented JSONL
|
|
34
|
+
* event stream that `./result-extractor.ts` normalizes. The engine's shared
|
|
35
|
+
* retry-until-valid loop performs the actual validation. Without a schema
|
|
36
|
+
* the argv matches the matrix's bare headless shape (`pi -p "<p>"`) and the
|
|
37
|
+
* extractor's plain-text path applies.
|
|
38
|
+
* - **tools** — deliberately unconsumed. Pi manages tool access through its
|
|
39
|
+
* own config/extension system (the matrix lists MCP as "extensions only");
|
|
40
|
+
* there is no documented per-tool allowlist flag, and inventing one would
|
|
41
|
+
* produce a silently broken command. A restrictive policy is therefore
|
|
42
|
+
* dropped rather than approximated — never silently widened.
|
|
43
|
+
* - **effort** — stays unconsumed (reserved; the shared request contract's
|
|
44
|
+
* "no builder consumes it yet" note stays true).
|
|
45
|
+
*
|
|
46
|
+
* NOT registered anywhere: `builders.ts` / `harnesses/index.ts` wiring is a
|
|
47
|
+
* follow-up integration task (as are the `PI_*` identity-env markers, which
|
|
48
|
+
* are registry-side). Exported standalone so that task only adds a registry
|
|
49
|
+
* entry.
|
|
50
|
+
*/
|
|
51
|
+
import { assertNotFlag } from "../../agent/builder-shared.js";
|
|
52
|
+
import { resolveModel } from "../../agent/model-aliases.js";
|
|
53
|
+
/** Canonical harness/platform id used for model-alias resolution. */
|
|
54
|
+
export const PI_PLATFORM = "pi";
|
|
55
|
+
/**
|
|
56
|
+
* Flag-shaped resume support per the capability matrix (`--session <id>`).
|
|
57
|
+
* Exported for the integration task's `AkmHarness.resume` registry entry; the
|
|
58
|
+
* harness-native session id comes from the unit row (stored opportunistically
|
|
59
|
+
* by the result extractor) — akm never depends on it (plan §"Session, MCP,
|
|
60
|
+
* and identity across harnesses").
|
|
61
|
+
*/
|
|
62
|
+
export const PI_RESUME_FLAG = "--session";
|
|
63
|
+
/**
|
|
64
|
+
* Assemble the positional prompt payload: the task prompt and — when a schema
|
|
65
|
+
* is requested — the same schema directive the workflow engine's prompt
|
|
66
|
+
* assembly uses, so both dispatch paths speak one dialect.
|
|
67
|
+
*/
|
|
68
|
+
function buildPromptPayload(req) {
|
|
69
|
+
const sections = [req.prompt];
|
|
70
|
+
if (req.schema) {
|
|
71
|
+
sections.push(`Respond with ONLY a JSON value matching this JSON Schema (no prose, no code fences):\n${JSON.stringify(req.schema)}`);
|
|
72
|
+
}
|
|
73
|
+
return sections.join("\n\n");
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Pi builder.
|
|
77
|
+
* Command shape:
|
|
78
|
+
* pi [--system-prompt "..."] [--model <m>] [--mode json] -p -- "<prompt[\n\nschema directive]>"
|
|
79
|
+
*/
|
|
80
|
+
export const piBuilder = {
|
|
81
|
+
platform: PI_PLATFORM,
|
|
82
|
+
build(profile, req) {
|
|
83
|
+
assertNotFlag(req.systemPrompt, "systemPrompt");
|
|
84
|
+
assertNotFlag(req.model, "model");
|
|
85
|
+
const args = [...profile.args];
|
|
86
|
+
if (req.systemPrompt) {
|
|
87
|
+
args.push("--system-prompt", req.systemPrompt);
|
|
88
|
+
}
|
|
89
|
+
if (req.model) {
|
|
90
|
+
const resolved = resolveModel(req.model, PI_PLATFORM, profile.modelAliases, profile.globalModelAliases);
|
|
91
|
+
args.push("--model", resolved);
|
|
92
|
+
}
|
|
93
|
+
if (req.schema) {
|
|
94
|
+
// Structured unit: JSONL event stream on stdout — the pi result
|
|
95
|
+
// extractor's documented input (prompt+validate tier).
|
|
96
|
+
args.push("--mode", "json");
|
|
97
|
+
}
|
|
98
|
+
// -p = non-interactive print mode; prompt is the trailing positional.
|
|
99
|
+
args.push("-p");
|
|
100
|
+
args.push("--");
|
|
101
|
+
args.push(buildPromptPayload(req));
|
|
102
|
+
return { argv: [profile.bin, ...args] };
|
|
103
|
+
},
|
|
104
|
+
};
|
|
@@ -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
|
+
* Pi coding-agent CLI harness (P2 integration, plan §"The adapter contract").
|
|
6
|
+
*
|
|
7
|
+
* Per-harness barrel gathering the Pi integration surfaces:
|
|
8
|
+
* - agent command builder → ./agent-builder.ts (piBuilder)
|
|
9
|
+
* - result extractor → ./result-extractor.ts (piResultExtractor)
|
|
10
|
+
*
|
|
11
|
+
* It also defines {@link PiHarness}, 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 { PI_RESUME_FLAG, piBuilder } from "./agent-builder.js";
|
|
17
|
+
import { piResultExtractor } from "./result-extractor.js";
|
|
18
|
+
export { PI_PLATFORM, PI_RESUME_FLAG, piBuilder } from "./agent-builder.js";
|
|
19
|
+
export { piResultExtractor } 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
|
+
* Pi coding-agent CLI.
|
|
33
|
+
*
|
|
34
|
+
* Canonical id is `'pi'`; no alias or distinct runtime identity.
|
|
35
|
+
*/
|
|
36
|
+
export class PiHarness extends BaseHarness {
|
|
37
|
+
id = "pi";
|
|
38
|
+
displayName = "Pi";
|
|
39
|
+
aliases = [];
|
|
40
|
+
agentBuilder = piBuilder;
|
|
41
|
+
resultExtractor = piResultExtractor;
|
|
42
|
+
// ── Workflow-engine descriptor (plan §"Capability matrix", P2) ────────────
|
|
43
|
+
// akm spawns the `pi` CLI locally per unit ⇒ local-runner.
|
|
44
|
+
pattern = "local-runner";
|
|
45
|
+
// `--mode json` emits a documented JSONL event stream akm parses, then
|
|
46
|
+
// validates against the node schema ⇒ native-json tier.
|
|
47
|
+
structuredOutput = "native-json";
|
|
48
|
+
// `pi --session <id>` replays a previous session (the matrix's -c/-r/
|
|
49
|
+
// --session family; the id-taking long form is the registered flag).
|
|
50
|
+
resume = { flag: PI_RESUME_FLAG, takesSessionId: true };
|
|
51
|
+
// Session-id env marker only — the matrix's bare PI_* presence vars must
|
|
52
|
+
// not stamp identity onto manual runs (see `AkmHarness.identityEnv`).
|
|
53
|
+
identityEnv = ["PI_SESSION_ID"];
|
|
54
|
+
capabilities = caps({
|
|
55
|
+
agentDispatch: true,
|
|
56
|
+
detection: true,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
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
|
+
/** Keys that may carry the harness-native session id, in precedence order. */
|
|
8
|
+
const SESSION_KEYS = ["session_id", "sessionId", "session"];
|
|
9
|
+
/**
|
|
10
|
+
* Coerce a candidate value into text. Handles the shapes Pi nests assistant
|
|
11
|
+
* answers in: bare strings, arrays of content blocks, and `{content|text}`
|
|
12
|
+
* wrappers. Blocks without a `content`/`text` string (thinking, tool-use)
|
|
13
|
+
* contribute nothing. Depth-bounded: JSON.parse output is acyclic, but
|
|
14
|
+
* nesting is capped anyway so a pathological envelope cannot recurse
|
|
15
|
+
* unboundedly.
|
|
16
|
+
*/
|
|
17
|
+
function textOf(value, depth = 0) {
|
|
18
|
+
if (depth > 4)
|
|
19
|
+
return undefined;
|
|
20
|
+
if (typeof value === "string")
|
|
21
|
+
return value;
|
|
22
|
+
if (Array.isArray(value)) {
|
|
23
|
+
const parts = [];
|
|
24
|
+
for (const block of value) {
|
|
25
|
+
const t = textOf(block, depth + 1);
|
|
26
|
+
if (t)
|
|
27
|
+
parts.push(t);
|
|
28
|
+
}
|
|
29
|
+
return parts.length > 0 ? parts.join("\n") : undefined;
|
|
30
|
+
}
|
|
31
|
+
if (isRecord(value)) {
|
|
32
|
+
return textOf(value.content ?? value.text, depth + 1);
|
|
33
|
+
}
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
/** Extract the text of one assistant-authored message record, if it is one. */
|
|
37
|
+
function assistantMessageText(message) {
|
|
38
|
+
if (!isRecord(message) || message.role !== "assistant")
|
|
39
|
+
return undefined;
|
|
40
|
+
return textOf(message.content ?? message.text);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Extract assistant text from one parsed JSON event/value:
|
|
44
|
+
* `message` envelopes (`message_start`/`message_end`), `messages` transcripts
|
|
45
|
+
* (`agent_end` — last assistant entry wins), and tolerant flat
|
|
46
|
+
* `role:"assistant"` events.
|
|
47
|
+
*/
|
|
48
|
+
function extractAssistantText(event) {
|
|
49
|
+
const fromEnvelope = assistantMessageText(event.message);
|
|
50
|
+
if (fromEnvelope !== undefined)
|
|
51
|
+
return fromEnvelope;
|
|
52
|
+
const transcript = event.messages;
|
|
53
|
+
if (Array.isArray(transcript)) {
|
|
54
|
+
for (let i = transcript.length - 1; i >= 0; i--) {
|
|
55
|
+
const text = assistantMessageText(transcript[i]);
|
|
56
|
+
if (text !== undefined)
|
|
57
|
+
return text;
|
|
58
|
+
}
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
return assistantMessageText(event);
|
|
62
|
+
}
|
|
63
|
+
/** Extract a harness-native session id from one parsed JSON value, if any. */
|
|
64
|
+
function extractSessionId(event) {
|
|
65
|
+
for (const key of SESSION_KEYS) {
|
|
66
|
+
const candidate = event[key];
|
|
67
|
+
if (typeof candidate === "string" && candidate.length > 0)
|
|
68
|
+
return candidate;
|
|
69
|
+
}
|
|
70
|
+
// Session-lifecycle events may carry the id under a bare `id` key.
|
|
71
|
+
if (typeof event.type === "string" && event.type.startsWith("session") && typeof event.id === "string" && event.id) {
|
|
72
|
+
return event.id;
|
|
73
|
+
}
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
/** JSON.parse that returns undefined instead of throwing. */
|
|
77
|
+
function tryParseJson(raw) {
|
|
78
|
+
try {
|
|
79
|
+
return JSON.parse(raw);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Normalize a raw pi run result into `{ text, sessionId? }`.
|
|
87
|
+
* See the module doc for the stdout shapes handled.
|
|
88
|
+
*/
|
|
89
|
+
export const piResultExtractor = (result) => {
|
|
90
|
+
// The raw result's own sessionId (SDK-style paths) is the fallback; an id
|
|
91
|
+
// found in the output stream wins because it is fresher.
|
|
92
|
+
const fallbackSessionId = result.sessionId;
|
|
93
|
+
const raw = result.stdout.trim();
|
|
94
|
+
// Shape 1 — whole stdout is one JSON document (spawn may have pre-parsed it
|
|
95
|
+
// when the profile requested parseOutput: "json"). A multi-event JSONL
|
|
96
|
+
// stream never parses as a single document, so this cannot shadow shape 2.
|
|
97
|
+
const whole = result.parsed !== undefined ? result.parsed : raw.length > 0 ? tryParseJson(raw) : undefined;
|
|
98
|
+
if (whole !== undefined) {
|
|
99
|
+
if (typeof whole === "string") {
|
|
100
|
+
return { text: whole, ...(fallbackSessionId ? { sessionId: fallbackSessionId } : {}) };
|
|
101
|
+
}
|
|
102
|
+
if (isRecord(whole)) {
|
|
103
|
+
const sessionId = extractSessionId(whole) ?? fallbackSessionId;
|
|
104
|
+
// Unknown envelope (no assistant text): fall back to raw stdout so
|
|
105
|
+
// downstream embedded-JSON parsing still has material to work with.
|
|
106
|
+
const text = extractAssistantText(whole) ?? textOf(whole) ?? raw;
|
|
107
|
+
return { text, ...(sessionId ? { sessionId } : {}) };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// Shape 2 — JSONL event stream (`--mode json`): last assistant text-bearing
|
|
111
|
+
// event wins; first session-id-bearing event supplies the id. Non-JSON
|
|
112
|
+
// banner lines are skipped.
|
|
113
|
+
let lastText;
|
|
114
|
+
let streamSessionId;
|
|
115
|
+
let sawJsonLine = false;
|
|
116
|
+
for (const line of raw.split("\n")) {
|
|
117
|
+
const trimmed = line.trim();
|
|
118
|
+
if (trimmed.length === 0)
|
|
119
|
+
continue;
|
|
120
|
+
const event = tryParseJson(trimmed);
|
|
121
|
+
if (event === undefined || !isRecord(event))
|
|
122
|
+
continue;
|
|
123
|
+
sawJsonLine = true;
|
|
124
|
+
streamSessionId ??= extractSessionId(event);
|
|
125
|
+
const text = extractAssistantText(event);
|
|
126
|
+
if (text !== undefined && text.length > 0)
|
|
127
|
+
lastText = text;
|
|
128
|
+
}
|
|
129
|
+
if (sawJsonLine) {
|
|
130
|
+
const sessionId = streamSessionId ?? fallbackSessionId;
|
|
131
|
+
return { text: lastText ?? raw, ...(sessionId ? { sessionId } : {}) };
|
|
132
|
+
}
|
|
133
|
+
// Shape 3 — plain text passthrough.
|
|
134
|
+
return { text: raw, ...(fallbackSessionId ? { sessionId: fallbackSessionId } : {}) };
|
|
135
|
+
};
|
|
@@ -13,6 +13,14 @@
|
|
|
13
13
|
export class BaseHarness {
|
|
14
14
|
runtimeId;
|
|
15
15
|
setupDetectionDir;
|
|
16
|
+
agentBuilder;
|
|
17
|
+
pattern;
|
|
18
|
+
structuredOutput;
|
|
19
|
+
resume;
|
|
20
|
+
identityEnv;
|
|
21
|
+
presenceEnv;
|
|
22
|
+
resultExtractor;
|
|
23
|
+
sessionLogProvider;
|
|
16
24
|
/**
|
|
17
25
|
* Lowercase prefixes that a decorated v1 profile name may start with and
|
|
18
26
|
* still belong to this harness (e.g. `["opencode-sdk"]`). The canonical id
|