akm-cli 0.7.5 → 0.8.0-rc.6
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} +113 -2
- package/README.md +20 -4
- package/SECURITY.md +93 -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 +133 -0
- package/dist/cli.js +1995 -551
- package/dist/commands/agent-dispatch.js +110 -0
- package/dist/commands/agent-support.js +68 -0
- package/dist/commands/completions.js +3 -0
- package/dist/commands/config-cli.js +130 -534
- package/dist/commands/consolidate.js +1531 -0
- package/dist/commands/curate.js +44 -3
- package/dist/commands/db-cli.js +23 -0
- package/dist/commands/distill-promotion-policy.js +660 -0
- package/dist/commands/distill.js +990 -75
- package/dist/commands/eval-cases.js +43 -0
- package/dist/commands/events.js +5 -23
- package/dist/commands/graph.js +477 -0
- package/dist/commands/health.js +400 -0
- package/dist/commands/help/help-accept.md +9 -0
- package/dist/commands/help/help-improve.md +77 -0
- package/dist/commands/help/help-proposals.md +15 -0
- package/dist/commands/help/help-propose.md +17 -0
- package/dist/commands/help/help-reject.md +8 -0
- package/dist/commands/history.js +54 -46
- package/dist/commands/improve-profiles.js +146 -0
- package/dist/commands/improve-result-file.js +103 -0
- package/dist/commands/improve.js +2175 -0
- package/dist/commands/info.js +5 -2
- package/dist/commands/init.js +50 -2
- package/dist/commands/installed-stashes.js +102 -139
- package/dist/commands/knowledge.js +136 -0
- package/dist/commands/lint/agent-linter.js +49 -0
- package/dist/commands/lint/base-linter.js +479 -0
- package/dist/commands/lint/command-linter.js +49 -0
- package/dist/commands/lint/default-linter.js +16 -0
- package/dist/commands/lint/index.js +183 -0
- package/dist/commands/lint/knowledge-linter.js +16 -0
- package/dist/commands/lint/markdown-insertion.js +343 -0
- package/dist/commands/lint/memory-linter.js +61 -0
- package/dist/commands/lint/registry.js +36 -0
- package/dist/commands/lint/skill-linter.js +45 -0
- package/dist/commands/lint/task-linter.js +50 -0
- package/dist/commands/lint/types.js +4 -0
- package/dist/commands/lint/vault-key-rules.js +139 -0
- package/dist/commands/lint/workflow-linter.js +56 -0
- package/dist/commands/lint.js +4 -0
- package/dist/commands/migration-help.js +5 -2
- package/dist/commands/proposal.js +66 -12
- package/dist/commands/propose.js +86 -31
- package/dist/commands/reflect.js +1119 -73
- package/dist/commands/registry-search.js +5 -2
- package/dist/commands/remember.js +69 -6
- package/dist/commands/schema-repair.js +203 -0
- package/dist/commands/search.js +115 -14
- package/dist/commands/self-update.js +3 -0
- package/dist/commands/show.js +144 -25
- package/dist/commands/source-add.js +17 -45
- package/dist/commands/source-clone.js +3 -0
- package/dist/commands/source-manage.js +14 -19
- package/dist/commands/tasks.js +438 -0
- package/dist/commands/url-checker.js +42 -0
- package/dist/commands/vault.js +130 -77
- package/dist/core/action-contributors.js +28 -0
- package/dist/core/asset-ref.js +7 -0
- package/dist/core/asset-registry.js +7 -16
- package/dist/core/asset-serialize.js +88 -0
- package/dist/core/asset-spec.js +22 -0
- package/dist/core/common.js +157 -0
- package/dist/core/concurrent.js +25 -0
- package/dist/core/config-io.js +347 -0
- package/dist/core/config-migration.js +625 -0
- package/dist/core/config-schema.js +501 -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 +327 -987
- package/dist/core/errors.js +40 -19
- package/dist/core/events.js +91 -138
- package/dist/core/file-lock.js +104 -0
- package/dist/core/frontmatter.js +3 -6
- package/dist/core/lesson-lint.js +3 -0
- package/dist/core/markdown.js +20 -0
- package/dist/core/memory-belief.js +62 -0
- package/dist/core/memory-contradiction-detect.js +274 -0
- package/dist/core/memory-improve.js +806 -0
- package/dist/core/parse.js +158 -0
- package/dist/core/paths.js +326 -14
- package/dist/core/proposal-quality-validators.js +364 -0
- package/dist/core/proposal-validators.js +69 -0
- package/dist/core/proposals.js +498 -42
- package/dist/core/state-db.js +927 -0
- package/dist/core/text-truncation.js +107 -0
- package/dist/core/time.js +54 -0
- package/dist/core/warn.js +62 -1
- package/dist/core/write-source.js +3 -0
- package/dist/indexer/db-backup.js +391 -0
- package/dist/indexer/db-search.js +152 -253
- package/dist/indexer/db.js +933 -103
- package/dist/indexer/ensure-index.js +64 -0
- package/dist/indexer/file-context.js +3 -0
- package/dist/indexer/graph-boost.js +376 -101
- package/dist/indexer/graph-db.js +391 -0
- package/dist/indexer/graph-dedup.js +95 -0
- package/dist/indexer/graph-extraction.js +550 -124
- package/dist/indexer/index-context.js +4 -0
- package/dist/indexer/indexer.js +506 -291
- package/dist/indexer/llm-cache.js +47 -0
- package/dist/indexer/manifest.js +3 -0
- package/dist/indexer/matchers.js +148 -160
- package/dist/indexer/memory-inference.js +99 -74
- package/dist/indexer/metadata-contributors.js +29 -0
- package/dist/indexer/metadata.js +255 -196
- package/dist/indexer/path-resolver.js +92 -0
- package/dist/indexer/project-context.js +192 -0
- package/dist/indexer/ranking-contributors.js +331 -0
- package/dist/indexer/ranking.js +81 -0
- package/dist/indexer/search-fields.js +5 -9
- package/dist/indexer/search-hit-enrichers.js +111 -0
- package/dist/indexer/search-source.js +44 -10
- package/dist/indexer/semantic-status.js +5 -16
- package/dist/indexer/staleness-detect.js +447 -0
- package/dist/indexer/usage-events.js +12 -9
- package/dist/indexer/walker.js +28 -0
- package/dist/integrations/agent/builders.js +135 -0
- package/dist/integrations/agent/config.js +122 -230
- package/dist/integrations/agent/detect.js +3 -0
- package/dist/integrations/agent/index.js +7 -13
- package/dist/integrations/agent/model-aliases.js +55 -0
- package/dist/integrations/agent/profiles.js +70 -5
- package/dist/integrations/agent/prompts.js +150 -74
- package/dist/integrations/agent/runner.js +151 -0
- package/dist/integrations/agent/sdk-runner.js +126 -0
- package/dist/integrations/agent/spawn.js +118 -23
- package/dist/integrations/github.js +3 -0
- package/dist/integrations/lockfile.js +32 -69
- package/dist/integrations/session-logs/index.js +68 -0
- package/dist/integrations/session-logs/providers/claude-code.js +59 -0
- package/dist/integrations/session-logs/providers/opencode.js +55 -0
- package/dist/integrations/session-logs/types.js +4 -0
- package/dist/llm/call-ai.js +62 -0
- package/dist/llm/client.js +72 -124
- package/dist/llm/embedder.js +3 -19
- package/dist/llm/embedders/cache.js +3 -7
- package/dist/llm/embedders/local.js +3 -0
- package/dist/llm/embedders/remote.js +20 -8
- package/dist/llm/embedders/types.js +3 -7
- package/dist/llm/feature-gate.js +89 -48
- package/dist/llm/graph-extract.js +676 -70
- package/dist/llm/index-passes.js +9 -23
- package/dist/llm/memory-infer.js +52 -71
- package/dist/llm/metadata-enhance.js +42 -29
- package/dist/llm/prompts/graph-extract-user-prompt.md +35 -0
- package/dist/output/cli-hints-full.md +281 -0
- package/dist/output/cli-hints-short.md +65 -0
- package/dist/output/cli-hints.js +5 -318
- package/dist/output/context.js +3 -0
- package/dist/output/renderers.js +223 -256
- package/dist/output/shapes.js +150 -105
- package/dist/output/text.js +318 -30
- package/dist/registry/build-index.js +3 -0
- package/dist/registry/create-provider-registry.js +3 -0
- package/dist/registry/factory.js +3 -0
- package/dist/registry/origin-resolve.js +3 -0
- package/dist/registry/providers/index.js +3 -0
- package/dist/registry/providers/skills-sh.js +70 -49
- package/dist/registry/providers/static-index.js +53 -48
- 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 +17307 -0
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +8900 -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 +775 -37
- package/dist/setup/steps.js +3 -15
- package/dist/sources/include.js +3 -0
- package/dist/sources/provider-factory.js +5 -12
- package/dist/sources/provider.js +3 -20
- package/dist/sources/providers/filesystem.js +19 -23
- package/dist/sources/providers/git.js +7 -5
- 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 +7 -0
- package/dist/tasks/backends/cron.js +203 -0
- package/dist/tasks/backends/exec-utils.js +28 -0
- package/dist/tasks/backends/index.js +24 -0
- package/dist/tasks/backends/launchd-template.xml +19 -0
- package/dist/tasks/backends/launchd.js +187 -0
- package/dist/tasks/backends/schtasks-template.xml +29 -0
- package/dist/tasks/backends/schtasks.js +215 -0
- package/dist/tasks/parser.js +211 -0
- package/dist/tasks/resolveAkmBin.js +87 -0
- package/dist/tasks/runner.js +458 -0
- package/dist/tasks/schedule.js +211 -0
- package/dist/tasks/schema.js +15 -0
- package/dist/tasks/validator.js +62 -0
- package/dist/version.js +3 -0
- package/dist/wiki/index-template.md +12 -0
- package/dist/wiki/ingest-workflow-template.md +54 -0
- package/dist/wiki/log-template.md +8 -0
- package/dist/wiki/schema-template.md +61 -0
- package/dist/wiki/wiki-templates.js +15 -0
- package/dist/wiki/wiki.js +13 -61
- package/dist/workflows/authoring.js +8 -25
- 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 +11 -3
- package/dist/workflows/runs.js +62 -91
- package/dist/workflows/schema.js +3 -0
- package/dist/workflows/scope-key.js +3 -0
- package/dist/workflows/validator.js +4 -8
- package/dist/workflows/workflow-template.md +24 -0
- package/docs/README.md +9 -2
- package/docs/data-and-telemetry.md +225 -0
- package/docs/migration/release-notes/0.7.0.md +1 -1
- package/docs/migration/release-notes/0.7.5.md +2 -2
- package/docs/migration/release-notes/0.8.0.md +48 -0
- package/docs/migration/v0.7-to-v0.8.md +1307 -0
- package/package.json +20 -8
- package/.github/LICENSE +0 -374
- package/dist/commands/install-audit.js +0 -381
- package/dist/templates/wiki-templates.js +0 -100
|
@@ -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
|
*
|
|
@@ -23,13 +26,14 @@
|
|
|
23
26
|
* during validation. We carry it through if the agent supplies it.
|
|
24
27
|
*/
|
|
25
28
|
import { TYPE_DIRS } from "../../core/asset-spec";
|
|
29
|
+
import { parseEmbeddedJsonResponse, stripCodeFences, stripThinkBlocks } from "../../core/parse";
|
|
26
30
|
/**
|
|
27
31
|
* Per-asset-type frontmatter / authoring hints surfaced in the prompt so
|
|
28
32
|
* the agent can produce content that passes proposal validation. Kept tiny:
|
|
29
33
|
* full schema docs live in `docs/` — these are nudges, not contracts.
|
|
30
34
|
*/
|
|
31
35
|
const TYPE_HINTS = {
|
|
32
|
-
lesson: "lesson assets MUST start with frontmatter containing `description` and `when_to_use` keys (both non-empty). Body
|
|
36
|
+
lesson: "lesson assets MUST start with frontmatter containing `description` and `when_to_use` keys (both non-empty). Body: 1–3 short paragraphs of practical guidance. A lesson is NOT a restatement of the source asset — it answers: When should I reach for this? What goes wrong without it? What did real use reveal that the asset itself doesn't say?",
|
|
33
37
|
skill: "skill assets are stored as `skills/<name>/SKILL.md`. Frontmatter typically includes `name`, `description`, and `when_to_use`.",
|
|
34
38
|
command: "command assets are markdown with optional frontmatter (`name`, `description`). The body is the prompt template the user invokes.",
|
|
35
39
|
agent: "agent assets are markdown with frontmatter describing the agent role (`name`, `description`, optional `tools`, `model`).",
|
|
@@ -80,7 +84,15 @@ function fileWriteContract(draftFilePath) {
|
|
|
80
84
|
export function buildReflectPrompt(input) {
|
|
81
85
|
const sections = [];
|
|
82
86
|
if (input.ref && input.type && input.name) {
|
|
83
|
-
|
|
87
|
+
// Change 2 — type-conditioned goal framing
|
|
88
|
+
const isLesson = input.type === "lesson";
|
|
89
|
+
const isSkill = input.type === "skill";
|
|
90
|
+
const goalSentence = isLesson
|
|
91
|
+
? `Your task is to distill what usage signals reveal about this ${input.type} asset — when to reach for it, what goes wrong without it, and what real use has revealed that the asset itself does not say. Do not reproduce the source content; your proposal must add information the source does not contain.`
|
|
92
|
+
: isSkill
|
|
93
|
+
? "Your task is to review this skill asset, identify what the feedback and related distilled lessons show is broken, missing, unclear, or durable enough to promote into long-term documentation, and produce a single improved proposal. If the strongest evidence points to companion reference material rather than the main SKILL.md, you may instead propose a skill-adjacent knowledge doc such as `knowledge:skills/<skill>/references/<topic>`."
|
|
94
|
+
: `Your task is to review this ${input.type} asset, identify what the feedback signals as broken, missing, or unclear, and produce an improved version. Do not reproduce the source content unchanged; your proposal must correct or add something the source lacks.`;
|
|
95
|
+
sections.push(goalSentence);
|
|
84
96
|
sections.push(`Target ref: ${input.ref}`);
|
|
85
97
|
sections.push(`Asset-type guidance: ${hintForType(input.type)}`);
|
|
86
98
|
}
|
|
@@ -92,10 +104,35 @@ export function buildReflectPrompt(input) {
|
|
|
92
104
|
if (input.task?.trim()) {
|
|
93
105
|
sections.push(`Task / focus: ${input.task.trim()}`);
|
|
94
106
|
}
|
|
107
|
+
// Change 3 & 4 — feedback moved before asset content; missing else branch added
|
|
108
|
+
if (input.feedback && input.feedback.length > 0) {
|
|
109
|
+
sections.push("Recent feedback / signals:");
|
|
110
|
+
for (const line of input.feedback)
|
|
111
|
+
sections.push(`- ${line}`);
|
|
112
|
+
}
|
|
113
|
+
else if (!input.ref) {
|
|
114
|
+
sections.push("Recent feedback / signals:");
|
|
115
|
+
sections.push("- (no feedback events recorded)");
|
|
116
|
+
}
|
|
117
|
+
else if (input.type === "skill" && input.relatedLessons && input.relatedLessons.length > 0) {
|
|
118
|
+
sections.push("No direct feedback events were recorded. Limit substantive changes to what is justified by the related distilled lessons below; do not speculate beyond that evidence.");
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
// ref is set but no feedback — explicitly constrain scope to schema compliance
|
|
122
|
+
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.");
|
|
123
|
+
}
|
|
95
124
|
if (input.assetContent?.trim()) {
|
|
96
|
-
|
|
125
|
+
// Cap at 12 000 chars to stay well under OS ARG_MAX when the prompt is
|
|
126
|
+
// passed as a CLI argument to opencode/claude. Large assets (wiki snapshots,
|
|
127
|
+
// long runbooks) would otherwise trigger E2BIG on posix_spawn.
|
|
128
|
+
const REFLECT_CONTENT_CAP = 12_000;
|
|
129
|
+
const body = input.assetContent.trimEnd();
|
|
130
|
+
const truncated = body.length > REFLECT_CONTENT_CAP;
|
|
131
|
+
sections.push(truncated
|
|
132
|
+
? `Current asset content (first ${REFLECT_CONTENT_CAP} chars — full asset is ${body.length} chars):`
|
|
133
|
+
: "Current asset content (verbatim):");
|
|
97
134
|
sections.push("```");
|
|
98
|
-
sections.push(
|
|
135
|
+
sections.push(truncated ? `${body.slice(0, REFLECT_CONTENT_CAP)}\n... [truncated — focus on the visible portion]` : body);
|
|
99
136
|
sections.push("```");
|
|
100
137
|
}
|
|
101
138
|
else if (input.ref) {
|
|
@@ -104,21 +141,78 @@ export function buildReflectPrompt(input) {
|
|
|
104
141
|
else {
|
|
105
142
|
sections.push("(No existing asset content was supplied.)");
|
|
106
143
|
}
|
|
107
|
-
if (input.feedback && input.feedback.length > 0) {
|
|
108
|
-
sections.push("Recent feedback / signals:");
|
|
109
|
-
for (const line of input.feedback)
|
|
110
|
-
sections.push(`- ${line}`);
|
|
111
|
-
}
|
|
112
|
-
else if (!input.ref) {
|
|
113
|
-
sections.push("Recent feedback / signals:");
|
|
114
|
-
sections.push("- (no feedback events recorded)");
|
|
115
|
-
}
|
|
116
144
|
if (input.schemaHints && input.schemaHints.length > 0) {
|
|
117
145
|
sections.push("Schema / lint hints to address:");
|
|
118
146
|
for (const line of input.schemaHints)
|
|
119
147
|
sections.push(`- ${line}`);
|
|
120
148
|
}
|
|
121
|
-
|
|
149
|
+
if (input.relatedLessons && input.relatedLessons.length > 0) {
|
|
150
|
+
sections.push("Related distilled lessons to evaluate for consolidation:");
|
|
151
|
+
for (const lesson of input.relatedLessons) {
|
|
152
|
+
sections.push(`Lesson ref: ${lesson.ref}`);
|
|
153
|
+
sections.push("```");
|
|
154
|
+
sections.push(lesson.content.trimEnd());
|
|
155
|
+
sections.push("```");
|
|
156
|
+
}
|
|
157
|
+
sections.push("Evaluate whether these lessons contain strong evidence of factual, repeatable guidance that should be promoted into long-term skill documentation.");
|
|
158
|
+
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.");
|
|
159
|
+
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.");
|
|
160
|
+
}
|
|
161
|
+
if (input.rejectedProposals && input.rejectedProposals.length > 0) {
|
|
162
|
+
const lines = ["## Previously Rejected Proposals"];
|
|
163
|
+
lines.push("The following proposals for this ref were already reviewed and rejected. " +
|
|
164
|
+
"Do NOT reproduce the same content or the same structural shape. " +
|
|
165
|
+
"Your new proposal must meaningfully differ from each of these in its approach, framing, or evidence used.");
|
|
166
|
+
for (const rp of input.rejectedProposals) {
|
|
167
|
+
lines.push(`\nRef: ${rp.ref}`);
|
|
168
|
+
lines.push(`Rejection reason: ${rp.reason}`);
|
|
169
|
+
if (rp.contentPreview) {
|
|
170
|
+
lines.push("Rejected content preview:");
|
|
171
|
+
lines.push("```");
|
|
172
|
+
lines.push(rp.contentPreview);
|
|
173
|
+
lines.push("```");
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
sections.push(lines.join("\n"));
|
|
177
|
+
}
|
|
178
|
+
if (input.avoidPatterns && input.avoidPatterns.length > 0) {
|
|
179
|
+
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")}`);
|
|
180
|
+
}
|
|
181
|
+
// R-1 / #372: Self-Refine (arXiv:2303.17651) — inject prior draft as critique target.
|
|
182
|
+
// On refinement iterations (iter > 0), the agent is shown its previous proposal
|
|
183
|
+
// and asked to self-critique and improve it rather than starting from scratch.
|
|
184
|
+
if (input.priorDraft?.trim()) {
|
|
185
|
+
sections.push("## Self-Refine: Critique and Improve\n" +
|
|
186
|
+
"The following is your previous draft proposal. " +
|
|
187
|
+
"Identify specific weaknesses: missing evidence, vague wording, incomplete frontmatter, " +
|
|
188
|
+
"or claims that duplicate existing content without adding new signal. " +
|
|
189
|
+
"Then produce an improved version that addresses those weaknesses. " +
|
|
190
|
+
"The revised proposal must be meaningfully better than the draft below — " +
|
|
191
|
+
"do not return the same content unchanged.\n\n" +
|
|
192
|
+
"Previous draft:\n```\n" +
|
|
193
|
+
input.priorDraft.trimEnd() +
|
|
194
|
+
"\n```");
|
|
195
|
+
}
|
|
196
|
+
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.");
|
|
197
|
+
// Content-preservation safety rails (#reflect-pipeline-fixes).
|
|
198
|
+
// These rules counter the observed failure modes where reflect rewrites
|
|
199
|
+
// asset content into shorter prose, drops concrete structure, or strips
|
|
200
|
+
// load-bearing frontmatter. Loud and explicit so small models follow.
|
|
201
|
+
if (input.ref && input.assetContent?.trim()) {
|
|
202
|
+
sections.push([
|
|
203
|
+
"## Content preservation rules (MUST follow)",
|
|
204
|
+
"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.",
|
|
205
|
+
"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.",
|
|
206
|
+
"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 ... -->`).",
|
|
207
|
+
"4. DO NOT pad the asset with speculative material. The improved body must be at most 200% of the source body length unless the feedback explicitly requests added sections.",
|
|
208
|
+
"5. Improve clarity of surrounding prose, fix structural issues, add missing required frontmatter fields. Do NOT rewrite a runbook into an essay.",
|
|
209
|
+
].join("\n"));
|
|
210
|
+
}
|
|
211
|
+
if (!input.draftFilePath && input.ref) {
|
|
212
|
+
// Reinforce that the `ref` field is mandatory and must exactly match the target.
|
|
213
|
+
// Small models frequently omit `ref` from the response JSON, causing parse errors.
|
|
214
|
+
sections.push(`IMPORTANT: The JSON "ref" field is REQUIRED. It MUST be exactly: "${input.ref}"`);
|
|
215
|
+
}
|
|
122
216
|
sections.push(input.draftFilePath ? fileWriteContract(input.draftFilePath) : RESPONSE_CONTRACT_JSON);
|
|
123
217
|
return sections.join("\n\n");
|
|
124
218
|
}
|
|
@@ -141,6 +235,33 @@ export function buildProposePrompt(input) {
|
|
|
141
235
|
sections.push(input.draftFilePath ? fileWriteContract(input.draftFilePath) : RESPONSE_CONTRACT_JSON);
|
|
142
236
|
return sections.join("\n\n");
|
|
143
237
|
}
|
|
238
|
+
/**
|
|
239
|
+
* Build the prompt for the schema repair pass in `akm improve`. Asks the
|
|
240
|
+
* agent to add the minimal required frontmatter to an asset that failed
|
|
241
|
+
* validation — without rewriting the body.
|
|
242
|
+
*/
|
|
243
|
+
export function buildSchemaRepairPrompt(input) {
|
|
244
|
+
const sections = [];
|
|
245
|
+
sections.push(`This ${input.type} asset failed schema validation with the error: "${input.reason}". ` +
|
|
246
|
+
`Your task is to fix the schema issue by adding or correcting the missing/invalid field(s) ` +
|
|
247
|
+
`while preserving all existing content.`);
|
|
248
|
+
sections.push(`Target ref: ${input.ref}`);
|
|
249
|
+
sections.push(`Schema requirements for ${input.type} assets: ${hintForType(input.type)}`);
|
|
250
|
+
const CONTENT_CAP = 3000;
|
|
251
|
+
const body = input.assetContent.trimEnd();
|
|
252
|
+
const truncated = body.length > CONTENT_CAP;
|
|
253
|
+
sections.push("Current asset content (first 3000 chars — sufficient to generate missing frontmatter):");
|
|
254
|
+
sections.push("```");
|
|
255
|
+
sections.push(truncated ? `${body.slice(0, CONTENT_CAP)}\n... [truncated]` : body);
|
|
256
|
+
sections.push("```");
|
|
257
|
+
sections.push("Produce the minimal fix: add ONLY the missing required frontmatter field(s). " +
|
|
258
|
+
"Do not rewrite the body unless it is empty. " +
|
|
259
|
+
"If `description` is missing, generate a concise one-sentence description from the content. " +
|
|
260
|
+
"If `when_to_use` is missing, generate a one-line trigger sentence. " +
|
|
261
|
+
"Preserve all existing frontmatter keys and the full body verbatim.");
|
|
262
|
+
sections.push(input.draftFilePath ? fileWriteContract(input.draftFilePath) : RESPONSE_CONTRACT_JSON);
|
|
263
|
+
return sections.join("\n\n");
|
|
264
|
+
}
|
|
144
265
|
/**
|
|
145
266
|
* Parse agent stdout into a proposal payload. The agent contract requires a
|
|
146
267
|
* single JSON object; anything else is reported as a parse error so callers
|
|
@@ -151,7 +272,8 @@ export function buildProposePrompt(input) {
|
|
|
151
272
|
* 2. Prose preamble / postamble around the JSON object (handled by `extractEmbeddedJson`).
|
|
152
273
|
*/
|
|
153
274
|
export function parseAgentProposalPayload(stdout) {
|
|
154
|
-
|
|
275
|
+
// Strip <think> blocks and fences, then attempt full parse with embedded fallback.
|
|
276
|
+
const trimmed = stripCodeFences(stripThinkBlocks(stdout)).trim();
|
|
155
277
|
if (!trimmed)
|
|
156
278
|
throw new Error("agent produced empty output");
|
|
157
279
|
let parsed;
|
|
@@ -162,7 +284,7 @@ export function parseAgentProposalPayload(stdout) {
|
|
|
162
284
|
// Agent output contains prose before/after the JSON object (e.g. a local
|
|
163
285
|
// LLM that narrates before responding). Try extracting the first balanced
|
|
164
286
|
// top-level `{…}` from the text rather than failing immediately.
|
|
165
|
-
const embedded =
|
|
287
|
+
const embedded = parseEmbeddedJsonResponse(trimmed);
|
|
166
288
|
if (!embedded)
|
|
167
289
|
throw directErr;
|
|
168
290
|
parsed = embedded;
|
|
@@ -180,68 +302,22 @@ export function parseAgentProposalPayload(stdout) {
|
|
|
180
302
|
if (parsed.frontmatter && typeof parsed.frontmatter === "object" && !Array.isArray(parsed.frontmatter)) {
|
|
181
303
|
out.frontmatter = parsed.frontmatter;
|
|
182
304
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
function extractEmbeddedJson(text) {
|
|
192
|
-
for (let start = 0; start < text.length; start++) {
|
|
193
|
-
if (text[start] !== "{")
|
|
194
|
-
continue;
|
|
195
|
-
let depth = 0;
|
|
196
|
-
let inString = false;
|
|
197
|
-
let escaped = false;
|
|
198
|
-
for (let i = start; i < text.length; i++) {
|
|
199
|
-
const ch = text[i];
|
|
200
|
-
if (inString) {
|
|
201
|
-
if (escaped) {
|
|
202
|
-
escaped = false;
|
|
203
|
-
}
|
|
204
|
-
else if (ch === "\\") {
|
|
205
|
-
escaped = true;
|
|
206
|
-
}
|
|
207
|
-
else if (ch === '"') {
|
|
208
|
-
inString = false;
|
|
209
|
-
}
|
|
210
|
-
continue;
|
|
211
|
-
}
|
|
212
|
-
if (ch === '"') {
|
|
213
|
-
inString = true;
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
|
-
if (ch === "{")
|
|
217
|
-
depth++;
|
|
218
|
-
if (ch === "}") {
|
|
219
|
-
depth--;
|
|
220
|
-
if (depth === 0) {
|
|
221
|
-
try {
|
|
222
|
-
return JSON.parse(text.slice(start, i + 1));
|
|
223
|
-
}
|
|
224
|
-
catch {
|
|
225
|
-
break;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
}
|
|
305
|
+
// Phase 6A: extract optional `confidence` (number in [0, 1]). Clamp gently
|
|
306
|
+
// rather than reject — a model that returns 1.0 or 0 with extra precision
|
|
307
|
+
// (e.g. 1.0000001) should still surface a usable score. Anything that isn't
|
|
308
|
+
// a finite number is dropped so downstream `createProposal` can rely on the
|
|
309
|
+
// shape invariant.
|
|
310
|
+
if (typeof parsed.confidence === "number" && Number.isFinite(parsed.confidence)) {
|
|
311
|
+
const clamped = Math.max(0, Math.min(1, parsed.confidence));
|
|
312
|
+
out.confidence = clamped;
|
|
230
313
|
}
|
|
231
|
-
return
|
|
314
|
+
return out;
|
|
232
315
|
}
|
|
233
316
|
/**
|
|
234
317
|
* Strip `\`\`\`json … \`\`\`` fences and `<think>…</think>` reasoning blocks
|
|
235
|
-
* from agent output.
|
|
236
|
-
*
|
|
318
|
+
* from agent output. Thin wrapper around `core/parse` helpers, kept exported
|
|
319
|
+
* for backward compatibility (re-exported from `integrations/agent/index.ts`).
|
|
237
320
|
*/
|
|
238
321
|
export function stripJsonFences(text) {
|
|
239
|
-
|
|
240
|
-
.trim()
|
|
241
|
-
.replace(/<think>[\s\S]*?<\/think>/gi, "")
|
|
242
|
-
.trim();
|
|
243
|
-
const fenced = stripped.match(/^```(?:json)?\s*\n([\s\S]*?)\n```$/);
|
|
244
|
-
if (fenced)
|
|
245
|
-
return fenced[1] ?? stripped;
|
|
246
|
-
return stripped;
|
|
322
|
+
return stripCodeFences(stripThinkBlocks(text));
|
|
247
323
|
}
|
|
@@ -0,0 +1,151 @@
|
|
|
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
|
+
function resolveEffectiveMode(entry, profileName, config) {
|
|
6
|
+
if (entry.mode)
|
|
7
|
+
return entry.mode;
|
|
8
|
+
// Infer from profile pool when profile is specified
|
|
9
|
+
if (profileName) {
|
|
10
|
+
if (config.profiles?.llm?.[profileName])
|
|
11
|
+
return "llm";
|
|
12
|
+
const agentProfile = config.profiles?.agent?.[profileName];
|
|
13
|
+
if (agentProfile) {
|
|
14
|
+
return agentProfile.platform === "opencode-sdk" ? "sdk" : "agent";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
// Fall back to defaults
|
|
18
|
+
if (config.defaults?.llm)
|
|
19
|
+
return "llm";
|
|
20
|
+
if (config.defaults?.agent)
|
|
21
|
+
return "agent";
|
|
22
|
+
return "llm";
|
|
23
|
+
}
|
|
24
|
+
function resolveProfileName(entry, mode, config) {
|
|
25
|
+
if (entry.profile)
|
|
26
|
+
return entry.profile;
|
|
27
|
+
if (mode === "llm") {
|
|
28
|
+
const defaultName = config.defaults?.llm;
|
|
29
|
+
if (defaultName)
|
|
30
|
+
return defaultName;
|
|
31
|
+
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`.");
|
|
32
|
+
}
|
|
33
|
+
const defaultName = config.defaults?.agent;
|
|
34
|
+
if (defaultName)
|
|
35
|
+
return defaultName;
|
|
36
|
+
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`.");
|
|
37
|
+
}
|
|
38
|
+
function buildLlmRunnerSpec(profileName, timeoutMs, config) {
|
|
39
|
+
const profile = config.profiles?.llm?.[profileName];
|
|
40
|
+
if (!profile) {
|
|
41
|
+
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.`);
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
kind: "llm",
|
|
45
|
+
connection: profile,
|
|
46
|
+
...(typeof timeoutMs === "number" ? { timeoutMs } : {}),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function buildAgentRunnerSpec(kind, profileName, timeoutMs, config) {
|
|
50
|
+
const profileConfig = config.profiles?.agent?.[profileName];
|
|
51
|
+
if (!profileConfig) {
|
|
52
|
+
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.`);
|
|
53
|
+
}
|
|
54
|
+
// Validate mode/platform consistency
|
|
55
|
+
if (kind === "sdk" && profileConfig.platform !== "opencode-sdk") {
|
|
56
|
+
throw new ConfigError(`Mode "sdk" requires platform "opencode-sdk", but profiles.agent["${profileName}"].platform is "${profileConfig.platform}".`, "INVALID_CONFIG_FILE");
|
|
57
|
+
}
|
|
58
|
+
if (kind === "agent" && profileConfig.platform === "opencode-sdk") {
|
|
59
|
+
throw new ConfigError(`Mode "agent" requires platform "opencode" or "claude", but profiles.agent["${profileName}"].platform is "opencode-sdk".`, "INVALID_CONFIG_FILE");
|
|
60
|
+
}
|
|
61
|
+
const agentProfile = {
|
|
62
|
+
name: profileName,
|
|
63
|
+
bin: profileConfig.bin ?? profileName,
|
|
64
|
+
args: profileConfig.args ?? [],
|
|
65
|
+
stdio: "captured",
|
|
66
|
+
envPassthrough: [],
|
|
67
|
+
parseOutput: "text",
|
|
68
|
+
...(profileConfig.model ? { model: profileConfig.model } : {}),
|
|
69
|
+
...(profileConfig.workspace ? { workspace: profileConfig.workspace } : {}),
|
|
70
|
+
};
|
|
71
|
+
return {
|
|
72
|
+
kind,
|
|
73
|
+
profile: agentProfile,
|
|
74
|
+
...(typeof timeoutMs === "number" ? { timeoutMs } : {}),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Resolve the runner used for "validation" passes on the `improve` section
|
|
79
|
+
* (Advantage D3 / Phase 4B — third model tier).
|
|
80
|
+
*
|
|
81
|
+
* Look-up order:
|
|
82
|
+
* 1. `profiles.improve.default.processes.validation` (preferred — lets users
|
|
83
|
+
* wire a lower-cost classifier model for staleness detection, confidence
|
|
84
|
+
* scoring, and lesson classification).
|
|
85
|
+
* 2. `defaults.llm` as a final fallback so callers always get a usable
|
|
86
|
+
* runner when any LLM is configured.
|
|
87
|
+
*
|
|
88
|
+
* Returns `null` when neither is configured (callers may then skip the
|
|
89
|
+
* validation pass rather than throwing).
|
|
90
|
+
*/
|
|
91
|
+
export function resolveValidationRunner(config) {
|
|
92
|
+
const validation = config.profiles?.improve?.default?.processes?.validation;
|
|
93
|
+
if (validation && validation.enabled !== false && (validation.profile || validation.mode)) {
|
|
94
|
+
try {
|
|
95
|
+
const spec = resolveImproveProcessRunnerFromProfile(validation, config);
|
|
96
|
+
if (spec)
|
|
97
|
+
return spec;
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// Fall through to defaults.llm below.
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const defaultLlm = config.defaults?.llm;
|
|
104
|
+
if (defaultLlm) {
|
|
105
|
+
try {
|
|
106
|
+
return buildLlmRunnerSpec(defaultLlm, undefined, config);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
export function resolveRunner(mode, profileName, config) {
|
|
115
|
+
if (mode === "llm")
|
|
116
|
+
return buildLlmRunnerSpec(profileName, undefined, config);
|
|
117
|
+
return buildAgentRunnerSpec(mode, profileName, undefined, config);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Resolve a RunnerSpec from an improve-profile process entry. Returns `null`
|
|
121
|
+
* when the entry is absent or provides no overrides — callers should fall
|
|
122
|
+
* back to the default per-process runner resolution path.
|
|
123
|
+
*/
|
|
124
|
+
export function resolveImproveProcessRunnerFromProfile(processConfig, config) {
|
|
125
|
+
if (!processConfig)
|
|
126
|
+
return null;
|
|
127
|
+
const { mode: explicitMode, profile, timeoutMs } = processConfig;
|
|
128
|
+
if (!explicitMode && !profile)
|
|
129
|
+
return null;
|
|
130
|
+
const mode = explicitMode ?? resolveEffectiveMode(processConfig, profile, config);
|
|
131
|
+
const profileName = profile ?? resolveProfileName(processConfig, mode, config);
|
|
132
|
+
if (mode === "llm") {
|
|
133
|
+
return buildLlmRunnerSpec(profileName, timeoutMs, config);
|
|
134
|
+
}
|
|
135
|
+
if (mode === "agent" || mode === "sdk") {
|
|
136
|
+
return buildAgentRunnerSpec(mode, profileName, timeoutMs, config);
|
|
137
|
+
}
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Convenience accessor for callers that previously read
|
|
142
|
+
* `getProcessOptions("index", "staleness_detection", config).thresholdDays`.
|
|
143
|
+
* After the 0.8.0 migration, those values live on first-class config keys —
|
|
144
|
+
* see `config.index?.stalenessDetection?.thresholdDays` etc.
|
|
145
|
+
*/
|
|
146
|
+
export function getStalenessDetectionThresholdDays(config) {
|
|
147
|
+
return config.index?.stalenessDetection?.thresholdDays;
|
|
148
|
+
}
|
|
149
|
+
// Re-export `isProcessEnabled` from feature-gate.ts so callers that previously
|
|
150
|
+
// imported it from runner.ts continue to work.
|
|
151
|
+
export { isProcessEnabled } from "../../llm/feature-gate";
|
|
@@ -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
|
+
* OpenCode SDK agent runner — uses embedded @opencode-ai/sdk instead of
|
|
6
|
+
* Bun.spawn. Requires no agent CLI binary to be installed. The user provides
|
|
7
|
+
* an OpenAI-compatible endpoint (or inherits from config.llm) for the SDK.
|
|
8
|
+
*/
|
|
9
|
+
import { resolveSecret } from "../../core/config";
|
|
10
|
+
// Singleton server — started once per process, reused across calls
|
|
11
|
+
let _server = null;
|
|
12
|
+
/**
|
|
13
|
+
* Close the singleton OpenCode SDK server and reset the handle.
|
|
14
|
+
* Primarily for use in tests to ensure clean teardown between test runs.
|
|
15
|
+
*/
|
|
16
|
+
export function closeServer() {
|
|
17
|
+
try {
|
|
18
|
+
_server?.server.close();
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
/* ignore */
|
|
22
|
+
}
|
|
23
|
+
_server = null;
|
|
24
|
+
}
|
|
25
|
+
async function getOrStartServer(profile, llmConfig) {
|
|
26
|
+
if (_server)
|
|
27
|
+
return _server;
|
|
28
|
+
const { createOpencode } = await import("@opencode-ai/sdk").catch(() => {
|
|
29
|
+
throw new Error("OpenCode SDK not available. Install @opencode-ai/sdk or configure a CLI agent instead.");
|
|
30
|
+
});
|
|
31
|
+
// Resolve endpoint and model: profile fields take precedence over config.llm
|
|
32
|
+
const endpoint = profile.endpoint ?? llmConfig?.endpoint;
|
|
33
|
+
const apiKey = resolveSecret(profile.apiKey ?? llmConfig?.apiKey);
|
|
34
|
+
const model = profile.model;
|
|
35
|
+
const sdkConfig = {};
|
|
36
|
+
if (model)
|
|
37
|
+
sdkConfig.model = model;
|
|
38
|
+
if (endpoint || apiKey) {
|
|
39
|
+
// Configure a custom OpenAI-compatible provider
|
|
40
|
+
sdkConfig.provider = {
|
|
41
|
+
"akm-custom": {
|
|
42
|
+
npm: "@ai-sdk/openai-compatible",
|
|
43
|
+
options: {
|
|
44
|
+
baseURL: endpoint?.replace(/\/chat\/completions$/, "").replace(/\/$/, ""),
|
|
45
|
+
...(apiKey ? { apiKey } : {}),
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
// Use the custom provider's model if not already qualified
|
|
50
|
+
if (model && !model.includes("/")) {
|
|
51
|
+
sdkConfig.model = `akm-custom/${model}`;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
_server = (await createOpencode(Object.keys(sdkConfig).length > 0 ? { config: sdkConfig } : {}));
|
|
55
|
+
process.once("exit", () => {
|
|
56
|
+
closeServer();
|
|
57
|
+
});
|
|
58
|
+
if (!_server)
|
|
59
|
+
throw new Error("Failed to initialise OpenCode SDK server.");
|
|
60
|
+
return _server;
|
|
61
|
+
}
|
|
62
|
+
export async function runOpencodeSdk(profile, prompt, _opts = {}, llmConfig) {
|
|
63
|
+
const start = Date.now();
|
|
64
|
+
let client;
|
|
65
|
+
try {
|
|
66
|
+
({ client } = await getOrStartServer(profile, llmConfig));
|
|
67
|
+
}
|
|
68
|
+
catch (e) {
|
|
69
|
+
return {
|
|
70
|
+
ok: false,
|
|
71
|
+
stdout: "",
|
|
72
|
+
stderr: String(e),
|
|
73
|
+
durationMs: Date.now() - start,
|
|
74
|
+
exitCode: 1,
|
|
75
|
+
reason: "spawn_failed",
|
|
76
|
+
error: String(e),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
// One session per call — do NOT reuse (history accumulates, token costs grow)
|
|
80
|
+
const sessionRes = await client.session.create({ body: { title: "akm" } });
|
|
81
|
+
const sessionId = sessionRes.data?.id;
|
|
82
|
+
if (!sessionId) {
|
|
83
|
+
return {
|
|
84
|
+
ok: false,
|
|
85
|
+
stdout: "",
|
|
86
|
+
stderr: "Failed to create session",
|
|
87
|
+
durationMs: Date.now() - start,
|
|
88
|
+
exitCode: 1,
|
|
89
|
+
reason: "spawn_failed",
|
|
90
|
+
error: "Failed to create OpenCode session",
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
const result = await client.session.prompt({
|
|
95
|
+
path: { id: sessionId },
|
|
96
|
+
body: { parts: [{ type: "text", text: prompt }] },
|
|
97
|
+
});
|
|
98
|
+
const parts = result.data?.parts ?? [];
|
|
99
|
+
const textPart = parts.find((p) => p.type === "text");
|
|
100
|
+
const stdout = textPart?.text ?? "";
|
|
101
|
+
return {
|
|
102
|
+
ok: true,
|
|
103
|
+
stdout,
|
|
104
|
+
stderr: "",
|
|
105
|
+
durationMs: Date.now() - start,
|
|
106
|
+
exitCode: 0,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
catch (e) {
|
|
110
|
+
return {
|
|
111
|
+
ok: false,
|
|
112
|
+
stdout: "",
|
|
113
|
+
stderr: String(e),
|
|
114
|
+
durationMs: Date.now() - start,
|
|
115
|
+
exitCode: 1,
|
|
116
|
+
reason: "non_zero_exit",
|
|
117
|
+
error: String(e),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
finally {
|
|
121
|
+
// Clean up session to prevent disk accumulation in ~/.local/share/opencode/
|
|
122
|
+
await client.session.delete({ path: { id: sessionId } }).catch(() => { });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/** @deprecated Use {@link runOpencodeSdk} instead. */
|
|
126
|
+
export const runAgentSdk = runOpencodeSdk;
|