akm-cli 0.8.0-rc2 → 0.8.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/{.github/CHANGELOG.md → CHANGELOG.md} +238 -3
- package/README.md +22 -6
- package/SECURITY.md +93 -0
- package/dist/assets/help/help-accept.md +12 -0
- package/dist/assets/help/help-improve.md +81 -0
- package/dist/{commands → assets}/help/help-proposals.md +7 -4
- package/dist/assets/help/help-reject.md +11 -0
- package/dist/{output → assets/hints}/cli-hints-full.md +60 -32
- package/dist/{output → assets/hints}/cli-hints-short.md +10 -7
- package/dist/assets/profiles/default.json +15 -0
- package/dist/assets/profiles/graph-refresh.json +13 -0
- package/dist/assets/profiles/memory-focus.json +12 -0
- package/dist/assets/profiles/quick.json +15 -0
- package/dist/assets/profiles/thorough.json +15 -0
- package/dist/assets/prompts/extract-session.md +80 -0
- package/dist/assets/prompts/graph-extract-user-prompt.md +35 -0
- package/dist/assets/tasks/graph-refresh-weekly.yml +10 -0
- package/dist/cli/config-migrate.js +144 -0
- package/dist/cli/config-validate.js +39 -0
- package/dist/cli/confirm.js +73 -0
- package/dist/cli/parse-args.js +93 -3
- package/dist/cli/shared.js +129 -0
- package/dist/cli.js +2141 -1268
- package/dist/commands/add-cli.js +279 -0
- package/dist/commands/agent-dispatch.js +20 -12
- package/dist/commands/agent-support.js +11 -5
- package/dist/commands/completions.js +3 -0
- package/dist/commands/config-cli.js +129 -517
- package/dist/commands/consolidate.js +1557 -147
- package/dist/commands/curate.js +44 -3
- package/dist/commands/db-cli.js +23 -0
- package/dist/commands/distill-promotion-policy.js +5 -3
- package/dist/commands/distill.js +906 -100
- package/dist/commands/env.js +213 -0
- package/dist/commands/eval-cases.js +3 -0
- package/dist/commands/events.js +3 -0
- package/dist/commands/extract-cli.js +127 -0
- package/dist/commands/extract-prompt.js +217 -0
- package/dist/commands/extract.js +477 -0
- package/dist/commands/feedback-cli.js +331 -0
- package/dist/commands/graph.js +260 -5
- package/dist/commands/health.js +1042 -55
- package/dist/commands/history.js +51 -16
- package/dist/commands/improve-auto-accept.js +97 -0
- package/dist/commands/improve-cli.js +236 -0
- package/dist/commands/improve-profiles.js +138 -0
- package/dist/commands/improve-result-file.js +167 -0
- package/dist/commands/improve.js +1736 -346
- package/dist/commands/info.js +26 -28
- package/dist/commands/init.js +49 -1
- package/dist/commands/installed-stashes.js +6 -23
- package/dist/commands/knowledge.js +3 -0
- package/dist/commands/lint/agent-linter.js +3 -0
- package/dist/commands/lint/base-linter.js +199 -5
- package/dist/commands/lint/command-linter.js +3 -0
- package/dist/commands/lint/default-linter.js +3 -0
- package/dist/commands/lint/env-key-rules.js +154 -0
- package/dist/commands/lint/index.js +92 -3
- package/dist/commands/lint/knowledge-linter.js +3 -0
- package/dist/commands/lint/markdown-insertion.js +343 -0
- package/dist/commands/lint/memory-linter.js +3 -0
- package/dist/commands/lint/registry.js +3 -0
- package/dist/commands/lint/skill-linter.js +3 -0
- package/dist/commands/lint/task-linter.js +15 -12
- package/dist/commands/lint/types.js +3 -0
- package/dist/commands/lint/workflow-linter.js +3 -0
- package/dist/commands/lint.js +3 -0
- package/dist/commands/migration-help.js +5 -2
- package/dist/commands/proposal-drain-policies.js +128 -0
- package/dist/commands/proposal-drain.js +477 -0
- package/dist/commands/proposal.js +60 -6
- package/dist/commands/propose.js +24 -19
- package/dist/commands/reflect.js +1004 -94
- package/dist/commands/registry-cli.js +150 -0
- package/dist/commands/registry-search.js +3 -0
- package/dist/commands/remember-cli.js +257 -0
- package/dist/commands/remember.js +15 -6
- package/dist/commands/schema-repair.js +88 -15
- package/dist/commands/search.js +99 -14
- package/dist/commands/secret.js +173 -0
- package/dist/commands/self-update.js +3 -0
- package/dist/commands/show.js +32 -13
- package/dist/commands/source-add.js +7 -35
- package/dist/commands/source-clone.js +3 -0
- package/dist/commands/source-manage.js +3 -0
- package/dist/commands/tasks.js +161 -95
- package/dist/commands/url-checker.js +3 -0
- package/dist/core/action-contributors.js +3 -0
- package/dist/core/asset-ref.js +13 -2
- package/dist/core/asset-registry.js +9 -2
- package/dist/core/asset-serialize.js +88 -0
- package/dist/core/asset-spec.js +61 -5
- package/dist/core/common.js +93 -5
- package/dist/core/concurrent.js +3 -0
- package/dist/core/config-io.js +347 -0
- package/dist/core/config-migration.js +622 -0
- package/dist/core/config-schema.js +558 -0
- package/dist/core/config-sources.js +108 -0
- package/dist/core/config-types.js +4 -0
- package/dist/core/config-walker.js +337 -0
- package/dist/core/config.js +366 -1077
- package/dist/core/errors.js +42 -20
- package/dist/core/events.js +31 -25
- package/dist/core/file-lock.js +104 -0
- package/dist/core/frontmatter.js +75 -10
- package/dist/core/lesson-lint.js +3 -0
- package/dist/core/markdown.js +3 -0
- package/dist/core/memory-belief.js +62 -0
- package/dist/core/memory-contradiction-detect.js +274 -0
- package/dist/core/memory-improve.js +142 -14
- package/dist/core/parse.js +3 -0
- package/dist/core/paths.js +218 -50
- package/dist/core/proposal-quality-validators.js +380 -0
- package/dist/core/proposal-validators.js +11 -3
- package/dist/core/proposals.js +464 -5
- package/dist/core/state-db.js +349 -56
- package/dist/core/text-truncation.js +107 -0
- package/dist/core/time.js +3 -0
- package/dist/core/tty.js +59 -0
- package/dist/core/warn.js +7 -2
- package/dist/core/write-source.js +12 -0
- package/dist/indexer/db-backup.js +391 -0
- package/dist/indexer/db-search.js +136 -28
- package/dist/indexer/db.js +661 -166
- package/dist/indexer/ensure-index.js +3 -0
- package/dist/indexer/file-context.js +3 -0
- package/dist/indexer/graph-boost.js +162 -40
- package/dist/indexer/graph-db.js +241 -51
- package/dist/indexer/graph-dedup.js +3 -7
- package/dist/indexer/graph-extraction.js +242 -149
- package/dist/indexer/index-context.js +3 -9
- package/dist/indexer/indexer.js +86 -16
- package/dist/indexer/llm-cache.js +24 -19
- package/dist/indexer/manifest.js +3 -0
- package/dist/indexer/matchers.js +184 -11
- package/dist/indexer/memory-inference.js +94 -50
- package/dist/indexer/metadata-contributors.js +3 -0
- package/dist/indexer/metadata.js +110 -50
- package/dist/indexer/path-resolver.js +3 -0
- package/dist/indexer/project-context.js +192 -0
- package/dist/indexer/ranking-contributors.js +134 -7
- package/dist/indexer/ranking.js +8 -1
- package/dist/indexer/search-fields.js +5 -9
- package/dist/indexer/search-hit-enrichers.js +91 -2
- package/dist/indexer/search-source.js +20 -1
- package/dist/indexer/semantic-status.js +4 -1
- package/dist/indexer/staleness-detect.js +447 -0
- package/dist/indexer/usage-events.js +12 -9
- package/dist/indexer/walker.js +3 -0
- package/dist/integrations/agent/builders.js +135 -0
- package/dist/integrations/agent/config.js +121 -401
- package/dist/integrations/agent/detect.js +3 -0
- package/dist/integrations/agent/index.js +6 -14
- package/dist/integrations/agent/model-aliases.js +55 -0
- package/dist/integrations/agent/profiles.js +3 -0
- package/dist/integrations/agent/prompts.js +137 -8
- package/dist/integrations/agent/runner.js +208 -0
- package/dist/integrations/agent/sdk-runner.js +8 -2
- package/dist/integrations/agent/spawn.js +54 -14
- package/dist/integrations/github.js +3 -0
- package/dist/integrations/lockfile.js +22 -51
- package/dist/integrations/session-logs/index.js +4 -0
- package/dist/integrations/session-logs/inline-refs.js +35 -0
- package/dist/integrations/session-logs/pre-filter.js +152 -0
- package/dist/integrations/session-logs/providers/claude-code.js +226 -0
- package/dist/integrations/session-logs/providers/opencode.js +231 -25
- package/dist/integrations/session-logs/types.js +3 -0
- package/dist/llm/call-ai.js +14 -26
- package/dist/llm/client.js +16 -2
- package/dist/llm/embedder.js +20 -29
- package/dist/llm/embedders/cache.js +3 -7
- package/dist/llm/embedders/local.js +42 -1
- package/dist/llm/embedders/remote.js +20 -8
- package/dist/llm/embedders/types.js +3 -7
- package/dist/llm/feature-gate.js +92 -56
- package/dist/llm/graph-extract.js +402 -31
- package/dist/llm/index-passes.js +44 -29
- package/dist/llm/memory-infer.js +30 -2
- package/dist/llm/metadata-enhance.js +3 -7
- package/dist/output/cli-hints.js +7 -4
- package/dist/output/context.js +60 -8
- package/dist/output/renderers.js +170 -194
- package/dist/output/shapes/curate.js +56 -0
- package/dist/output/shapes/distill.js +10 -0
- package/dist/output/shapes/env-list.js +19 -0
- package/dist/output/shapes/events.js +11 -0
- package/dist/output/shapes/helpers.js +424 -0
- package/dist/output/shapes/history.js +7 -0
- package/dist/output/shapes/passthrough.js +105 -0
- package/dist/output/shapes/proposal-accept.js +7 -0
- package/dist/output/shapes/proposal-diff.js +7 -0
- package/dist/output/shapes/proposal-list.js +7 -0
- package/dist/output/shapes/proposal-producer.js +11 -0
- package/dist/output/shapes/proposal-reject.js +7 -0
- package/dist/output/shapes/proposal-show.js +7 -0
- package/dist/output/shapes/registry-search.js +6 -0
- package/dist/output/shapes/registry.js +30 -0
- package/dist/output/shapes/search.js +6 -0
- package/dist/output/shapes/secret-list.js +19 -0
- package/dist/output/shapes/show.js +6 -0
- package/dist/output/shapes/vault-list.js +19 -0
- package/dist/output/shapes.js +51 -549
- package/dist/output/text/add.js +6 -0
- package/dist/output/text/clone.js +6 -0
- package/dist/output/text/config.js +6 -0
- package/dist/output/text/curate.js +6 -0
- package/dist/output/text/distill.js +7 -0
- package/dist/output/text/enable-disable.js +7 -0
- package/dist/output/text/events.js +10 -0
- package/dist/output/text/feedback.js +6 -0
- package/dist/output/text/helpers.js +1059 -0
- package/dist/output/text/history.js +7 -0
- package/dist/output/text/import.js +6 -0
- package/dist/output/text/index.js +6 -0
- package/dist/output/text/info.js +6 -0
- package/dist/output/text/init.js +6 -0
- package/dist/output/text/list.js +6 -0
- package/dist/output/text/proposal-producer.js +8 -0
- package/dist/output/text/proposal.js +12 -0
- package/dist/output/text/registry-commands.js +11 -0
- package/dist/output/text/registry.js +30 -0
- package/dist/output/text/remember.js +6 -0
- package/dist/output/text/remove.js +6 -0
- package/dist/output/text/save.js +6 -0
- package/dist/output/text/search.js +6 -0
- package/dist/output/text/show.js +6 -0
- package/dist/output/text/update.js +6 -0
- package/dist/output/text/upgrade.js +6 -0
- package/dist/output/text/vault.js +16 -0
- package/dist/output/text/wiki.js +15 -0
- package/dist/output/text/workflow.js +14 -0
- package/dist/output/text.js +44 -1329
- package/dist/registry/build-index.js +3 -0
- package/dist/registry/create-provider-registry.js +3 -0
- package/dist/registry/factory.js +4 -1
- package/dist/registry/origin-resolve.js +3 -0
- package/dist/registry/providers/index.js +3 -0
- package/dist/registry/providers/skills-sh.js +11 -2
- package/dist/registry/providers/static-index.js +10 -1
- package/dist/registry/providers/types.js +3 -24
- package/dist/registry/resolve.js +11 -16
- package/dist/registry/types.js +3 -0
- package/dist/scripts/migrate-storage.js +17767 -0
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +9031 -0
- package/dist/scripts/migrations/v16-to-v17.js +141 -0
- package/dist/setup/detect.js +3 -0
- package/dist/setup/ripgrep-install.js +3 -0
- package/dist/setup/ripgrep-resolve.js +3 -0
- package/dist/setup/setup.js +306 -67
- package/dist/setup/steps.js +3 -15
- package/dist/sources/include.js +3 -0
- package/dist/sources/provider-factory.js +3 -11
- package/dist/sources/provider.js +3 -20
- package/dist/sources/providers/filesystem.js +19 -23
- package/dist/sources/providers/git.js +171 -21
- package/dist/sources/providers/index.js +3 -0
- package/dist/sources/providers/install-types.js +3 -13
- package/dist/sources/providers/npm.js +3 -4
- package/dist/sources/providers/provider-utils.js +3 -0
- package/dist/sources/providers/sync-from-ref.js +3 -11
- package/dist/sources/providers/tar-utils.js +3 -0
- package/dist/sources/providers/website.js +18 -22
- package/dist/sources/resolve.js +3 -0
- package/dist/sources/types.js +3 -0
- package/dist/sources/website-ingest.js +3 -0
- package/dist/tasks/backends/cron.js +3 -0
- package/dist/tasks/backends/exec-utils.js +3 -0
- package/dist/tasks/backends/index.js +3 -11
- package/dist/tasks/backends/launchd.js +4 -1
- package/dist/tasks/backends/schtasks.js +4 -1
- package/dist/tasks/parser.js +51 -38
- package/dist/tasks/resolveAkmBin.js +3 -0
- package/dist/tasks/runner.js +35 -9
- package/dist/tasks/schedule.js +20 -1
- package/dist/tasks/schema.js +5 -3
- package/dist/tasks/validator.js +6 -3
- package/dist/version.js +3 -0
- package/dist/wiki/wiki-templates.js +6 -3
- package/dist/wiki/wiki.js +4 -1
- package/dist/workflows/authoring.js +4 -1
- package/dist/workflows/cli.js +3 -0
- package/dist/workflows/db.js +140 -10
- package/dist/workflows/document-cache.js +3 -10
- package/dist/workflows/parser.js +3 -0
- package/dist/workflows/renderer.js +3 -0
- package/dist/workflows/runs.js +18 -1
- package/dist/workflows/schema.js +3 -0
- package/dist/workflows/scope-key.js +3 -0
- package/dist/workflows/validator.js +5 -9
- package/docs/README.md +7 -2
- package/docs/data-and-telemetry.md +225 -0
- package/docs/migration/release-notes/0.7.5.md +2 -2
- package/docs/migration/release-notes/0.8.0.md +57 -5
- package/docs/migration/v0.7-to-v0.8.md +1378 -0
- package/package.json +28 -11
- package/.github/LICENSE +0 -374
- package/dist/commands/help/help-accept.md +0 -9
- package/dist/commands/help/help-improve.md +0 -53
- package/dist/commands/help/help-reject.md +0 -8
- package/dist/commands/install-audit.js +0 -385
- package/dist/commands/vault.js +0 -310
- package/dist/indexer/match-contributors.js +0 -141
- package/dist/integrations/agent/pipeline.js +0 -39
- package/dist/integrations/agent/runners.js +0 -31
- package/dist/llm/prompts/graph-extract-user-prompt.md +0 -12
- /package/dist/{tasks → assets}/backends/launchd-template.xml +0 -0
- /package/dist/{tasks → assets}/backends/schtasks-template.xml +0 -0
- /package/dist/{commands → assets}/help/help-propose.md +0 -0
- /package/dist/{wiki → assets/wiki}/index-template.md +0 -0
- /package/dist/{wiki → assets/wiki}/ingest-workflow-template.md +0 -0
- /package/dist/{wiki → assets/wiki}/log-template.md +0 -0
- /package/dist/{wiki → assets/wiki}/schema-template.md +0 -0
- /package/dist/{workflows → assets/workflows}/workflow-template.md +0 -0
|
@@ -1,3 +1,6 @@
|
|
|
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/.
|
|
1
4
|
const COMMON_PASSTHROUGH = ["HOME", "PATH", "USER", "LANG", "LC_ALL", "TERM", "TMPDIR"];
|
|
2
5
|
/**
|
|
3
6
|
* Built-in profiles for the five agent CLIs the v1 spec calls out
|
|
@@ -1,3 +1,6 @@
|
|
|
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/.
|
|
1
4
|
/**
|
|
2
5
|
* Shared prompt builders for proposal-producing agent commands (#226).
|
|
3
6
|
*
|
|
@@ -38,7 +41,8 @@ const TYPE_HINTS = {
|
|
|
38
41
|
memory: "memory assets are short factual notes the user wants persisted across sessions. Frontmatter usually includes `description`.",
|
|
39
42
|
workflow: "workflow assets are markdown describing a multi-step process. Include `# <Title>` and ordered `## Step N` sections.",
|
|
40
43
|
script: "script assets are executable text files. Include a shebang and minimal usage comment.",
|
|
41
|
-
|
|
44
|
+
env: "env assets are `.env` files holding a group of related CONFIGURATION for an app/service (KEY=VALUE pairs, `#` comments) — URLs, flags, and any credentials it needs. Values may or may not be sensitive; all are protected (key names discoverable, values stay on disk). Inject with `akm env run env:<name> -- <cmd>` (the safe path — values never reach stdout/your context); do NOT run `akm env export` and read its output, as that prints values. For a single sensitive value used on its own for authentication (token, key, cert) use a `secret` instead. Never echo values back to the user.",
|
|
45
|
+
vault: "vault assets are DEPRECATED (use env). They store environment variables (KEY=VALUE pairs); comments use `#`. Never echo secret values back to the user.",
|
|
42
46
|
wiki: "wiki assets are markdown reference pages with `# Title` and structured headings.",
|
|
43
47
|
};
|
|
44
48
|
function hintForType(type) {
|
|
@@ -55,9 +59,15 @@ function knownTypeList() {
|
|
|
55
59
|
*/
|
|
56
60
|
const RESPONSE_CONTRACT_JSON = [
|
|
57
61
|
"Respond ONLY with a single JSON object. No prose before or after.",
|
|
58
|
-
'Shape: {"ref": "<type>:<name>", "content": "<full file contents>", "frontmatter": {...}}',
|
|
62
|
+
'Shape: {"ref": "<type>:<name>", "content": "<full file contents>", "frontmatter": {...}, "confidence": <number 0..1>}',
|
|
59
63
|
"`content` is the full file body that will be written if accepted.",
|
|
60
64
|
"`frontmatter` is optional — include it if `content` starts with `---` so reviewers can sanity-check the keys.",
|
|
65
|
+
"`confidence` is REQUIRED. Self-rate this proposal on [0, 1] by how certain you are it materially improves the source asset. Calibrate honestly:",
|
|
66
|
+
" • 0.90+ — high certainty: fixes a real defect or adds load-bearing missing content; a reviewer would clearly accept.",
|
|
67
|
+
" • 0.70–0.89 — clear improvement, but a reviewer might reasonably prefer different framing or scope.",
|
|
68
|
+
" • 0.50–0.69 — marginal / judgment call; might help, might not be worth the churn.",
|
|
69
|
+
" • Below 0.50 — you are not confident this improves on the source. Prefer returning the source body roughly unchanged with a low score over inventing changes.",
|
|
70
|
+
"Auto-accept gates on confidence ≥ 0.80 by default. Overclaiming ships low-quality changes; underclaiming leaves good ones stuck in queue. Be honest.",
|
|
61
71
|
].join("\n");
|
|
62
72
|
/**
|
|
63
73
|
* Response contract used when a draft file path is available. Instructs the
|
|
@@ -69,14 +79,42 @@ function fileWriteContract(draftFilePath) {
|
|
|
69
79
|
`Write the complete improved asset content to: ${draftFilePath}`,
|
|
70
80
|
"Use your file-editing tools to create or overwrite that file.",
|
|
71
81
|
"Do NOT output JSON to stdout. Do NOT print the file contents. Just write the file.",
|
|
72
|
-
"When
|
|
82
|
+
"When done, output a single line on stdout: DRAFT_WRITTEN confidence=<0.0-1.0>",
|
|
83
|
+
"`confidence` is REQUIRED and must be your honest self-rated [0, 1] score for this proposal:",
|
|
84
|
+
" • 0.90+ — fixes a real defect or adds load-bearing missing content; reviewer would clearly accept.",
|
|
85
|
+
" • 0.70–0.89 — clear improvement, but a reviewer might prefer different framing.",
|
|
86
|
+
" • 0.50–0.69 — marginal / judgment call.",
|
|
87
|
+
" • Below 0.50 — not confident; prefer not writing changes at all.",
|
|
88
|
+
"Auto-accept gates on confidence ≥ 0.80. Overclaim → low-quality changes land; underclaim → good changes stuck in queue.",
|
|
73
89
|
].join("\n");
|
|
74
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Extract a confidence score from a `DRAFT_WRITTEN confidence=<n>` line emitted
|
|
93
|
+
* by an agent following {@link fileWriteContract}. Tolerates trailing prose,
|
|
94
|
+
* surrounding log lines, and missing/invalid confidence (returns `undefined`
|
|
95
|
+
* so callers can keep the proposal but skip auto-accept).
|
|
96
|
+
*
|
|
97
|
+
* Matched forms (case-insensitive, anywhere in stdout):
|
|
98
|
+
* - `DRAFT_WRITTEN confidence=0.85`
|
|
99
|
+
* - `DRAFT_WRITTEN confidence=0.85 ...trailing`
|
|
100
|
+
* - `DRAFT_WRITTEN` (no confidence — returns `undefined`)
|
|
101
|
+
*/
|
|
102
|
+
export function extractDraftConfidence(stdout) {
|
|
103
|
+
if (!stdout)
|
|
104
|
+
return undefined;
|
|
105
|
+
const match = stdout.match(/\bDRAFT_WRITTEN\b[^\S\r\n]+confidence=([0-9]*\.?[0-9]+)/i);
|
|
106
|
+
if (!match)
|
|
107
|
+
return undefined;
|
|
108
|
+
const value = Number.parseFloat(match[1] ?? "");
|
|
109
|
+
if (!Number.isFinite(value) || value < 0 || value > 1)
|
|
110
|
+
return undefined;
|
|
111
|
+
return value;
|
|
112
|
+
}
|
|
75
113
|
/**
|
|
76
114
|
* Build the prompt for `akm reflect [ref]`. Asks the agent to review an
|
|
77
115
|
* existing asset (plus any negative feedback / lint findings) and propose
|
|
78
|
-
* an improved version. Returns a
|
|
79
|
-
*
|
|
116
|
+
* an improved version. Returns a {@link ReflectPromptResult} containing the
|
|
117
|
+
* prompt string and an optional character ceiling for max-tokens enforcement.
|
|
80
118
|
*/
|
|
81
119
|
export function buildReflectPrompt(input) {
|
|
82
120
|
const sections = [];
|
|
@@ -119,9 +157,17 @@ export function buildReflectPrompt(input) {
|
|
|
119
157
|
sections.push("No usage feedback recorded. Limit your proposal to schema and structural improvements only: missing required frontmatter fields, unclear `when_to_use`, ambiguous description, or broken formatting. Do not speculate about runtime weaknesses you have not observed.");
|
|
120
158
|
}
|
|
121
159
|
if (input.assetContent?.trim()) {
|
|
122
|
-
|
|
160
|
+
// Cap at 12 000 chars to stay well under OS ARG_MAX when the prompt is
|
|
161
|
+
// passed as a CLI argument to opencode/claude. Large assets (wiki snapshots,
|
|
162
|
+
// long runbooks) would otherwise trigger E2BIG on posix_spawn.
|
|
163
|
+
const REFLECT_CONTENT_CAP = 12_000;
|
|
164
|
+
const body = input.assetContent.trimEnd();
|
|
165
|
+
const truncated = body.length > REFLECT_CONTENT_CAP;
|
|
166
|
+
sections.push(truncated
|
|
167
|
+
? `Current asset content (first ${REFLECT_CONTENT_CAP} chars — full asset is ${body.length} chars):`
|
|
168
|
+
: "Current asset content (verbatim):");
|
|
123
169
|
sections.push("```");
|
|
124
|
-
sections.push(
|
|
170
|
+
sections.push(truncated ? `${body.slice(0, REFLECT_CONTENT_CAP)}\n... [truncated — focus on the visible portion]` : body);
|
|
125
171
|
sections.push("```");
|
|
126
172
|
}
|
|
127
173
|
else if (input.ref) {
|
|
@@ -147,12 +193,86 @@ export function buildReflectPrompt(input) {
|
|
|
147
193
|
sections.push("Promote only guidance that is durable, generally applicable, and supported by repeated evidence. Do not copy anecdotal details, one-off incidents, or duplicate wording verbatim.");
|
|
148
194
|
sections.push("If the guidance belongs in the main skill instructions, update the skill proposal. If it belongs in a companion reference document, return a `knowledge:skills/<skill>/references/<topic>` proposal instead.");
|
|
149
195
|
}
|
|
196
|
+
if (input.rejectedProposals && input.rejectedProposals.length > 0) {
|
|
197
|
+
const lines = ["## Previously Rejected Proposals"];
|
|
198
|
+
lines.push("The following proposals for this ref were already reviewed and rejected. " +
|
|
199
|
+
"Do NOT reproduce the same content or the same structural shape. " +
|
|
200
|
+
"Your new proposal must meaningfully differ from each of these in its approach, framing, or evidence used.");
|
|
201
|
+
for (const rp of input.rejectedProposals) {
|
|
202
|
+
lines.push(`\nRef: ${rp.ref}`);
|
|
203
|
+
lines.push(`Rejection reason: ${rp.reason}`);
|
|
204
|
+
if (rp.contentPreview) {
|
|
205
|
+
lines.push("Rejected content preview:");
|
|
206
|
+
lines.push("```");
|
|
207
|
+
lines.push(rp.contentPreview);
|
|
208
|
+
lines.push("```");
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
sections.push(lines.join("\n"));
|
|
212
|
+
}
|
|
150
213
|
if (input.avoidPatterns && input.avoidPatterns.length > 0) {
|
|
151
214
|
sections.push(`## Avoid These Patterns\nPrevious assets in this run produced these errors — do not repeat them:\n${input.avoidPatterns.map((e) => `- ${e}`).join("\n")}`);
|
|
152
215
|
}
|
|
216
|
+
// R-1 / #372: Self-Refine (arXiv:2303.17651) — inject prior draft as critique target.
|
|
217
|
+
// On refinement iterations (iter > 0), the agent is shown its previous proposal
|
|
218
|
+
// and asked to self-critique and improve it rather than starting from scratch.
|
|
219
|
+
if (input.priorDraft?.trim()) {
|
|
220
|
+
sections.push("## Self-Refine: Critique and Improve\n" +
|
|
221
|
+
"The following is your previous draft proposal. " +
|
|
222
|
+
"Identify specific weaknesses: missing evidence, vague wording, incomplete frontmatter, " +
|
|
223
|
+
"or claims that duplicate existing content without adding new signal. " +
|
|
224
|
+
"Then produce an improved version that addresses those weaknesses. " +
|
|
225
|
+
"The revised proposal must be meaningfully better than the draft below — " +
|
|
226
|
+
"do not return the same content unchanged.\n\n" +
|
|
227
|
+
"Previous draft:\n```\n" +
|
|
228
|
+
input.priorDraft.trimEnd() +
|
|
229
|
+
"\n```");
|
|
230
|
+
}
|
|
153
231
|
sections.push("Produce a single proposal that addresses the feedback and respects the asset-type contract. If the proposal's frontmatter is missing `when_to_use`, you MUST generate one — a one-line trigger sentence describing exactly when a user should reach for this asset.");
|
|
232
|
+
// Content-preservation safety rails (#reflect-pipeline-fixes).
|
|
233
|
+
// These rules counter the observed failure modes where reflect rewrites
|
|
234
|
+
// asset content into shorter prose, drops concrete structure, or strips
|
|
235
|
+
// load-bearing frontmatter. Loud and explicit so small models follow.
|
|
236
|
+
//
|
|
237
|
+
// maxOutputChars is hoisted so the return value can include it for callers
|
|
238
|
+
// on the LLM path that want to set a hard max_tokens cap on the request.
|
|
239
|
+
let maxOutputChars;
|
|
240
|
+
if (input.ref && input.assetContent?.trim()) {
|
|
241
|
+
// Strip frontmatter to get source body length — mirrors checkReflectSize which
|
|
242
|
+
// compares body-only lengths. Inline regex avoids importing parseFrontmatter.
|
|
243
|
+
const rawContent = input.assetContent.trimEnd();
|
|
244
|
+
const fmBodyMatch = rawContent.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?([\s\S]*)$/);
|
|
245
|
+
const sourceBodyLen = (fmBodyMatch ? fmBodyMatch[1] : rawContent).trim().length;
|
|
246
|
+
// Compute concrete char bounds matching checkReflectSize constants:
|
|
247
|
+
// REFLECT_SIZE_GUARD_MIN_BYTES=200, REFLECT_SHRINK_RATIO_MIN=0.5,
|
|
248
|
+
// REFLECT_ABSOLUTE_FLOOR_BYTES=150, REFLECT_EXPAND_RATIO_MAX=2.5,
|
|
249
|
+
// REFLECT_ABSOLUTE_CEILING_BYTES=2500, REFLECT_ABSOLUTE_MAX_BYTES=25000.
|
|
250
|
+
// Embed concrete counts only when the gate will actually fire (source >= 200 chars).
|
|
251
|
+
const showCharBounds = sourceBodyLen >= 200;
|
|
252
|
+
const minChars = Math.max(Math.round(0.5 * sourceBodyLen), 150);
|
|
253
|
+
const maxChars = Math.min(Math.max(Math.round(2.5 * sourceBodyLen), 2500), 25000);
|
|
254
|
+
if (showCharBounds)
|
|
255
|
+
maxOutputChars = maxChars;
|
|
256
|
+
sections.push([
|
|
257
|
+
"## Content preservation rules (MUST follow)",
|
|
258
|
+
"1. PRESERVE ALL concrete content: code blocks, fenced snippets, CLI commands, numbered/bulleted checklists, tables, YAML/JSON examples, file paths, configuration keys, environment variable names, and CSS/HTML selectors. These are load-bearing — do NOT replace them with prose summaries.",
|
|
259
|
+
"2. PRESERVE the source asset's frontmatter. The post-processor reassembles the final asset from the original frontmatter plus your body. Do NOT emit `---` frontmatter delimiters at the top of `content` — start `content` with the markdown body (e.g. `# Heading` or the first paragraph). If you include frontmatter anyway, identity fields (`name`, `ref`, `id`, `slug`, `type`) will be reset to the original values.",
|
|
260
|
+
showCharBounds
|
|
261
|
+
? `3. DO NOT shrink the asset. Your body must be at least ${minChars} characters (source body is ${sourceBodyLen} chars; floor is 50%). If you genuinely need to remove a major section, explain why in a comment line at the top of the body (e.g. \`<!-- removed obsolete section X because ... -->\`).`
|
|
262
|
+
: "3. DO NOT shrink the asset dramatically. The improved body must be at least 50% of the source body length. If you genuinely need to remove a major section, explain why in a comment line at the top of the body (e.g. `<!-- removed obsolete section X because ... -->`).",
|
|
263
|
+
showCharBounds
|
|
264
|
+
? `4. DO NOT pad the asset with speculative material. Your body must be at most ${maxChars} characters (source body is ${sourceBodyLen} chars; ceiling is 250%). Do not add invented sections, hypothetical examples, or padding prose.`
|
|
265
|
+
: "4. DO NOT pad the asset with speculative material. The improved body must be at most 250% of the source body length unless the feedback explicitly requests added sections.",
|
|
266
|
+
"5. Improve clarity of surrounding prose, fix structural issues, add missing required frontmatter fields. Do NOT rewrite a runbook into an essay.",
|
|
267
|
+
].join("\n"));
|
|
268
|
+
}
|
|
269
|
+
if (!input.draftFilePath && input.ref) {
|
|
270
|
+
// Reinforce that the `ref` field is mandatory and must exactly match the target.
|
|
271
|
+
// Small models frequently omit `ref` from the response JSON, causing parse errors.
|
|
272
|
+
sections.push(`IMPORTANT: The JSON "ref" field is REQUIRED. It MUST be exactly: "${input.ref}"`);
|
|
273
|
+
}
|
|
154
274
|
sections.push(input.draftFilePath ? fileWriteContract(input.draftFilePath) : RESPONSE_CONTRACT_JSON);
|
|
155
|
-
return sections.join("\n\n");
|
|
275
|
+
return { prompt: sections.join("\n\n"), ...(maxOutputChars !== undefined ? { maxOutputChars } : {}) };
|
|
156
276
|
}
|
|
157
277
|
/**
|
|
158
278
|
* Build the prompt for `akm propose <type> <name> --task ...`. Asks the
|
|
@@ -240,6 +360,15 @@ export function parseAgentProposalPayload(stdout) {
|
|
|
240
360
|
if (parsed.frontmatter && typeof parsed.frontmatter === "object" && !Array.isArray(parsed.frontmatter)) {
|
|
241
361
|
out.frontmatter = parsed.frontmatter;
|
|
242
362
|
}
|
|
363
|
+
// Phase 6A: extract optional `confidence` (number in [0, 1]). Clamp gently
|
|
364
|
+
// rather than reject — a model that returns 1.0 or 0 with extra precision
|
|
365
|
+
// (e.g. 1.0000001) should still surface a usable score. Anything that isn't
|
|
366
|
+
// a finite number is dropped so downstream `createProposal` can rely on the
|
|
367
|
+
// shape invariant.
|
|
368
|
+
if (typeof parsed.confidence === "number" && Number.isFinite(parsed.confidence)) {
|
|
369
|
+
const clamped = Math.max(0, Math.min(1, parsed.confidence));
|
|
370
|
+
out.confidence = clamped;
|
|
371
|
+
}
|
|
243
372
|
return out;
|
|
244
373
|
}
|
|
245
374
|
/**
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
import { ConfigError } from "../../core/errors";
|
|
5
|
+
import { warn } from "../../core/warn";
|
|
6
|
+
function resolveEffectiveMode(entry, profileName, config) {
|
|
7
|
+
if (entry.mode)
|
|
8
|
+
return entry.mode;
|
|
9
|
+
// Infer from profile pool when profile is specified
|
|
10
|
+
if (profileName) {
|
|
11
|
+
if (config.profiles?.llm?.[profileName])
|
|
12
|
+
return "llm";
|
|
13
|
+
const agentProfile = config.profiles?.agent?.[profileName];
|
|
14
|
+
if (agentProfile) {
|
|
15
|
+
return agentProfile.platform === "opencode-sdk" ? "sdk" : "agent";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
// Fall back to defaults
|
|
19
|
+
if (config.defaults?.llm)
|
|
20
|
+
return "llm";
|
|
21
|
+
if (config.defaults?.agent)
|
|
22
|
+
return "agent";
|
|
23
|
+
return "llm";
|
|
24
|
+
}
|
|
25
|
+
function resolveProfileName(entry, mode, config) {
|
|
26
|
+
if (entry.profile)
|
|
27
|
+
return entry.profile;
|
|
28
|
+
if (mode === "llm") {
|
|
29
|
+
const defaultName = config.defaults?.llm;
|
|
30
|
+
if (defaultName)
|
|
31
|
+
return defaultName;
|
|
32
|
+
throw new ConfigError(`No LLM profile configured. Set defaults.llm in config or specify profile in the process entry.`, "LLM_NOT_CONFIGURED", "Run `akm setup` or define a profile under `profiles.llm` and set `defaults.llm`.");
|
|
33
|
+
}
|
|
34
|
+
const defaultName = config.defaults?.agent;
|
|
35
|
+
if (defaultName)
|
|
36
|
+
return defaultName;
|
|
37
|
+
throw new ConfigError(`No agent profile configured. Set defaults.agent in config or specify profile in the process entry.`, "INVALID_CONFIG_FILE", "Run `akm setup` to configure an agent profile, or add one under `profiles.agent`.");
|
|
38
|
+
}
|
|
39
|
+
function buildLlmRunnerSpec(profileName, timeoutMs, config) {
|
|
40
|
+
const profile = config.profiles?.llm?.[profileName];
|
|
41
|
+
if (!profile) {
|
|
42
|
+
throw new ConfigError(`LLM profile "${profileName}" not found in profiles.llm.`, "LLM_NOT_CONFIGURED", `Available profiles: ${Object.keys(config.profiles?.llm ?? {}).join(", ") || "none"}. Run \`akm setup\` to configure.`);
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
kind: "llm",
|
|
46
|
+
connection: profile,
|
|
47
|
+
...(typeof timeoutMs === "number" ? { timeoutMs } : {}),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function buildAgentRunnerSpec(kind, profileName, timeoutMs, config) {
|
|
51
|
+
const profileConfig = config.profiles?.agent?.[profileName];
|
|
52
|
+
if (!profileConfig) {
|
|
53
|
+
throw new ConfigError(`Agent profile "${profileName}" not found in profiles.agent.`, "INVALID_CONFIG_FILE", `Available profiles: ${Object.keys(config.profiles?.agent ?? {}).join(", ") || "none"}. Run \`akm setup\` to configure.`);
|
|
54
|
+
}
|
|
55
|
+
// Validate mode/platform consistency
|
|
56
|
+
if (kind === "sdk" && profileConfig.platform !== "opencode-sdk") {
|
|
57
|
+
throw new ConfigError(`Mode "sdk" requires platform "opencode-sdk", but profiles.agent["${profileName}"].platform is "${profileConfig.platform}".`, "INVALID_CONFIG_FILE");
|
|
58
|
+
}
|
|
59
|
+
if (kind === "agent" && profileConfig.platform === "opencode-sdk") {
|
|
60
|
+
throw new ConfigError(`Mode "agent" requires platform "opencode" or "claude", but profiles.agent["${profileName}"].platform is "opencode-sdk".`, "INVALID_CONFIG_FILE");
|
|
61
|
+
}
|
|
62
|
+
const agentProfile = {
|
|
63
|
+
name: profileName,
|
|
64
|
+
bin: profileConfig.bin ?? profileName,
|
|
65
|
+
args: profileConfig.args ?? [],
|
|
66
|
+
stdio: "captured",
|
|
67
|
+
envPassthrough: [],
|
|
68
|
+
parseOutput: "text",
|
|
69
|
+
...(profileConfig.model ? { model: profileConfig.model } : {}),
|
|
70
|
+
...(profileConfig.workspace ? { workspace: profileConfig.workspace } : {}),
|
|
71
|
+
};
|
|
72
|
+
return {
|
|
73
|
+
kind,
|
|
74
|
+
profile: agentProfile,
|
|
75
|
+
...(typeof timeoutMs === "number" ? { timeoutMs } : {}),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Resolve the runner used for "validation" passes on the `improve` section
|
|
80
|
+
* (Advantage D3 / Phase 4B — third model tier).
|
|
81
|
+
*
|
|
82
|
+
* Look-up order:
|
|
83
|
+
* 1. `profiles.improve.default.processes.validation` (preferred — lets users
|
|
84
|
+
* wire a lower-cost classifier model for staleness detection, confidence
|
|
85
|
+
* scoring, and lesson classification).
|
|
86
|
+
* 2. `defaults.llm` as a final fallback so callers always get a usable
|
|
87
|
+
* runner when any LLM is configured.
|
|
88
|
+
*
|
|
89
|
+
* Returns `null` when neither is configured (callers may then skip the
|
|
90
|
+
* validation pass rather than throwing).
|
|
91
|
+
*/
|
|
92
|
+
/**
|
|
93
|
+
* Shared resolution path for the `validation` and `triage judgment` tiers,
|
|
94
|
+
* both of which take a `{ mode?, profile?, timeoutMs? }`-shaped block, attempt
|
|
95
|
+
* to resolve it via {@link resolveImproveProcessRunnerFromProfile}, and
|
|
96
|
+
* otherwise fall back to an `llm` runner built from `defaults.llm`.
|
|
97
|
+
*
|
|
98
|
+
* @param block the `{ mode?, profile?, timeoutMs? }` subset to resolve.
|
|
99
|
+
* @param config the active AKM config.
|
|
100
|
+
* @param opts.fallbackTimeoutMs timeout applied to the `defaults.llm` fallback
|
|
101
|
+
* runner (validation passes `undefined`; triage forwards `block.timeoutMs`).
|
|
102
|
+
* @param opts.suppressLlmFallbackForExplicitMode when `true` and the block
|
|
103
|
+
* explicitly sets `mode: "agent"` or `"sdk"`, do NOT silently fall back
|
|
104
|
+
* to `defaults.llm` if the profile resolver returns `null` or throws —
|
|
105
|
+
* return `null` instead and emit a `warn(...)` (see FIX 8). Validation
|
|
106
|
+
* passes `false` to preserve its always-fallback behavior.
|
|
107
|
+
*/
|
|
108
|
+
function resolveProcessRunnerWithLlmFallback(block, config, opts) {
|
|
109
|
+
const explicitNonLlmMode = block?.mode === "agent" || block?.mode === "sdk";
|
|
110
|
+
if (block && (block.mode || block.profile)) {
|
|
111
|
+
try {
|
|
112
|
+
const spec = resolveImproveProcessRunnerFromProfile(block, config);
|
|
113
|
+
if (spec)
|
|
114
|
+
return spec;
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// Fall through to defaults.llm below (unless suppressed for explicit modes).
|
|
118
|
+
}
|
|
119
|
+
// FIX 8: an EXPLICIT agent/sdk request must not be silently downgraded to
|
|
120
|
+
// llm. When the profile resolver could not produce a runner, surface the
|
|
121
|
+
// misconfiguration and let callers skip this tier rather than substituting
|
|
122
|
+
// an llm judge.
|
|
123
|
+
if (opts.suppressLlmFallbackForExplicitMode && explicitNonLlmMode) {
|
|
124
|
+
warn(`[akm] Could not resolve the "${block?.mode}" judgment profile; ` +
|
|
125
|
+
`skipping the judgment tier instead of falling back to an llm judge. ` +
|
|
126
|
+
`Check profiles.agent and the judgment profile/mode configuration.`);
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const defaultLlm = config.defaults?.llm;
|
|
131
|
+
if (defaultLlm) {
|
|
132
|
+
try {
|
|
133
|
+
return buildLlmRunnerSpec(defaultLlm, opts.fallbackTimeoutMs, config);
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
export function resolveValidationRunner(config) {
|
|
142
|
+
const validation = config.profiles?.improve?.default?.processes?.validation;
|
|
143
|
+
const block = validation && validation.enabled !== false ? validation : undefined;
|
|
144
|
+
return resolveProcessRunnerWithLlmFallback(block, config, {
|
|
145
|
+
fallbackTimeoutMs: undefined,
|
|
146
|
+
suppressLlmFallbackForExplicitMode: false,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Resolve the runner for the triage judgment tier (Proposal-Queue Triage,
|
|
151
|
+
* Phase 3). The `judgment` block is a `{ mode?, profile?, timeoutMs? }` subset
|
|
152
|
+
* compatible with {@link ImproveProcessConfig}, so it resolves through the same
|
|
153
|
+
* {@link resolveImproveProcessRunnerFromProfile} path.
|
|
154
|
+
*
|
|
155
|
+
* Mirrors {@link resolveValidationRunner}'s `defaults.llm` fallback only for
|
|
156
|
+
* the unset/`llm` case: when the block sets neither `mode` nor `profile` (so
|
|
157
|
+
* the profile resolver returns `null`) or `mode: "llm"`, fall back to an `llm`
|
|
158
|
+
* runner built from `defaults.llm` so `judgment.mode: llm` defaults are honored
|
|
159
|
+
* (§14). However (FIX 8) when the caller EXPLICITLY sets `mode: "agent"` or
|
|
160
|
+
* `"sdk"` and the profile resolver returns `null` or throws, do NOT downgrade
|
|
161
|
+
* to an llm judge — return `null` (callers skip the judgment tier and emit
|
|
162
|
+
* `triage_deferred`) and emit a `warn(...)`. Returns `null` when nothing is
|
|
163
|
+
* configured.
|
|
164
|
+
*/
|
|
165
|
+
export function resolveTriageJudgmentRunner(judgment, config) {
|
|
166
|
+
return resolveProcessRunnerWithLlmFallback(judgment, config, {
|
|
167
|
+
fallbackTimeoutMs: judgment?.timeoutMs,
|
|
168
|
+
suppressLlmFallbackForExplicitMode: true,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
export function resolveRunner(mode, profileName, config) {
|
|
172
|
+
if (mode === "llm")
|
|
173
|
+
return buildLlmRunnerSpec(profileName, undefined, config);
|
|
174
|
+
return buildAgentRunnerSpec(mode, profileName, undefined, config);
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Resolve a RunnerSpec from an improve-profile process entry. Returns `null`
|
|
178
|
+
* when the entry is absent or provides no overrides — callers should fall
|
|
179
|
+
* back to the default per-process runner resolution path.
|
|
180
|
+
*/
|
|
181
|
+
export function resolveImproveProcessRunnerFromProfile(processConfig, config) {
|
|
182
|
+
if (!processConfig)
|
|
183
|
+
return null;
|
|
184
|
+
const { mode: explicitMode, profile, timeoutMs } = processConfig;
|
|
185
|
+
if (!explicitMode && !profile)
|
|
186
|
+
return null;
|
|
187
|
+
const mode = explicitMode ?? resolveEffectiveMode(processConfig, profile, config);
|
|
188
|
+
const profileName = profile ?? resolveProfileName(processConfig, mode, config);
|
|
189
|
+
if (mode === "llm") {
|
|
190
|
+
return buildLlmRunnerSpec(profileName, timeoutMs, config);
|
|
191
|
+
}
|
|
192
|
+
if (mode === "agent" || mode === "sdk") {
|
|
193
|
+
return buildAgentRunnerSpec(mode, profileName, timeoutMs, config);
|
|
194
|
+
}
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Convenience accessor for callers that previously read
|
|
199
|
+
* `getProcessOptions("index", "staleness_detection", config).thresholdDays`.
|
|
200
|
+
* After the 0.8.0 migration, those values live on first-class config keys —
|
|
201
|
+
* see `config.index?.stalenessDetection?.thresholdDays` etc.
|
|
202
|
+
*/
|
|
203
|
+
export function getStalenessDetectionThresholdDays(config) {
|
|
204
|
+
return config.index?.stalenessDetection?.thresholdDays;
|
|
205
|
+
}
|
|
206
|
+
// Re-export `isProcessEnabled` from feature-gate.ts so callers that previously
|
|
207
|
+
// imported it from runner.ts continue to work.
|
|
208
|
+
export { isProcessEnabled } from "../../llm/feature-gate";
|
|
@@ -1,8 +1,12 @@
|
|
|
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/.
|
|
1
4
|
/**
|
|
2
5
|
* OpenCode SDK agent runner — uses embedded @opencode-ai/sdk instead of
|
|
3
6
|
* Bun.spawn. Requires no agent CLI binary to be installed. The user provides
|
|
4
7
|
* an OpenAI-compatible endpoint (or inherits from config.llm) for the SDK.
|
|
5
8
|
*/
|
|
9
|
+
import { resolveSecret } from "../../core/config";
|
|
6
10
|
// Singleton server — started once per process, reused across calls
|
|
7
11
|
let _server = null;
|
|
8
12
|
/**
|
|
@@ -26,7 +30,7 @@ async function getOrStartServer(profile, llmConfig) {
|
|
|
26
30
|
});
|
|
27
31
|
// Resolve endpoint and model: profile fields take precedence over config.llm
|
|
28
32
|
const endpoint = profile.endpoint ?? llmConfig?.endpoint;
|
|
29
|
-
const apiKey = profile.apiKey ?? llmConfig?.apiKey;
|
|
33
|
+
const apiKey = resolveSecret(profile.apiKey ?? llmConfig?.apiKey);
|
|
30
34
|
const model = profile.model;
|
|
31
35
|
const sdkConfig = {};
|
|
32
36
|
if (model)
|
|
@@ -55,7 +59,7 @@ async function getOrStartServer(profile, llmConfig) {
|
|
|
55
59
|
throw new Error("Failed to initialise OpenCode SDK server.");
|
|
56
60
|
return _server;
|
|
57
61
|
}
|
|
58
|
-
export async function
|
|
62
|
+
export async function runOpencodeSdk(profile, prompt, _opts = {}, llmConfig) {
|
|
59
63
|
const start = Date.now();
|
|
60
64
|
let client;
|
|
61
65
|
try {
|
|
@@ -118,3 +122,5 @@ export async function runAgentSdk(profile, prompt, _opts = {}, llmConfig) {
|
|
|
118
122
|
await client.session.delete({ path: { id: sessionId } }).catch(() => { });
|
|
119
123
|
}
|
|
120
124
|
}
|
|
125
|
+
/** @deprecated Use {@link runOpencodeSdk} instead. */
|
|
126
|
+
export const runAgentSdk = runOpencodeSdk;
|
|
@@ -1,3 +1,6 @@
|
|
|
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/.
|
|
1
4
|
/**
|
|
2
5
|
* Agent CLI spawn wrapper (v1 spec §12.2).
|
|
3
6
|
*
|
|
@@ -14,6 +17,7 @@
|
|
|
14
17
|
import fs from "node:fs";
|
|
15
18
|
import os from "node:os";
|
|
16
19
|
import path from "node:path";
|
|
20
|
+
import { getCommandBuilder } from "./builders";
|
|
17
21
|
import { DEFAULT_AGENT_TIMEOUT_MS } from "./config";
|
|
18
22
|
/**
|
|
19
23
|
* Kill the process group of `proc` with `signal`, falling back to
|
|
@@ -24,7 +28,7 @@ import { DEFAULT_AGENT_TIMEOUT_MS } from "./config";
|
|
|
24
28
|
* reaped alongside the node wrapper. The fallback keeps test fakes working
|
|
25
29
|
* without modification.
|
|
26
30
|
*/
|
|
27
|
-
|
|
31
|
+
function killGroup(proc, signal) {
|
|
28
32
|
if (typeof proc.pid === "number") {
|
|
29
33
|
try {
|
|
30
34
|
process.kill(-proc.pid, signal);
|
|
@@ -61,7 +65,33 @@ export function supplementPathForSchedulerContext(existingPath) {
|
|
|
61
65
|
if (existingPath.split(path.delimiter).some((d) => d.startsWith(home))) {
|
|
62
66
|
return existingPath;
|
|
63
67
|
}
|
|
64
|
-
const candidates =
|
|
68
|
+
const candidates = pathCandidatesForCurrentPlatform(home);
|
|
69
|
+
const existing = new Set(existingPath.split(path.delimiter).filter(Boolean));
|
|
70
|
+
const toAdd = candidates.filter((d) => !existing.has(d) && fs.existsSync(d));
|
|
71
|
+
if (toAdd.length === 0)
|
|
72
|
+
return existingPath;
|
|
73
|
+
return [...toAdd, existingPath].filter(Boolean).join(path.delimiter);
|
|
74
|
+
}
|
|
75
|
+
function pathCandidatesForCurrentPlatform(home) {
|
|
76
|
+
if (process.platform === "win32") {
|
|
77
|
+
// Windows: Bun + Cargo + Scoop + Chocolatey + system tools. Order favors
|
|
78
|
+
// user-local installs over machine-global so the user's chosen toolchain
|
|
79
|
+
// wins. These paths are commonly stripped from Task Scheduler / service
|
|
80
|
+
// environments, mirroring the cron/launchd problem on POSIX.
|
|
81
|
+
const localAppData = process.env.LOCALAPPDATA ?? path.join(home, "AppData", "Local");
|
|
82
|
+
const userProfile = process.env.USERPROFILE ?? home;
|
|
83
|
+
const programFiles = process.env.ProgramFiles ?? "C:\\Program Files";
|
|
84
|
+
return [
|
|
85
|
+
path.join(userProfile, ".bun", "bin"),
|
|
86
|
+
path.join(localAppData, "Programs", "bun"),
|
|
87
|
+
path.join(userProfile, ".cargo", "bin"),
|
|
88
|
+
path.join(localAppData, "Programs", "Git", "cmd"),
|
|
89
|
+
path.join(userProfile, "scoop", "shims"),
|
|
90
|
+
path.join(programFiles, "Git", "cmd"),
|
|
91
|
+
"C:\\ProgramData\\chocolatey\\bin",
|
|
92
|
+
];
|
|
93
|
+
}
|
|
94
|
+
return [
|
|
65
95
|
path.join(home, ".bun", "bin"),
|
|
66
96
|
path.join(home, ".cargo", "bin"),
|
|
67
97
|
path.join(home, ".local", "bin"),
|
|
@@ -69,11 +99,6 @@ export function supplementPathForSchedulerContext(existingPath) {
|
|
|
69
99
|
"/opt/homebrew/sbin",
|
|
70
100
|
"/usr/local/bin",
|
|
71
101
|
];
|
|
72
|
-
const existing = new Set(existingPath.split(path.delimiter).filter(Boolean));
|
|
73
|
-
const toAdd = candidates.filter((d) => !existing.has(d) && fs.existsSync(d));
|
|
74
|
-
if (toAdd.length === 0)
|
|
75
|
-
return existingPath;
|
|
76
|
-
return [...toAdd, existingPath].filter(Boolean).join(path.delimiter);
|
|
77
102
|
}
|
|
78
103
|
function resolveSpawnFn(options) {
|
|
79
104
|
if (options.spawn)
|
|
@@ -159,15 +184,30 @@ export async function runAgent(profile, prompt, options = {}) {
|
|
|
159
184
|
const parseOutput = options.parseOutput ?? profile.parseOutput;
|
|
160
185
|
const setTimeoutImpl = options.setTimeoutFn ?? setTimeout;
|
|
161
186
|
const clearTimeoutImpl = options.clearTimeoutFn ?? clearTimeout;
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
187
|
+
// Build argv via the platform-specific builder when dispatch params are
|
|
188
|
+
// provided; fall back to the legacy positional-prompt form otherwise.
|
|
189
|
+
let builtArgv;
|
|
190
|
+
let builtEnv;
|
|
191
|
+
if (options.dispatch !== undefined) {
|
|
192
|
+
const builder = getCommandBuilder(profile.commandBuilder ?? profile.name, options.builderRegistry);
|
|
193
|
+
const built = builder.build(profile, options.dispatch);
|
|
194
|
+
builtArgv = built.argv;
|
|
195
|
+
builtEnv = built.env;
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
const legacyArgs = [...profile.args, ...(options.args ?? [])];
|
|
199
|
+
if (prompt !== undefined)
|
|
200
|
+
legacyArgs.push(prompt);
|
|
201
|
+
builtArgv = [profile.bin, ...legacyArgs];
|
|
202
|
+
}
|
|
203
|
+
// Extra args (e.g. forwarded CLI positionals) are appended after the builder output.
|
|
204
|
+
const finalArgv = [...builtArgv, ...(options.dispatch ? (options.args ?? []) : [])];
|
|
205
|
+
const env = { ...buildChildEnv(profile, options), ...(builtEnv ?? {}) };
|
|
166
206
|
const start = Date.now();
|
|
167
207
|
let proc;
|
|
168
208
|
try {
|
|
169
209
|
const spawnFn = resolveSpawnFn(options);
|
|
170
|
-
proc = spawnFn(
|
|
210
|
+
proc = spawnFn(finalArgv, {
|
|
171
211
|
stdin: stdioMode === "captured" ? (options.stdin !== undefined ? "pipe" : "ignore") : "inherit",
|
|
172
212
|
stdout: stdioMode === "captured" ? "pipe" : "inherit",
|
|
173
213
|
stderr: stdioMode === "captured" ? "pipe" : "inherit",
|
|
@@ -207,13 +247,13 @@ export async function runAgent(profile, prompt, options = {}) {
|
|
|
207
247
|
let timer;
|
|
208
248
|
if (timeoutMs !== null) {
|
|
209
249
|
timer = setTimeoutImpl(() => {
|
|
210
|
-
if (proc.exitCode !== null)
|
|
250
|
+
if (!proc || proc.exitCode !== null)
|
|
211
251
|
return;
|
|
212
252
|
timedOut = true;
|
|
213
253
|
killGroup(proc, "SIGTERM");
|
|
214
254
|
// Follow up with SIGKILL after 5 s in case the process ignores SIGTERM.
|
|
215
255
|
setTimeoutImpl(() => {
|
|
216
|
-
if (proc.exitCode !== null)
|
|
256
|
+
if (!proc || proc.exitCode !== null)
|
|
217
257
|
return;
|
|
218
258
|
killGroup(proc, "SIGKILL");
|
|
219
259
|
}, 5000);
|
|
@@ -1,3 +1,6 @@
|
|
|
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/.
|
|
1
4
|
import * as childProcess from "node:child_process";
|
|
2
5
|
export const GITHUB_API_BASE = "https://api.github.com";
|
|
3
6
|
const GITHUB_TOKEN_DOMAINS = new Set(["api.github.com", "github.com", "uploads.github.com"]);
|