akm-cli 0.9.0-rc.0 → 0.9.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +339 -2
- package/SECURITY.md +23 -24
- package/dist/assets/help/help-improve.md +10 -10
- package/dist/assets/hints/cli-hints-full.md +44 -10
- package/dist/assets/hints/cli-hints-short.md +6 -2
- package/dist/assets/{profiles → improve-strategies}/default.json +1 -0
- package/dist/assets/{profiles → improve-strategies}/graph-refresh.json +1 -1
- package/dist/assets/{profiles → improve-strategies}/proactive-maintenance.json +2 -3
- package/dist/assets/{profiles → improve-strategies}/reflect-distill.json +3 -4
- package/dist/assets/prompts/workflow-unit-preamble.md +26 -0
- package/dist/assets/stash-skeleton/README.md +28 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +6 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +6 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +12 -1
- package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +11 -1
- package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +6 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +9 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +7 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +7 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +6 -0
- package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +98 -0
- package/dist/assets/stash-skeleton/facts/conventions/domains.md +63 -0
- package/dist/assets/stash-skeleton/facts/conventions/organization.md +127 -0
- package/dist/assets/tasks/core/backup.yml +1 -0
- package/dist/assets/tasks/core/extract.yml +1 -0
- package/dist/assets/tasks/core/improve.yml +1 -0
- package/dist/assets/tasks/core/index-refresh.yml +1 -0
- package/dist/assets/tasks/core/sync.yml +1 -0
- package/dist/assets/tasks/core/version-check.yml +1 -0
- package/dist/assets/tasks/graph-refresh-weekly.yml +4 -4
- package/dist/assets/templates/html/health.html +5 -1
- package/dist/cli/config-migrate.js +31 -138
- package/dist/cli/config-validate.js +10 -8
- package/dist/cli.js +48 -14
- package/dist/commands/agent/agent-dispatch.js +17 -16
- package/dist/commands/agent/agent-support.js +0 -24
- package/dist/commands/agent/contribute-cli.js +5 -15
- package/dist/commands/backup-cli.js +54 -0
- package/dist/commands/config-cli.js +45 -159
- package/dist/commands/env/env-binding.js +95 -0
- package/dist/commands/env/env-cli.js +8 -65
- package/dist/commands/env/secret.js +8 -5
- package/dist/commands/health/checks.js +130 -83
- package/dist/commands/health/html-report.js +4 -0
- package/dist/commands/health/improve-metrics.js +30 -32
- package/dist/commands/health/llm-usage.js +19 -19
- package/dist/commands/health/md-report.js +4 -0
- package/dist/commands/health/metrics.js +2 -1
- package/dist/commands/health/surfaces.js +5 -4
- package/dist/commands/health.js +1 -1
- package/dist/commands/improve/consolidate/chunking.js +2 -2
- package/dist/commands/improve/consolidate.js +28 -25
- package/dist/commands/improve/distill/promote-memory.js +5 -12
- package/dist/commands/improve/distill/quality-gate.js +5 -7
- package/dist/commands/improve/distill.js +16 -5
- package/dist/commands/improve/eligibility.js +22 -12
- package/dist/commands/improve/extract-cli.js +47 -19
- package/dist/commands/improve/extract.js +110 -62
- package/dist/commands/improve/improve-cli.js +38 -16
- package/dist/commands/improve/improve-result-file.js +30 -24
- package/dist/commands/improve/improve-strategies.js +137 -0
- package/dist/commands/improve/improve.js +60 -30
- package/dist/commands/improve/locks.js +66 -45
- package/dist/commands/improve/loop-stages.js +75 -33
- package/dist/commands/improve/memory/memory-belief.js +79 -7
- package/dist/commands/improve/memory/memory-contradiction-detect.js +12 -4
- package/dist/commands/improve/preparation.js +71 -73
- package/dist/commands/improve/procedural.js +3 -2
- package/dist/commands/improve/recombine.js +2 -1
- package/dist/commands/improve/reflect.js +119 -214
- package/dist/commands/improve/shared.js +11 -5
- package/dist/commands/lint/base-linter.js +152 -42
- package/dist/commands/mv-cli.js +809 -0
- package/dist/commands/proposal/proposal-cli.js +18 -8
- package/dist/commands/proposal/propose.js +64 -69
- package/dist/commands/read/knowledge.js +436 -4
- package/dist/commands/read/remember-cli.js +39 -2
- package/dist/commands/read/search-cli.js +6 -1
- package/dist/commands/registry-cli.js +29 -14
- package/dist/commands/remember.js +2 -0
- package/dist/commands/sources/init.js +13 -14
- package/dist/commands/sources/migration-help.js +7 -4
- package/dist/commands/sources/schema-repair.js +2 -4
- package/dist/commands/sources/source-add.js +62 -73
- package/dist/commands/sources/source-manage.js +50 -46
- package/dist/commands/sources/stash-cli.js +41 -4
- package/dist/commands/tasks/default-tasks.js +12 -12
- package/dist/commands/tasks/tasks-cli.js +7 -3
- package/dist/commands/tasks/tasks.js +113 -18
- package/dist/commands/wiki-cli.js +9 -10
- package/dist/commands/workflow-cli.js +276 -12
- package/dist/core/asset/asset-spec.js +58 -1
- package/dist/core/asset/frontmatter.js +12 -2
- package/dist/core/common.js +5 -3
- package/dist/core/config/config-io.js +28 -17
- package/dist/core/config/config-schema.js +379 -66
- package/dist/core/config/config-types.js +3 -3
- package/dist/core/config/config-version.js +29 -0
- package/dist/core/config/config-walker.js +98 -27
- package/dist/core/config/config.js +132 -266
- package/dist/core/config/deep-merge.js +41 -0
- package/dist/core/config/engine-semantics.js +32 -0
- package/dist/core/errors.js +2 -2
- package/dist/core/extra-params.js +61 -0
- package/dist/core/file-lock.js +201 -56
- package/dist/core/improve-result.js +178 -0
- package/dist/core/json-schema.js +142 -0
- package/dist/core/maintenance-barrier.js +119 -0
- package/dist/core/migration-backup.js +416 -0
- package/dist/core/paths.js +3 -0
- package/dist/core/redaction.js +358 -0
- package/dist/core/state/migrations.js +17 -2
- package/dist/core/state-db.js +44 -1
- package/dist/indexer/db/db.js +118 -2
- package/dist/indexer/graph/graph-extraction.js +28 -16
- package/dist/indexer/index-writer-lock.js +31 -24
- package/dist/indexer/index-written-assets.js +15 -6
- package/dist/indexer/indexer.js +47 -2
- package/dist/indexer/passes/memory-inference.js +10 -6
- package/dist/indexer/passes/metadata.js +250 -0
- package/dist/indexer/search/db-search.js +111 -44
- package/dist/indexer/search/fts-query.js +41 -0
- package/dist/indexer/search/ranking-contributors.js +48 -0
- package/dist/indexer/search/ranking.js +36 -23
- package/dist/indexer/search/search-fields.js +11 -1
- package/dist/indexer/walk/matchers.js +39 -0
- package/dist/integrations/agent/builder-shared.js +7 -0
- package/dist/integrations/agent/builders.js +5 -50
- package/dist/integrations/agent/config.js +3 -143
- package/dist/integrations/agent/detect.js +17 -2
- package/dist/integrations/agent/engine-resolution.js +202 -0
- package/dist/integrations/agent/index.js +1 -2
- package/dist/integrations/agent/model-aliases.js +16 -2
- package/dist/integrations/agent/profiles.js +36 -62
- package/dist/integrations/agent/runner-dispatch.js +91 -4
- package/dist/integrations/agent/runner.js +76 -207
- package/dist/integrations/agent/spawn.js +141 -20
- package/dist/integrations/harnesses/aider/agent-builder.js +112 -0
- package/dist/integrations/harnesses/aider/index.js +57 -0
- package/dist/integrations/harnesses/aider/result-extractor.js +53 -0
- package/dist/integrations/harnesses/amazonq/agent-builder.js +152 -0
- package/dist/integrations/harnesses/amazonq/index.js +58 -0
- package/dist/integrations/harnesses/amazonq/result-extractor.js +48 -0
- package/dist/integrations/harnesses/claude/agent-builder.js +46 -8
- package/dist/integrations/harnesses/claude/index.js +25 -25
- package/dist/integrations/harnesses/claude/result-extractor.js +52 -0
- package/dist/integrations/harnesses/codex/agent-builder.js +136 -0
- package/dist/integrations/harnesses/codex/index.js +62 -0
- package/dist/integrations/harnesses/codex/result-extractor.js +73 -0
- package/dist/integrations/harnesses/copilot/agent-builder.js +121 -0
- package/dist/integrations/harnesses/copilot/index.js +59 -0
- package/dist/integrations/harnesses/copilot/result-extractor.js +151 -0
- package/dist/integrations/harnesses/gemini/agent-builder.js +120 -0
- package/dist/integrations/harnesses/gemini/index.js +59 -0
- package/dist/integrations/harnesses/gemini/result-extractor.js +121 -0
- package/dist/integrations/harnesses/index.js +27 -28
- package/dist/integrations/harnesses/opencode/agent-builder.js +2 -3
- package/dist/integrations/harnesses/opencode/index.js +15 -22
- package/dist/integrations/harnesses/opencode-sdk/harness.js +60 -0
- package/dist/integrations/harnesses/opencode-sdk/index.js +8 -32
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +681 -108
- package/dist/integrations/harnesses/openhands/agent-builder.js +125 -0
- package/dist/integrations/harnesses/openhands/index.js +57 -0
- package/dist/integrations/harnesses/openhands/result-extractor.js +103 -0
- package/dist/integrations/harnesses/pi/agent-builder.js +103 -0
- package/dist/integrations/harnesses/pi/index.js +57 -0
- package/dist/integrations/harnesses/pi/result-extractor.js +135 -0
- package/dist/integrations/harnesses/types.js +8 -32
- package/dist/integrations/lockfile.js +32 -21
- package/dist/integrations/session-logs/index.js +24 -11
- package/dist/llm/client.js +48 -14
- package/dist/llm/feature-gate.js +15 -47
- package/dist/llm/graph-extract.js +1 -1
- package/dist/llm/index-passes.js +8 -42
- package/dist/llm/memory-infer-impl.js +1 -1
- package/dist/llm/usage-persist.js +4 -0
- package/dist/llm/usage-telemetry.js +35 -5
- package/dist/output/renderers.js +3 -2
- package/dist/output/shapes/helpers.js +2 -1
- package/dist/output/shapes/passthrough.js +6 -0
- package/dist/output/text/helpers.js +215 -2
- package/dist/output/text/workflow.js +3 -1
- package/dist/schemas/akm-config.json +16638 -0
- package/dist/schemas/akm-task.json +87 -0
- package/dist/schemas/akm-workflow.json +372 -0
- package/dist/scripts/migrate-storage.js +10944 -8801
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +9247 -350
- package/dist/setup/detected-engines.js +142 -0
- package/dist/setup/engine-config.js +89 -0
- package/dist/setup/setup.js +236 -132
- package/dist/setup/steps/connection.js +61 -32
- package/dist/setup/steps/platforms.js +4 -4
- package/dist/setup/steps.js +3 -2
- package/dist/storage/database.js +13 -1
- package/dist/storage/engines/sqlite-migrations.js +1 -0
- package/dist/storage/repositories/improve-runs-repository.js +5 -5
- package/dist/storage/repositories/task-history-repository.js +78 -0
- package/dist/storage/repositories/workflow-runs-repository.js +190 -1
- package/dist/tasks/parser.js +138 -52
- package/dist/tasks/runner.js +71 -75
- package/dist/tasks/schema.js +1 -1
- package/dist/tasks/validator.js +11 -6
- package/dist/text-import-hook.mjs +1 -1
- package/dist/wiki/wiki.js +9 -8
- package/dist/workflows/authoring/authoring.js +123 -10
- package/dist/workflows/authoring/workflow-program-template.yaml +31 -0
- package/dist/workflows/cli.js +4 -0
- package/dist/workflows/concurrency-policy.js +15 -0
- package/dist/workflows/db.js +200 -13
- package/dist/workflows/exec/brief.js +478 -0
- package/dist/workflows/exec/frozen-judge.js +47 -0
- package/dist/workflows/exec/native-executor.js +1034 -0
- package/dist/workflows/exec/param-secrets.js +115 -0
- package/dist/workflows/exec/report.js +1355 -0
- package/dist/workflows/exec/run-workflow.js +609 -0
- package/dist/workflows/exec/scheduler.js +71 -0
- package/dist/workflows/exec/step-work.js +1212 -0
- package/dist/workflows/exec/unit-writer.js +23 -0
- package/dist/workflows/exec/watch.js +116 -0
- package/dist/workflows/exec/worktree.js +171 -0
- package/dist/workflows/ir/compile.js +375 -0
- package/dist/workflows/ir/freeze.js +243 -0
- package/dist/workflows/ir/params.js +54 -0
- package/dist/workflows/ir/plan-hash.js +68 -0
- package/dist/workflows/ir/schema.js +545 -0
- package/dist/workflows/parser.js +10 -1
- package/dist/workflows/program/expressions.js +369 -0
- package/dist/workflows/program/parser.js +869 -0
- package/dist/workflows/program/project.js +104 -0
- package/dist/workflows/program/schema.js +54 -0
- package/dist/workflows/renderer.js +82 -5
- package/dist/workflows/resource-limits.js +20 -0
- package/dist/workflows/runtime/agent-identity.js +59 -14
- package/dist/workflows/runtime/plan-classifier.js +187 -0
- package/dist/workflows/runtime/runs.js +246 -69
- package/dist/workflows/runtime/unit-checkin.js +45 -0
- package/dist/workflows/runtime/workflow-asset-loader.js +42 -1
- package/dist/workflows/validate-summary.js +24 -3
- package/dist/workflows/validator.js +26 -1
- package/docs/data-and-telemetry.md +4 -3
- package/docs/migration/release-notes/0.6.0.md +1 -1
- package/docs/migration/release-notes/0.7.0.md +5 -4
- package/docs/migration/release-notes/0.9.0-beta.60.md +19 -0
- package/docs/migration/v0.8-to-v0.9.md +401 -0
- package/package.json +4 -2
- package/schemas/akm-config.json +16638 -0
- package/schemas/akm-task.json +87 -0
- package/schemas/akm-workflow.json +372 -0
- package/dist/commands/improve/improve-profiles.js +0 -168
- package/dist/core/config/config-migration.js +0 -602
- package/dist/core/deep-merge.js +0 -38
- package/dist/llm/call-ai.js +0 -62
- package/dist/setup/legacy-config.js +0 -106
- package/docs/README.md +0 -104
- /package/dist/assets/{profiles → improve-strategies}/catchup.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/consolidate.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/frequent.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/memory-focus.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/quick.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/recombine-only.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/synthesize.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/thorough.json +0 -0
|
@@ -143,21 +143,93 @@ function buildChildEnv(profile, options) {
|
|
|
143
143
|
}
|
|
144
144
|
return env;
|
|
145
145
|
}
|
|
146
|
+
const STREAM_READ_TIMEOUT = Symbol("stream-read-timeout");
|
|
146
147
|
async function readStream(stream, opts) {
|
|
147
148
|
if (!stream)
|
|
148
|
-
return "";
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
149
|
+
return { text: "", timedOut: false };
|
|
150
|
+
const reader = stream.getReader();
|
|
151
|
+
const decoder = new TextDecoder();
|
|
152
|
+
let text = "";
|
|
153
|
+
if (!opts?.timeoutMs) {
|
|
154
|
+
try {
|
|
155
|
+
while (true) {
|
|
156
|
+
const chunk = await reader.read();
|
|
157
|
+
if (chunk.done)
|
|
158
|
+
break;
|
|
159
|
+
text += decoder.decode(chunk.value, { stream: true });
|
|
160
|
+
}
|
|
161
|
+
text += decoder.decode();
|
|
162
|
+
return { text, timedOut: false };
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
return { text, timedOut: false, error };
|
|
166
|
+
}
|
|
167
|
+
finally {
|
|
168
|
+
try {
|
|
169
|
+
reader.releaseLock();
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
/* ignore */
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
152
176
|
// Race the stream read against a timeout so a process that is killed via
|
|
153
177
|
// SIGTERM/SIGKILL but whose pipe endpoints stay open (e.g. background
|
|
154
178
|
// threads still holding the fd) cannot block the caller indefinitely.
|
|
155
|
-
// On timeout we return whatever
|
|
156
|
-
|
|
179
|
+
// On timeout we return whatever was decoded before the pipe stopped draining.
|
|
180
|
+
const setTimeoutImpl = opts.setTimeoutFn ?? setTimeout;
|
|
181
|
+
const clearTimeoutImpl = opts.clearTimeoutFn ?? clearTimeout;
|
|
182
|
+
let timer;
|
|
157
183
|
const timeoutPromise = new Promise((resolve) => {
|
|
158
|
-
|
|
184
|
+
timer = setTimeoutImpl(() => {
|
|
185
|
+
timer = undefined;
|
|
186
|
+
resolve(STREAM_READ_TIMEOUT);
|
|
187
|
+
}, opts.timeoutMs);
|
|
188
|
+
if (typeof timer !== "number")
|
|
189
|
+
timer.unref?.();
|
|
159
190
|
});
|
|
160
|
-
|
|
191
|
+
try {
|
|
192
|
+
while (true) {
|
|
193
|
+
const chunk = await Promise.race([reader.read(), timeoutPromise]);
|
|
194
|
+
if (chunk === STREAM_READ_TIMEOUT) {
|
|
195
|
+
void reader.cancel().catch(() => { });
|
|
196
|
+
return { text, timedOut: true };
|
|
197
|
+
}
|
|
198
|
+
if (chunk.done)
|
|
199
|
+
break;
|
|
200
|
+
text += decoder.decode(chunk.value, { stream: true });
|
|
201
|
+
}
|
|
202
|
+
text += decoder.decode();
|
|
203
|
+
return { text, timedOut: false };
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
return { text, timedOut: false, error };
|
|
207
|
+
}
|
|
208
|
+
finally {
|
|
209
|
+
if (timer !== undefined) {
|
|
210
|
+
clearTimeoutImpl(timer);
|
|
211
|
+
}
|
|
212
|
+
try {
|
|
213
|
+
reader.releaseLock();
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
/* ignore */
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
function streamFailureMessage(profileName, stdout, stderr) {
|
|
221
|
+
const failures = [];
|
|
222
|
+
if (stdout.error)
|
|
223
|
+
failures.push(`stdout read failed: ${stdout.error instanceof Error ? stdout.error.message : String(stdout.error)}`);
|
|
224
|
+
if (stderr.error)
|
|
225
|
+
failures.push(`stderr read failed: ${stderr.error instanceof Error ? stderr.error.message : String(stderr.error)}`);
|
|
226
|
+
if (stdout.timedOut)
|
|
227
|
+
failures.push("stdout drain timed out");
|
|
228
|
+
if (stderr.timedOut)
|
|
229
|
+
failures.push("stderr drain timed out");
|
|
230
|
+
if (failures.length === 0)
|
|
231
|
+
return undefined;
|
|
232
|
+
return `agent CLI "${profileName}" output capture failed: ${failures.join("; ")}`;
|
|
161
233
|
}
|
|
162
234
|
/**
|
|
163
235
|
* Spawn the agent CLI described by `profile` with `prompt` (forwarded as
|
|
@@ -179,17 +251,28 @@ async function readStream(stream, opts) {
|
|
|
179
251
|
*/
|
|
180
252
|
export async function runAgent(profile, prompt, options = {}) {
|
|
181
253
|
const stdioMode = options.stdio ?? profile.stdio;
|
|
182
|
-
// null = explicitly disabled (no kill timer). undefined =
|
|
183
|
-
const timeoutMs = options.timeoutMs !== undefined ? options.timeoutMs :
|
|
254
|
+
// null = explicitly disabled (no kill timer). undefined = runtime default.
|
|
255
|
+
const timeoutMs = options.timeoutMs !== undefined ? options.timeoutMs : DEFAULT_TIMEOUT_MS;
|
|
184
256
|
const parseOutput = options.parseOutput ?? profile.parseOutput;
|
|
185
257
|
const setTimeoutImpl = options.setTimeoutFn ?? setTimeout;
|
|
186
258
|
const clearTimeoutImpl = options.clearTimeoutFn ?? clearTimeout;
|
|
259
|
+
// Observability seam — ids/status only, best-effort (see RunAgentOptions.onEvent).
|
|
260
|
+
const emitSpawnEvent = (type, data) => {
|
|
261
|
+
if (!options.onEvent)
|
|
262
|
+
return;
|
|
263
|
+
try {
|
|
264
|
+
options.onEvent({ type, data });
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
// Observability must never break the dispatch.
|
|
268
|
+
}
|
|
269
|
+
};
|
|
187
270
|
// Build argv via the platform-specific builder when dispatch params are
|
|
188
271
|
// provided; fall back to the legacy positional-prompt form otherwise.
|
|
189
272
|
let builtArgv;
|
|
190
273
|
let builtEnv;
|
|
191
274
|
if (options.dispatch !== undefined) {
|
|
192
|
-
const builder = getCommandBuilder(profile.
|
|
275
|
+
const builder = getCommandBuilder(profile.platform ?? profile.name, options.builderRegistry);
|
|
193
276
|
const built = builder.build(profile, options.dispatch);
|
|
194
277
|
builtArgv = built.argv;
|
|
195
278
|
builtEnv = built.env;
|
|
@@ -225,9 +308,7 @@ export async function runAgent(profile, prompt, options = {}) {
|
|
|
225
308
|
stdout: stdioMode === "captured" ? "pipe" : "inherit",
|
|
226
309
|
stderr: stdioMode === "captured" ? "pipe" : "inherit",
|
|
227
310
|
env,
|
|
228
|
-
|
|
229
|
-
// declared on AgentDispatchRequest but consumed by nothing — P0.5 fix).
|
|
230
|
-
...((options.cwd ?? options.dispatch?.cwd) ? { cwd: options.cwd ?? options.dispatch?.cwd } : {}),
|
|
311
|
+
...(options.cwd ? { cwd: options.cwd } : {}),
|
|
231
312
|
// Spawn in its own process group so killGroup(-pid, signal) reaches all
|
|
232
313
|
// descendants (e.g. the .opencode binary that opencode's node wrapper forks).
|
|
233
314
|
// Only applied in captured mode — interactive mode inherits the parent
|
|
@@ -247,6 +328,10 @@ export async function runAgent(profile, prompt, options = {}) {
|
|
|
247
328
|
error: err instanceof Error ? err.message : String(err),
|
|
248
329
|
};
|
|
249
330
|
}
|
|
331
|
+
emitSpawnEvent("spawn_start", {
|
|
332
|
+
profile: profile.name,
|
|
333
|
+
...(typeof proc.pid === "number" ? { pid: proc.pid } : {}),
|
|
334
|
+
});
|
|
250
335
|
// Hard timeout. We prefer SIGTERM, then SIGKILL if SIGTERM is ignored,
|
|
251
336
|
// but the subprocess only exposes a single .kill() — one signal is enough
|
|
252
337
|
// for the structured-failure contract.
|
|
@@ -267,11 +352,13 @@ export async function runAgent(profile, prompt, options = {}) {
|
|
|
267
352
|
timedOut = true;
|
|
268
353
|
killGroup(proc, "SIGTERM");
|
|
269
354
|
// Follow up with SIGKILL after 5 s in case the process ignores SIGTERM.
|
|
270
|
-
setTimeoutImpl(() => {
|
|
355
|
+
const sigkillTimer = setTimeoutImpl(() => {
|
|
271
356
|
if (!proc || proc.exitCode !== null)
|
|
272
357
|
return;
|
|
273
358
|
killGroup(proc, "SIGKILL");
|
|
274
359
|
}, 5000);
|
|
360
|
+
if (typeof sigkillTimer !== "number")
|
|
361
|
+
sigkillTimer.unref?.();
|
|
275
362
|
}, timeoutMs);
|
|
276
363
|
}
|
|
277
364
|
// Cooperative cancel: same SIGTERM→SIGKILL discipline as the timeout, but
|
|
@@ -308,11 +395,19 @@ export async function runAgent(profile, prompt, options = {}) {
|
|
|
308
395
|
// When there is no kill timer, allow up to 30 s for streams to drain.
|
|
309
396
|
const streamDrainTimeoutMs = timeoutMs !== null ? timeoutMs + 2_000 : 30_000;
|
|
310
397
|
const stdoutPromise = stdioMode === "captured"
|
|
311
|
-
? readStream(proc.stdout ?? null, {
|
|
312
|
-
|
|
398
|
+
? readStream(proc.stdout ?? null, {
|
|
399
|
+
timeoutMs: streamDrainTimeoutMs,
|
|
400
|
+
setTimeoutFn: setTimeoutImpl,
|
|
401
|
+
clearTimeoutFn: clearTimeoutImpl,
|
|
402
|
+
})
|
|
403
|
+
: Promise.resolve({ text: "", timedOut: false });
|
|
313
404
|
const stderrPromise = stdioMode === "captured"
|
|
314
|
-
? readStream(proc.stderr ?? null, {
|
|
315
|
-
|
|
405
|
+
? readStream(proc.stderr ?? null, {
|
|
406
|
+
timeoutMs: streamDrainTimeoutMs,
|
|
407
|
+
setTimeoutFn: setTimeoutImpl,
|
|
408
|
+
clearTimeoutFn: clearTimeoutImpl,
|
|
409
|
+
})
|
|
410
|
+
: Promise.resolve({ text: "", timedOut: false });
|
|
316
411
|
// Optional stdin payload (captured mode only).
|
|
317
412
|
//
|
|
318
413
|
// BUG-H1: race the stdin write/close against `proc.exited` and the
|
|
@@ -352,6 +447,12 @@ export async function runAgent(profile, prompt, options = {}) {
|
|
|
352
447
|
// will not block indefinitely.
|
|
353
448
|
await Promise.allSettled([stdoutPromise, stderrPromise]);
|
|
354
449
|
const durationMs = Date.now() - start;
|
|
450
|
+
emitSpawnEvent("spawn_exit", {
|
|
451
|
+
profile: profile.name,
|
|
452
|
+
...(typeof proc.pid === "number" ? { pid: proc.pid } : {}),
|
|
453
|
+
exitCode: null,
|
|
454
|
+
status: "spawn_failed",
|
|
455
|
+
});
|
|
355
456
|
return {
|
|
356
457
|
ok: false,
|
|
357
458
|
exitCode: null,
|
|
@@ -364,7 +465,15 @@ export async function runAgent(profile, prompt, options = {}) {
|
|
|
364
465
|
}
|
|
365
466
|
clearTimeoutImpl(timer);
|
|
366
467
|
abortSignal?.removeEventListener("abort", onAbort);
|
|
367
|
-
|
|
468
|
+
emitSpawnEvent("spawn_exit", {
|
|
469
|
+
profile: profile.name,
|
|
470
|
+
...(typeof proc.pid === "number" ? { pid: proc.pid } : {}),
|
|
471
|
+
exitCode,
|
|
472
|
+
status: aborted ? "aborted" : timedOut ? "timeout" : exitCode !== 0 ? "non_zero_exit" : "ok",
|
|
473
|
+
});
|
|
474
|
+
const [stdoutRead, stderrRead] = await Promise.all([stdoutPromise, stderrPromise]);
|
|
475
|
+
const stdout = stdoutRead.text;
|
|
476
|
+
const stderr = stderrRead.text;
|
|
368
477
|
const durationMs = Date.now() - start;
|
|
369
478
|
if (aborted) {
|
|
370
479
|
return {
|
|
@@ -388,6 +497,18 @@ export async function runAgent(profile, prompt, options = {}) {
|
|
|
388
497
|
error: `agent CLI "${profile.name}" timed out after ${timeoutMs ?? 0}ms`,
|
|
389
498
|
};
|
|
390
499
|
}
|
|
500
|
+
const captureFailure = streamFailureMessage(profile.name, stdoutRead, stderrRead);
|
|
501
|
+
if (captureFailure) {
|
|
502
|
+
return {
|
|
503
|
+
ok: false,
|
|
504
|
+
exitCode,
|
|
505
|
+
stdout,
|
|
506
|
+
stderr,
|
|
507
|
+
durationMs,
|
|
508
|
+
reason: "spawn_failed",
|
|
509
|
+
error: captureFailure,
|
|
510
|
+
};
|
|
511
|
+
}
|
|
391
512
|
if (exitCode !== 0) {
|
|
392
513
|
return {
|
|
393
514
|
ok: false,
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
/**
|
|
5
|
+
* Aider CLI agent command builder (P2, plan §"The adapter contract" step 2 /
|
|
6
|
+
* §"Capability matrix").
|
|
7
|
+
*
|
|
8
|
+
* Translates a platform-agnostic {@link AgentDispatchRequest} into the exact
|
|
9
|
+
* headless argv the `aider` CLI expects. Per the capability matrix the
|
|
10
|
+
* headless invocation is:
|
|
11
|
+
*
|
|
12
|
+
* aider -m "<p>" --yes-always
|
|
13
|
+
*
|
|
14
|
+
* with `--model <m>` for model selection, NO structured-output mode (Aider is
|
|
15
|
+
* the "none" tier — prompt-injected schema + embedded-JSON extraction), and
|
|
16
|
+
* NO session flag (resume is chat-history files, not an id).
|
|
17
|
+
*
|
|
18
|
+
* Platform-specific mapping decisions (all localized here, per the adapter
|
|
19
|
+
* contract):
|
|
20
|
+
*
|
|
21
|
+
* - **prompt** — `--message` is Aider's one-shot non-interactive mode (the
|
|
22
|
+
* matrix's `-m`; the long form is used for self-documenting argv). Unlike
|
|
23
|
+
* claude/codex/pi there is NO positional prompt and NO `--` end-of-options
|
|
24
|
+
* separator to hide behind: the prompt is a *flag value*. The glued
|
|
25
|
+
* `--message=<payload>` form is therefore used so a payload that begins
|
|
26
|
+
* with `-`/`--` binds to the flag lexically and can never be parsed as a
|
|
27
|
+
* separate option by Aider's argument parser.
|
|
28
|
+
* - **--yes-always** — always emitted: auto-confirms every interactive
|
|
29
|
+
* prompt (create file? run command? …), which is what makes a captured,
|
|
30
|
+
* unattended run possible. This is the matrix's documented headless shape.
|
|
31
|
+
* - **--no-pretty** — always emitted: disables colored/pretty terminal
|
|
32
|
+
* rendering so captured stdout is clean text for `./result-extractor.ts`
|
|
33
|
+
* (the same "dispatch is the captured path" reasoning as the Claude
|
|
34
|
+
* builder's unconditional `--print` and Codex's unconditional `--json`).
|
|
35
|
+
* - **systemPrompt** — Aider has no system-prompt flag (its nearest concept
|
|
36
|
+
* is conventions files via `--read`, which take a path, not text). Folded
|
|
37
|
+
* into the message payload — system text first, blank line, then the task —
|
|
38
|
+
* mirroring the codex builder's treatment.
|
|
39
|
+
* - **schema** — the matrix places Aider in the "via prompt+validate" tier
|
|
40
|
+
* with *no* structured output mode at all (plan §"Structured-output
|
|
41
|
+
* normalization", tier "none"): there is no schema flag and no JSON output
|
|
42
|
+
* flag, so the JSON Schema is injected into the message payload using the
|
|
43
|
+
* exact directive wording of the engine's prompt assembly
|
|
44
|
+
* (`step-work.ts` `buildUnitPrompt`) and the pi builder, so all
|
|
45
|
+
* dispatch paths speak one dialect. Downstream, embedded-JSON extraction +
|
|
46
|
+
* the engine's shared retry-until-valid loop supply the validation Aider
|
|
47
|
+
* lacks. No temp schema file is written — that is Codex's native-schema
|
|
48
|
+
* mechanism (`--output-schema`), which Aider does not have.
|
|
49
|
+
* - **tools** — deliberately unconsumed. Aider has no per-tool allowlist
|
|
50
|
+
* flag; tool-ish behaviour is governed by its own switches (`--yes-always`,
|
|
51
|
+
* git integration, shell-command confirmation). A restrictive policy is
|
|
52
|
+
* therefore dropped rather than approximated — never silently widened.
|
|
53
|
+
* - **resume/session** — NOT expressible: Aider persists context in
|
|
54
|
+
* chat-history files (`.aider.chat.history.md`), not session ids, so there
|
|
55
|
+
* is no flag-shaped `HarnessResumeSupport` to export and the extractor
|
|
56
|
+
* never yields a `sessionId`. akm's `workflow_run_units` remains the
|
|
57
|
+
* durable source of truth; resume works even against a harness with no
|
|
58
|
+
* session model (plan §"Session, MCP, and identity across harnesses" —
|
|
59
|
+
* Aider is the plan's named example).
|
|
60
|
+
* - **effort** — stays unconsumed (reserved; the shared request contract's
|
|
61
|
+
* "no builder consumes it yet" note stays true).
|
|
62
|
+
*
|
|
63
|
+
* NOT registered anywhere: `builders.ts` / `harnesses/index.ts` wiring is a
|
|
64
|
+
* follow-up integration task (as is the registry entry declaring
|
|
65
|
+
* `structuredOutput: "none"` and no `resume`). Exported standalone so that
|
|
66
|
+
* task only adds a registry entry.
|
|
67
|
+
*/
|
|
68
|
+
import { assertNotFlag, resolveDispatchModel, } from "../../agent/builder-shared.js";
|
|
69
|
+
/** Canonical harness/platform id used for model-alias resolution. */
|
|
70
|
+
export const AIDER_PLATFORM = "aider";
|
|
71
|
+
/**
|
|
72
|
+
* Assemble the `--message` payload: optional system text, the task prompt,
|
|
73
|
+
* and — when a schema is requested — the same schema directive the workflow
|
|
74
|
+
* engine's prompt assembly uses (Aider has no native structured output, so
|
|
75
|
+
* the prompt is the only channel; plan §"Structured-output normalization",
|
|
76
|
+
* tier "none").
|
|
77
|
+
*/
|
|
78
|
+
function buildMessagePayload(req) {
|
|
79
|
+
const sections = [];
|
|
80
|
+
if (req.systemPrompt)
|
|
81
|
+
sections.push(req.systemPrompt);
|
|
82
|
+
sections.push(req.prompt);
|
|
83
|
+
if (req.schema) {
|
|
84
|
+
sections.push(`Respond with ONLY a JSON value matching this JSON Schema (no prose, no code fences):\n${JSON.stringify(req.schema)}`);
|
|
85
|
+
}
|
|
86
|
+
return sections.join("\n\n");
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Aider builder.
|
|
90
|
+
* Command shape:
|
|
91
|
+
* aider [--model <m>] --yes-always --no-pretty --message=<[system\n\n]prompt[\n\nschema directive]>
|
|
92
|
+
*/
|
|
93
|
+
export const aiderBuilder = {
|
|
94
|
+
platform: AIDER_PLATFORM,
|
|
95
|
+
build(profile, req) {
|
|
96
|
+
assertNotFlag(req.systemPrompt, "systemPrompt");
|
|
97
|
+
assertNotFlag(req.model, "model");
|
|
98
|
+
const args = [...profile.args];
|
|
99
|
+
if (req.model) {
|
|
100
|
+
const resolved = resolveDispatchModel(req, profile, AIDER_PLATFORM);
|
|
101
|
+
args.push("--model", resolved);
|
|
102
|
+
}
|
|
103
|
+
// Headless essentials (matrix shape): auto-confirm everything, and keep
|
|
104
|
+
// captured stdout free of pretty/ANSI rendering for the extractor.
|
|
105
|
+
args.push("--yes-always");
|
|
106
|
+
args.push("--no-pretty");
|
|
107
|
+
// Glued form: the payload is a flag VALUE (no positional prompt exists),
|
|
108
|
+
// so `=` binding keeps a dash-leading payload from parsing as an option.
|
|
109
|
+
args.push(`--message=${buildMessagePayload(req)}`);
|
|
110
|
+
return { argv: [profile.bin, ...args] };
|
|
111
|
+
},
|
|
112
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
/**
|
|
5
|
+
* Aider CLI harness (P2 integration, plan §"The adapter contract").
|
|
6
|
+
*
|
|
7
|
+
* Per-harness barrel gathering the Aider integration surfaces:
|
|
8
|
+
* - agent command builder → ./agent-builder.ts (aiderBuilder)
|
|
9
|
+
* - result extractor → ./result-extractor.ts (aiderResultExtractor)
|
|
10
|
+
*
|
|
11
|
+
* It also defines {@link AiderHarness}, the {@link AkmHarness} descriptor that
|
|
12
|
+
* `HARNESS_REGISTRY` registers. Dispatch-only: no native session-log reader or
|
|
13
|
+
* config importer yet.
|
|
14
|
+
*/
|
|
15
|
+
import { BaseHarness } from "../types.js";
|
|
16
|
+
import { aiderBuilder } from "./agent-builder.js";
|
|
17
|
+
import { aiderResultExtractor } from "./result-extractor.js";
|
|
18
|
+
export { AIDER_PLATFORM, aiderBuilder } from "./agent-builder.js";
|
|
19
|
+
export { aiderResultExtractor } from "./result-extractor.js";
|
|
20
|
+
function caps(c) {
|
|
21
|
+
return {
|
|
22
|
+
sessionLogs: false,
|
|
23
|
+
agentDispatch: false,
|
|
24
|
+
detection: false,
|
|
25
|
+
configImport: false,
|
|
26
|
+
runtimeIdentity: false,
|
|
27
|
+
...c,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Aider.
|
|
32
|
+
*
|
|
33
|
+
* Canonical id is `'aider'`; no alias or distinct runtime identity.
|
|
34
|
+
*/
|
|
35
|
+
export class AiderHarness extends BaseHarness {
|
|
36
|
+
id = "aider";
|
|
37
|
+
displayName = "Aider";
|
|
38
|
+
aliases = [];
|
|
39
|
+
agentBuilder = aiderBuilder;
|
|
40
|
+
resultExtractor = aiderResultExtractor;
|
|
41
|
+
// ── Workflow-engine descriptor (plan §"Capability matrix", P2) ────────────
|
|
42
|
+
// akm spawns the `aider` CLI locally per unit ⇒ local-runner.
|
|
43
|
+
pattern = "local-runner";
|
|
44
|
+
// No structured-output mode at all (the matrix's "none — parse output"):
|
|
45
|
+
// akm injects the schema into the prompt and extracts embedded JSON.
|
|
46
|
+
structuredOutput = "none";
|
|
47
|
+
// No `resume`: Aider persists context in chat-history files
|
|
48
|
+
// (`.aider.chat.history.md`), not session ids — the plan's named example of
|
|
49
|
+
// a harness with no session model. akm's `workflow_run_units` remains the
|
|
50
|
+
// durable resume source of truth.
|
|
51
|
+
// No `identityEnv`: the matrix lists Aider's identity markers as uncertain,
|
|
52
|
+
// and Aider stamps no session var onto child processes.
|
|
53
|
+
capabilities = caps({
|
|
54
|
+
agentDispatch: true,
|
|
55
|
+
detection: true,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
/**
|
|
5
|
+
* ANSI escape sequences (CSI color/cursor codes). Defensive only: the builder
|
|
6
|
+
* always passes `--no-pretty`, but a user profile may override that.
|
|
7
|
+
* Constructed from a char code so no literal control character appears in
|
|
8
|
+
* source (lint-friendly, same bytes).
|
|
9
|
+
*/
|
|
10
|
+
const ANSI_ESCAPE_RE = new RegExp(`${String.fromCharCode(27)}\\[[0-9;?]*[ -/]*[@-~]`, "g");
|
|
11
|
+
/**
|
|
12
|
+
* Aider's own announcement lines — banner, model/repo configuration echo,
|
|
13
|
+
* chat-history notices, edit/commit/usage reports. All patterns are anchored
|
|
14
|
+
* at line start and match documented phrasings only; anything else is treated
|
|
15
|
+
* as reply content.
|
|
16
|
+
*/
|
|
17
|
+
const NOISE_LINE_PATTERNS = [
|
|
18
|
+
/^aider v\d/, // version banner
|
|
19
|
+
/^(Main model|Weak model|Editor model|Model):\s/, // model announcements
|
|
20
|
+
/^Git repo:\s/, // repo detection
|
|
21
|
+
/^Repo-map:\s/, // repo-map status
|
|
22
|
+
/^Added .+ to the chat\.?$/, // file add notices
|
|
23
|
+
/^Restored previous conversation history\.?$/, // chat-history reload notice
|
|
24
|
+
/^Use \/help\b/, // onboarding hint line
|
|
25
|
+
/^Tokens: .+/, // usage/cost footer
|
|
26
|
+
/^Cost: .+/, // standalone cost footer
|
|
27
|
+
/^Applied edit to .+/, // edit application notices
|
|
28
|
+
/^Commit [0-9a-f]{6,}\b/, // auto-commit notices
|
|
29
|
+
/^You can use \/undo\b/, // undo hint after auto-commit
|
|
30
|
+
/^[─━-]{4,}$/, // ── / ━━ / ---- separator rules
|
|
31
|
+
];
|
|
32
|
+
function isNoiseLine(line) {
|
|
33
|
+
return NOISE_LINE_PATTERNS.some((pattern) => pattern.test(line));
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Normalize a raw aider run result into `{ text, sessionId? }`.
|
|
37
|
+
* See the module doc for the stdout shape handled.
|
|
38
|
+
*/
|
|
39
|
+
export const aiderResultExtractor = (result) => {
|
|
40
|
+
const raw = result.stdout.replace(ANSI_ESCAPE_RE, "").trim();
|
|
41
|
+
const kept = raw
|
|
42
|
+
.split("\n")
|
|
43
|
+
.filter((line) => !isNoiseLine(line.trim()))
|
|
44
|
+
.join("\n")
|
|
45
|
+
.trim();
|
|
46
|
+
// Never return empty text when stdout had content: an all-noise filter
|
|
47
|
+
// outcome means the patterns over-matched for this capture, so fall back to
|
|
48
|
+
// the full trimmed stdout (downstream parsing still gets material).
|
|
49
|
+
const text = kept.length > 0 ? kept : raw;
|
|
50
|
+
// No session model (chat-history files, not ids): only a spawn-layer id
|
|
51
|
+
// passes through; output never supplies one.
|
|
52
|
+
return result.sessionId === undefined ? { text } : { text, sessionId: result.sessionId };
|
|
53
|
+
};
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
/**
|
|
5
|
+
* Amazon Q Developer CLI agent command builder (P2, plan §"The adapter
|
|
6
|
+
* contract" step 2 / §"Capability matrix").
|
|
7
|
+
*
|
|
8
|
+
* Translates a platform-agnostic {@link AgentDispatchRequest} into the exact
|
|
9
|
+
* headless argv the `q` CLI expects. Per the capability matrix the headless
|
|
10
|
+
* invocation is:
|
|
11
|
+
*
|
|
12
|
+
* q chat --no-interactive --trust-all-tools "<prompt>"
|
|
13
|
+
*
|
|
14
|
+
* with `--model <m>` for model selection and `--resume` for resume. Resume is
|
|
15
|
+
* registry-side (`AkmHarness.resume`, {@link AMAZONQ_RESUME_FLAG}) — and NOTE:
|
|
16
|
+
* unlike every other harness's resume, Q's `--resume` is a bare flag that
|
|
17
|
+
* replays the previous conversation *of the working directory*; it takes no
|
|
18
|
+
* session id. There is nothing to thread from `workflow_run_units` — akm's own
|
|
19
|
+
* unit rows remain the durable resume source of truth regardless (plan
|
|
20
|
+
* §"Session, MCP, and identity across harnesses").
|
|
21
|
+
*
|
|
22
|
+
* Platform-specific mapping decisions (all localized here, per the adapter
|
|
23
|
+
* contract):
|
|
24
|
+
*
|
|
25
|
+
* - **subcommand** — headless dispatch is the `chat` subcommand. The builder
|
|
26
|
+
* prepends `chat` itself (mirroring the codex builder's `exec` handling); a
|
|
27
|
+
* user profile that already pins `chat` as its first arg is not doubled.
|
|
28
|
+
* - **prompt** — the trailing positional `[INPUT]` argument of `q chat`,
|
|
29
|
+
* preceded by the `--` end-of-options separator (mirroring the
|
|
30
|
+
* claude/codex/pi builders) so a prompt whose text begins with `-`/`--` can
|
|
31
|
+
* never be parsed as flags. `--no-interactive` makes Q print the response
|
|
32
|
+
* and exit instead of opening the REPL.
|
|
33
|
+
* - **systemPrompt** — `q chat` has no system-prompt flag (persona/context
|
|
34
|
+
* comes from Q's own agent config files), so the system prompt is folded
|
|
35
|
+
* into the positional payload ahead of the task prompt, separated by a
|
|
36
|
+
* blank line. `assertNotFlag` still guards it.
|
|
37
|
+
* - **schema** — the matrix places Q in the NO-structured-output tier
|
|
38
|
+
* ("via prompt+validate": *(none documented)* — there is no `--json` or
|
|
39
|
+
* `--output-format` to ask for). The JSON Schema is therefore passed
|
|
40
|
+
* through the prompt: a directive matching the engine's wording
|
|
41
|
+
* (`step-work.ts` `buildUnitPrompt`) is appended to the payload.
|
|
42
|
+
* Stdout stays plain text; `./result-extractor.ts` strips terminal framing
|
|
43
|
+
* and the engine's shared embedded-JSON parse + retry-until-valid loop does
|
|
44
|
+
* the rest. No schema temp file is written — that seam is codex-only
|
|
45
|
+
* (`--output-schema`); inventing a flag here would produce a silently
|
|
46
|
+
* broken command.
|
|
47
|
+
* - **tools** — a string/array tool policy maps to Q's documented
|
|
48
|
+
* `--trust-tools=<t1,t2>` allowlist flag (equals-joined, per `q chat
|
|
49
|
+
* --help`). With no policy at all, headless runs need autonomy, so
|
|
50
|
+
* `--trust-all-tools` is emitted per the matrix. A *structured* policy
|
|
51
|
+
* object is NOT expressible as Q flags; it is deliberately dropped without
|
|
52
|
+
* falling back to `--trust-all-tools` (never silently widen a restriction)
|
|
53
|
+
* — Q then refuses untrusted tool actions in non-interactive mode, which is
|
|
54
|
+
* the conservative failure mode.
|
|
55
|
+
* - **effort** — stays unconsumed (reserved; the shared request contract's
|
|
56
|
+
* "no builder consumes it yet" note stays true).
|
|
57
|
+
*
|
|
58
|
+
* NOT registered anywhere: `builders.ts` / `harnesses/index.ts` wiring is a
|
|
59
|
+
* follow-up integration task (as is the registry-side capability entry —
|
|
60
|
+
* pattern `local-runner`, structuredOutput `none`). Exported standalone so
|
|
61
|
+
* that task only adds a registry entry.
|
|
62
|
+
*/
|
|
63
|
+
import { assertNotFlag, resolveDispatchModel, } from "../../agent/builder-shared.js";
|
|
64
|
+
/** Canonical harness/platform id used for model-alias resolution. */
|
|
65
|
+
export const AMAZONQ_PLATFORM = "amazonq";
|
|
66
|
+
/**
|
|
67
|
+
* Resume flag per the capability matrix (`--resume`). Exported for the
|
|
68
|
+
* integration task's `AkmHarness.resume` registry entry. Bare flag: Q resumes
|
|
69
|
+
* the previous conversation of the current working directory and takes NO
|
|
70
|
+
* session id value — do not append one after it.
|
|
71
|
+
*/
|
|
72
|
+
export const AMAZONQ_RESUME_FLAG = "--resume";
|
|
73
|
+
/**
|
|
74
|
+
* Split a tool policy into individual tool names for `--trust-tools`.
|
|
75
|
+
* Strings are comma-separated lists; arrays are taken as-is. Structured
|
|
76
|
+
* policy objects return `undefined` (not expressible as Q flags — see
|
|
77
|
+
* module doc).
|
|
78
|
+
*/
|
|
79
|
+
function toolPolicyEntries(tools) {
|
|
80
|
+
if (typeof tools === "string") {
|
|
81
|
+
return tools
|
|
82
|
+
.split(",")
|
|
83
|
+
.map((t) => t.trim())
|
|
84
|
+
.filter(Boolean);
|
|
85
|
+
}
|
|
86
|
+
if (Array.isArray(tools)) {
|
|
87
|
+
return tools.map((t) => t.trim()).filter(Boolean);
|
|
88
|
+
}
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Assemble the positional prompt payload: optional system prompt, the task
|
|
93
|
+
* prompt, and — when a schema is requested — the same schema directive the
|
|
94
|
+
* workflow engine's prompt assembly uses, so both dispatch paths speak one
|
|
95
|
+
* dialect.
|
|
96
|
+
*/
|
|
97
|
+
function buildPromptPayload(req) {
|
|
98
|
+
const sections = [];
|
|
99
|
+
if (req.systemPrompt)
|
|
100
|
+
sections.push(req.systemPrompt);
|
|
101
|
+
sections.push(req.prompt);
|
|
102
|
+
if (req.schema) {
|
|
103
|
+
sections.push(`Respond with ONLY a JSON value matching this JSON Schema (no prose, no code fences):\n${JSON.stringify(req.schema)}`);
|
|
104
|
+
}
|
|
105
|
+
return sections.join("\n\n");
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Amazon Q Developer CLI builder.
|
|
109
|
+
* Command shape:
|
|
110
|
+
* q chat --no-interactive (--trust-all-tools | --trust-tools=<t1,t2>)
|
|
111
|
+
* [--model <m>] -- "<systemPrompt?\n\nprompt\n\nschema directive?>"
|
|
112
|
+
*/
|
|
113
|
+
export const amazonqBuilder = {
|
|
114
|
+
platform: AMAZONQ_PLATFORM,
|
|
115
|
+
build(profile, req) {
|
|
116
|
+
assertNotFlag(req.systemPrompt, "systemPrompt");
|
|
117
|
+
assertNotFlag(req.model, "model");
|
|
118
|
+
// Built-in q profiles would ship `args: []`; headless dispatch is the
|
|
119
|
+
// `chat` subcommand. Don't double it when a user profile already pins it.
|
|
120
|
+
const extra = profile.args[0] === "chat" ? profile.args.slice(1) : [...profile.args];
|
|
121
|
+
const args = ["chat", ...extra];
|
|
122
|
+
// Print the response and exit — required for captured dispatch.
|
|
123
|
+
args.push("--no-interactive");
|
|
124
|
+
if (req.tools) {
|
|
125
|
+
// Structured policy objects (entries === undefined) emit NO trust
|
|
126
|
+
// flags: dropping a restriction must never widen to --trust-all-tools.
|
|
127
|
+
const entries = toolPolicyEntries(req.tools);
|
|
128
|
+
if (entries !== undefined) {
|
|
129
|
+
for (const tool of entries) {
|
|
130
|
+
assertNotFlag(tool, "tools entry");
|
|
131
|
+
}
|
|
132
|
+
// Q's documented allowlist form is equals-joined and comma-separated
|
|
133
|
+
// (`--trust-tools=fs_read,fs_write`); an empty list trusts no tools.
|
|
134
|
+
args.push(`--trust-tools=${entries.join(",")}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
// Headless default per the capability matrix: units must run without
|
|
139
|
+
// interactive tool-approval prompts.
|
|
140
|
+
args.push("--trust-all-tools");
|
|
141
|
+
}
|
|
142
|
+
if (req.model) {
|
|
143
|
+
const resolved = resolveDispatchModel(req, profile, AMAZONQ_PLATFORM);
|
|
144
|
+
args.push("--model", resolved);
|
|
145
|
+
}
|
|
146
|
+
// No system-prompt / schema flags exist on `q chat` — both travel in the
|
|
147
|
+
// positional payload, after the end-of-options separator.
|
|
148
|
+
args.push("--");
|
|
149
|
+
args.push(buildPromptPayload(req));
|
|
150
|
+
return { argv: [profile.bin, ...args] };
|
|
151
|
+
},
|
|
152
|
+
};
|