akm-cli 0.9.25-alpha.1 → 0.9.25-alpha.3
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 +243 -280
- package/dist/assets/prompts/reflect-feedback-framing.md +1 -1
- package/dist/assets/prompts/reflect-llm-framed-contract.md +2 -9
- package/dist/assets/prompts/reflect-llm-schema-contract.md +1 -3
- package/dist/assets/prompts/reflect-output-repair.md +1 -1
- package/dist/cli.js +1 -1
- package/dist/commands/improve/consolidate/pair-pass.js +1 -0
- package/dist/commands/improve/consolidate.js +7 -2
- package/dist/commands/improve/execution.js +2 -3
- package/dist/commands/improve/extract-cli.js +3 -2
- package/dist/commands/improve/extract.js +2 -1
- package/dist/commands/improve/improve-cli.js +33 -1
- package/dist/commands/improve/loop-stages.js +3 -0
- package/dist/commands/improve/reflect-noise.js +125 -0
- package/dist/commands/improve/reflect.js +150 -333
- package/dist/commands/improve/retrieval-gate.js +7 -2
- package/dist/commands/improve/session-asset.js +6 -0
- package/dist/commands/improve/stage.js +31 -39
- package/dist/commands/proposal/drain.js +4 -7
- package/dist/commands/proposal/propose.js +2 -11
- package/dist/commands/proposal/validators/proposal-quality-validators.js +11 -5
- package/dist/commands/proposal/validators/proposal-validators.js +4 -5
- package/dist/commands/read/search-cli.js +0 -38
- package/dist/core/asset/asset-serialize.js +1 -1
- package/dist/core/config/schema/engines.js +15 -33
- package/dist/core/config/schema/improve-processes.js +16 -0
- package/dist/core/content-safety.js +0 -24
- package/dist/core/redaction.js +4 -0
- package/dist/core/spawn-env.js +25 -0
- package/dist/core/structured.js +1 -1
- package/dist/execution/source.js +8 -12
- package/dist/integrations/agent/config.js +1 -3
- package/dist/integrations/agent/engine-resolution.js +0 -3
- package/dist/integrations/agent/execution.js +14 -13
- package/dist/integrations/agent/index.js +1 -1
- package/dist/integrations/agent/model-map.js +15 -16
- package/dist/integrations/agent/profiles.js +2 -2
- package/dist/integrations/agent/prompts.js +51 -127
- package/dist/integrations/agent/request-lowering.js +9 -7
- package/dist/integrations/agent/runner-dispatch.js +25 -31
- package/dist/integrations/harnesses/aider/agent-builder.js +1 -2
- package/dist/integrations/harnesses/amazonq/agent-builder.js +1 -2
- package/dist/integrations/harnesses/claude/agent-builder.js +4 -16
- package/dist/integrations/harnesses/codex/agent-builder.js +1 -2
- package/dist/integrations/harnesses/codex/index.js +6 -11
- package/dist/integrations/harnesses/codex/session-log.js +211 -0
- package/dist/integrations/harnesses/ids.js +10 -16
- package/dist/integrations/harnesses/opencode/agent-builder.js +14 -24
- package/dist/integrations/harnesses/opencode/model-config.js +15 -62
- package/dist/integrations/harnesses/opencode/model-work-agent.js +71 -36
- package/dist/integrations/harnesses/opencode-sdk/harness.js +2 -6
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +32 -90
- package/dist/integrations/harnesses/openhands/agent-builder.js +1 -2
- package/dist/integrations/harnesses/pi/agent-builder.js +1 -2
- package/dist/integrations/harnesses/types.js +3 -3
- package/dist/llm/feature-gate.js +2 -5
- package/dist/llm/index-passes.js +2 -2
- package/dist/llm/structured-call.js +5 -5
- package/dist/output/shapes/passthrough.js +1 -0
- package/dist/scripts/akm-migrate-node.js +381 -239
- package/dist/scripts/akm-migrate.js +381 -239
- package/dist/workflows/exec/unit-dispatch.js +4 -13
- package/docs/reference/cli.md +29 -16
- package/docs/reference/configuration.md +79 -85
- package/docs/reference/data-and-telemetry.md +2 -3
- package/docs/reference/workflow-schema.md +6 -9
- package/package.json +1 -1
- package/schemas/akm-config.json +108 -36
|
@@ -32,6 +32,10 @@ const GRADE_SCHEMA = {
|
|
|
32
32
|
additionalProperties: false,
|
|
33
33
|
properties: { grade: { type: "integer", minimum: 0, maximum: 3 }, reason: { type: "string" } },
|
|
34
34
|
};
|
|
35
|
+
function parseGrade(raw) {
|
|
36
|
+
const grade = parseEmbeddedJsonResponse(raw)?.grade;
|
|
37
|
+
return typeof grade === "number" && Number.isInteger(grade) && grade >= 0 && grade <= 3 ? grade : undefined;
|
|
38
|
+
}
|
|
35
39
|
/** Up to five distinct task queries, in the given order, whitespace collapsed. */
|
|
36
40
|
export function usableRetrievalQueries(raw) {
|
|
37
41
|
const out = [];
|
|
@@ -100,10 +104,11 @@ export async function runRetrievalRegressionGate(args) {
|
|
|
100
104
|
...(args.signal ? { signal: args.signal } : {}),
|
|
101
105
|
...(args.chat ? { chat: args.chat } : {}),
|
|
102
106
|
},
|
|
107
|
+
parse: parseGrade,
|
|
103
108
|
...(args.onNotices ? { onNotices: args.onNotices } : {}),
|
|
104
109
|
});
|
|
105
|
-
const grade = outcome.ok ?
|
|
106
|
-
if (
|
|
110
|
+
const grade = outcome.ok ? parseGrade(outcome.raw) : undefined;
|
|
111
|
+
if (grade === undefined) {
|
|
107
112
|
return {
|
|
108
113
|
pass: false,
|
|
109
114
|
queries: args.queries.length,
|
|
@@ -99,6 +99,12 @@ export function buildSessionAccessInstructions(harness, logPath, sessionId) {
|
|
|
99
99
|
`Parse messages: jq -r 'select(.type=="message") | .message.content[]? | select(.type=="text") | .text' ${logPath}`,
|
|
100
100
|
].join("\n");
|
|
101
101
|
}
|
|
102
|
+
if (harness === "codex") {
|
|
103
|
+
return [
|
|
104
|
+
`Read with: cat ${logPath}`,
|
|
105
|
+
`Parse messages: jq -r 'select(.type=="response_item" and .payload.type=="message") | .payload.content[]? | .text // empty' ${logPath}`,
|
|
106
|
+
].join("\n");
|
|
107
|
+
}
|
|
102
108
|
if (harness === "opencode") {
|
|
103
109
|
return [
|
|
104
110
|
`Open the SQLite database at ${JSON.stringify(logPath)} in read-only mode.`,
|
|
@@ -3,9 +3,8 @@
|
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
import { getImproveProcessConfig } from "../../core/config/config.js";
|
|
5
5
|
import { ConfigError } from "../../core/errors.js";
|
|
6
|
-
import { validateJsonSchemaSubset } from "../../core/json-schema.js";
|
|
7
6
|
import { parseEmbeddedJsonResponse } from "../../core/parse.js";
|
|
8
|
-
import {
|
|
7
|
+
import { defaultFeedback } from "../../core/structured.js";
|
|
9
8
|
import { warn } from "../../core/warn.js";
|
|
10
9
|
import { runnerLlmConnection } from "../../integrations/agent/runner.js";
|
|
11
10
|
import { callStructured, dispatchFailureReason, dispatchFailureResult, } from "../../llm/structured-call.js";
|
|
@@ -45,43 +44,23 @@ export function stageRunner(frozen, config, profile, processName, onNotices) {
|
|
|
45
44
|
onNotices?.(resolved.notices);
|
|
46
45
|
return resolved?.runner;
|
|
47
46
|
}
|
|
48
|
-
const TRANSPORT_FAILED = Symbol("stage-transport-failed");
|
|
49
47
|
/**
|
|
50
48
|
* One model call. Provider trouble (transport error, timeout, abort, a
|
|
51
49
|
* disabled feature) comes back as `{ ok: false }`; only a configuration
|
|
52
|
-
* failure throws. A reply to a call with `request.responseSchema` that
|
|
53
|
-
*
|
|
54
|
-
*
|
|
50
|
+
* failure throws. A reply to a call with `request.responseSchema` that the
|
|
51
|
+
* stage's own `parse` rejects gets one corrective retry; the last reply comes
|
|
52
|
+
* back either way, and the caller parses it again.
|
|
55
53
|
*/
|
|
56
54
|
export async function callStage(call) {
|
|
57
|
-
const
|
|
58
|
-
if (!
|
|
59
|
-
return
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
dispatch: async (feedback) => {
|
|
65
|
-
const outcome = await callStageOnce(feedback ? { ...call, prompt: `${call.prompt}\n\n${feedback}` } : call);
|
|
66
|
-
if (!outcome.ok) {
|
|
67
|
-
failure = outcome;
|
|
68
|
-
throw TRANSPORT_FAILED;
|
|
69
|
-
}
|
|
70
|
-
reply = outcome;
|
|
71
|
-
return outcome.raw;
|
|
72
|
-
},
|
|
73
|
-
validate: (candidate) => {
|
|
74
|
-
const errors = validateJsonSchemaSubset(candidate, schema);
|
|
75
|
-
return errors.length === 0 ? { ok: true, value: candidate } : { ok: false, errors };
|
|
76
|
-
},
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
catch (err) {
|
|
80
|
-
if (err !== TRANSPORT_FAILED)
|
|
81
|
-
throw err;
|
|
82
|
-
}
|
|
55
|
+
const reply = await callStageOnce(call);
|
|
56
|
+
if (!reply.ok || !call.request?.responseSchema)
|
|
57
|
+
return reply;
|
|
58
|
+
if ((call.parse ?? parseEmbeddedJsonResponse)(reply.raw) !== undefined)
|
|
59
|
+
return reply;
|
|
60
|
+
const feedback = defaultFeedback({ reason: "parse_error", errors: [] });
|
|
61
|
+
const retry = await callStageOnce({ ...call, prompt: `${call.prompt}\n\n${feedback}` });
|
|
83
62
|
// A retry that fails in transport keeps the first reply, which the caller may still accept.
|
|
84
|
-
return
|
|
63
|
+
return retry.ok ? retry : reply;
|
|
85
64
|
}
|
|
86
65
|
/** Timeout and abort come from the dispatch's own reason, whatever the runner's kind. */
|
|
87
66
|
function failureReason(err) {
|
|
@@ -283,15 +262,24 @@ function buildChangedRegion(sourceContent, candidateContent) {
|
|
|
283
262
|
const added = candidate.slice(prefix, candidate.length - suffix).join("\n");
|
|
284
263
|
return boundedDocument(`Removed or replaced:\n${removed || "(none)"}\n\nAdded or replacement:\n${added || "(none)"}`);
|
|
285
264
|
}
|
|
286
|
-
/**
|
|
287
|
-
|
|
265
|
+
/**
|
|
266
|
+
* What the judge may do with tools when it runs on an agent engine: verify a
|
|
267
|
+
* fact the revision adds or alters, and nothing else. The plain judge's prompt
|
|
268
|
+
* is unchanged (its rubric is tuned and measured without this paragraph).
|
|
269
|
+
*/
|
|
270
|
+
function reflectJudgeToolRules(ref) {
|
|
271
|
+
const asset = ref ? `The asset is \`${ref}\`: read it with akm_show, ` : "Read an asset with akm_show ";
|
|
272
|
+
return `Tools: ${asset}or an asset the changed region names, only to verify a fact the revision adds or alters; the text above already shows every change. Do not search, do not read anything else, and do not use a tool to judge structure or wording. One or two reads at most. Before scoring, check three lists: (1) every statement the revision adds: find each in the asset, or as a fact the feedback states about the subject, and score QUALITY 1-2 if any is in neither; a statement is found only when the asset or the feedback says it, in any words: a new step, cause, consequence or detail that merely seems to follow is not found; feedback says what to fix and is not content, so an added statement about how the asset was used, found or verified is unsupported; (2) every fact, caveat and field of the source: find each in the revision, and score PRESERVATION 1-3 if any is missing; (3) every point the feedback makes: find the text it is about changed in the revision, and score NEED 2-3 if any is not; a note that restates the feedback does not address it. A read that finds nothing wrong raises no score above what these lists support. Then reply with the JSON.`;
|
|
273
|
+
}
|
|
274
|
+
/** Judge prompt for an in-place revision. `tools` is set when the judge runs on an agent engine. */
|
|
275
|
+
export function buildReflectJudgePrompt(candidateContent, sourceContent, feedback, tools) {
|
|
288
276
|
return [
|
|
289
277
|
"You are evaluating a proposed revision to an existing akm asset.",
|
|
290
278
|
"",
|
|
291
279
|
"Score this revision on each criterion from 1 (poor) to 5 (excellent):",
|
|
292
|
-
"1. NEED: Does the revision fix a concrete problem in the source? Concrete problems are: something the feedback reports as wrong or missing; a factual error;
|
|
293
|
-
"2. PRESERVATION: Does it keep every concrete fact, identifier, command, path, number and
|
|
294
|
-
"3. QUALITY: Is it coherent and accurate, with no claims, steps or details that the source or the feedback does not support?",
|
|
280
|
+
"1. NEED: Does the revision fix a concrete problem in the source? Concrete problems are: something the feedback reports as wrong or missing; a factual error; broken, garbled, truncated or missing text; and a missing or broken title, description or when_to_use field. Compare the source's description with the revision's: a description with a sentence split in its middle by a stray period or line-wrap artifact (as in 'calls. asset writes'), an unbalanced or escaped quote, or a truncated ending is broken, and repairing it is a concrete problem fixed even when the rest of the revision only adds stamps or reformats; adding or removing a trailing period, or rewording a readable description, repairs nothing. A missing title, description or when_to_use is a concrete problem whether or not the feedback mentions it: empty or positive feedback does not mean the source was complete, and the frontmatter is complete only when it has all three. A when_to_use is missing when the frontmatter has none, even if the body has a 'when to use' section; moving or copying that text into the field is the fix. A missing type field or a provenance stamp such as generated or verified is not a concrete problem. Score 4-5 when the revision fixes one, even a small one, whatever else it also reformats. Score 1-2 when the source was already complete and correct and the revision only rewords, restates, reformats, adds a type field or a stamp, or adds headings, an introduction or a table of contents.",
|
|
281
|
+
"2. PRESERVATION: Does it keep every concrete fact, identifier, command, path, number, example, caveat and frontmatter field from the source, without truncation? Check the changed region line by line. Score 1-3 when any of them is dropped or weakened, even when the revision also fixes something. Trimming narrative that states no fact, or removing what the feedback asks to remove or rescope, is not a drop.",
|
|
282
|
+
"3. QUALITY: Is it coherent and accurate, with no claims, steps or details that the source or the feedback does not support? Check each added or changed statement against the source and the feedback: a statement that follows from either counts as supported, and frontmatter stamps such as type, generated, verified or quality, whatever their values, and a restatement of existing content are not claims. Score 1-2 when the revision adds a claim neither supports, turns a draft or proposal into a decision, strengthens a statement beyond the source (a preference into a requirement, a possibility into a fact, a pending fix into a done one), adds a hedge such as 'may be outdated', or a placeholder such as 'TODO' or 'verify'; a fix elsewhere in the revision does not raise this score.",
|
|
295
283
|
"",
|
|
296
284
|
"Feedback:",
|
|
297
285
|
"```",
|
|
@@ -313,6 +301,7 @@ export function buildReflectJudgePrompt(candidateContent, sourceContent, feedbac
|
|
|
313
301
|
buildChangedRegion(sourceContent, candidateContent),
|
|
314
302
|
"```",
|
|
315
303
|
"",
|
|
304
|
+
...(tools ? [reflectJudgeToolRules(tools.ref), ""] : []),
|
|
316
305
|
'Return ONLY valid JSON, no prose: {"scores": {"need": <1-5 integer>, "preservation": <1-5 integer>, "quality": <1-5 integer>}, "reason": "<one sentence>"}',
|
|
317
306
|
].join("\n");
|
|
318
307
|
}
|
|
@@ -421,6 +410,7 @@ async function runQualityJudge(feature, config, prompt, keys, chat, options) {
|
|
|
421
410
|
...(options.signal ? { signal: options.signal } : {}),
|
|
422
411
|
...(chat ? { chat } : {}),
|
|
423
412
|
},
|
|
413
|
+
parse: (raw) => parseJudgeResponse(raw, keys),
|
|
424
414
|
...(options.onNotices ? { onNotices: options.onNotices } : {}),
|
|
425
415
|
});
|
|
426
416
|
if (!outcome.ok) {
|
|
@@ -462,6 +452,8 @@ export function runLessonQualityJudge(config, lessonContent, sourceContent, chat
|
|
|
462
452
|
}
|
|
463
453
|
/** Judge an in-place reflect revision without new-lesson novelty criteria. */
|
|
464
454
|
export function runReflectQualityJudge(config, candidateContent, sourceContent, feedback, chat, options = {}) {
|
|
465
|
-
|
|
455
|
+
// A judge on an agent engine gets the tool rules; the runner is the frozen one or none.
|
|
456
|
+
const tools = options.llmRunner && options.llmRunner.kind !== "llm" ? { ref: options.ref } : undefined;
|
|
457
|
+
const prompt = buildReflectJudgePrompt(candidateContent, sourceContent, feedback, tools);
|
|
466
458
|
return runQualityJudge("proposal_quality_gate", config, prompt, REFLECT_JUDGE_CRITERIA, chat, options);
|
|
467
459
|
}
|
|
@@ -25,8 +25,7 @@ import { ConfigError } from "../../core/errors.js";
|
|
|
25
25
|
import { appendEvent } from "../../core/events.js";
|
|
26
26
|
import { escapeJsonStringControls, stripCodeFences, stripThinkBlocks } from "../../core/parse.js";
|
|
27
27
|
import { info, warn } from "../../core/warn.js";
|
|
28
|
-
import {
|
|
29
|
-
import { DEFAULT_MODEL_WORK_TIMEOUT_MS } from "../../integrations/agent/config.js";
|
|
28
|
+
import { DEFAULT_LLM_TIMEOUT_MS } from "../../integrations/agent/config.js";
|
|
30
29
|
import { buildExecution, resolveExecution } from "../../integrations/agent/execution.js";
|
|
31
30
|
import { assertRunnerCredentials, runExecution, } from "../../integrations/agent/runner-dispatch.js";
|
|
32
31
|
import { errMessage, noticeSet } from "../improve/stage.js";
|
|
@@ -177,10 +176,8 @@ async function dispatchJudgment(runner, prompt, seams) {
|
|
|
177
176
|
const prepared = resolveExecution({
|
|
178
177
|
content: prompt,
|
|
179
178
|
runner,
|
|
180
|
-
current: {
|
|
181
|
-
|
|
182
|
-
tools: MODEL_WORK_TOOLS,
|
|
183
|
-
},
|
|
179
|
+
current: Object.hasOwn(runner, "timeoutMs") ? {} : { timeout: DEFAULT_LLM_TIMEOUT_MS },
|
|
180
|
+
modelWork: true,
|
|
184
181
|
});
|
|
185
182
|
const lowered = buildExecution(prepared.request, prepared.runner);
|
|
186
183
|
notices = lowered.notices;
|
|
@@ -345,7 +342,7 @@ export async function drainProposals(opts, promoteFn = akmProposalAccept, reject
|
|
|
345
342
|
const prepared = resolveExecution({
|
|
346
343
|
content: "Validate the selected proposal judgment runner before mutation.",
|
|
347
344
|
runner: opts.judgment,
|
|
348
|
-
|
|
345
|
+
modelWork: true,
|
|
349
346
|
});
|
|
350
347
|
assertRunnerCredentials(buildExecution(prepared.request, prepared.runner).runner);
|
|
351
348
|
}
|
|
@@ -27,8 +27,7 @@ import { deriveEntryProvenance } from "../../indexer/installations.js";
|
|
|
27
27
|
import { fallbackAnnouncement } from "../../integrations/agent/engine-fallback.js";
|
|
28
28
|
import { buildExecution, resolveExecution } from "../../integrations/agent/execution.js";
|
|
29
29
|
import { buildProposePrompt, PROPOSAL_JSON_SCHEMA, validateProposalPayload, } from "../../integrations/agent/prompts.js";
|
|
30
|
-
import { assertRunnerCredentials, collectDispatchSensitiveValues, runExecution, } from "../../integrations/agent/runner-dispatch.js";
|
|
31
|
-
import { getHarness } from "../../integrations/harnesses/index.js";
|
|
30
|
+
import { assertRunnerCredentials, collectDispatchSensitiveValues, runExecution, unwrapHarnessReply, } from "../../integrations/agent/runner-dispatch.js";
|
|
32
31
|
import { baseFailureFields, enoentHintMessage, isEnoentFailure } from "../agent/agent-support.js";
|
|
33
32
|
import { createProposal, resolveProposalQueueTarget, } from "./repository.js";
|
|
34
33
|
function failureEnvelope(result, type, name, engine, notices, fallbackReason = "non_zero_exit") {
|
|
@@ -45,14 +44,6 @@ function noticeFields(notices) {
|
|
|
45
44
|
return notices.length > 0 ? { notices } : {};
|
|
46
45
|
}
|
|
47
46
|
const DISPATCH_FAILED = Symbol("proposal-dispatch-failed");
|
|
48
|
-
/** A reply's text, unwrapped from its harness's framing (claude's `--output-format json` envelope). */
|
|
49
|
-
function replyText(execution, result) {
|
|
50
|
-
const runner = execution.runner;
|
|
51
|
-
if (runner.kind !== "agent")
|
|
52
|
-
return result.stdout;
|
|
53
|
-
const extractor = getHarness(runner.profile.platform ?? runner.profile.name)?.resultExtractor;
|
|
54
|
-
return extractor ? extractor(result).text : result.stdout;
|
|
55
|
-
}
|
|
56
47
|
/**
|
|
57
48
|
* Resolve, lower, and dispatch the already-rendered proposal prompt with the
|
|
58
49
|
* proposal's JSON Schema as its output schema, and capture the reply. A reply
|
|
@@ -92,7 +83,7 @@ async function dispatchProposalPrompt(prompt, config, options, onDispatchReady)
|
|
|
92
83
|
results.push(result);
|
|
93
84
|
if (!result.ok)
|
|
94
85
|
throw DISPATCH_FAILED;
|
|
95
|
-
return
|
|
86
|
+
return unwrapHarnessReply(execution.runner, result).text;
|
|
96
87
|
},
|
|
97
88
|
validate: validateProposalPayload,
|
|
98
89
|
});
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
* growing past min(max(250% of the source, 2500 bytes), 25000 bytes) suggests
|
|
10
10
|
* speculation. The absolute bounds keep small assets (a p25 source is ~780
|
|
11
11
|
* bytes) from tripping on one good paragraph, and 25000 (below p99) still
|
|
12
|
-
* catches runaway expansion. Sources under 200 bytes are too noisy to judge.
|
|
12
|
+
* catches runaway expansion. Sources under 200 bytes are too noisy to judge. A
|
|
13
|
+
* body that does not grow is never expansion, however long its source.
|
|
13
14
|
*/
|
|
14
15
|
import { parseFrontmatter } from "../../../core/asset/frontmatter.js";
|
|
15
16
|
import { parseRefInput } from "../../../core/asset/resolve-ref.js";
|
|
@@ -159,7 +160,8 @@ export function checkReflectSize(sourceBody, proposedBody) {
|
|
|
159
160
|
return { ok: false, code: "EXCESSIVE_SHRINKAGE", ratio };
|
|
160
161
|
}
|
|
161
162
|
const expandCeiling = Math.min(Math.max(REFLECT_EXPAND_RATIO_MAX * sourceLen, REFLECT_ABSOLUTE_CEILING_BYTES), REFLECT_ABSOLUTE_MAX_BYTES);
|
|
162
|
-
|
|
163
|
+
// A body that does not grow is never expansion, even where the cap sits below the source's own length.
|
|
164
|
+
if (proposedLen > sourceLen && proposedLen > expandCeiling)
|
|
163
165
|
return { ok: false, code: "EXCESSIVE_EXPANSION", ratio };
|
|
164
166
|
return { ok: true };
|
|
165
167
|
}
|
|
@@ -331,7 +333,11 @@ const redactedContentValidator = {
|
|
|
331
333
|
];
|
|
332
334
|
},
|
|
333
335
|
};
|
|
334
|
-
/**
|
|
336
|
+
/**
|
|
337
|
+
* The run-only "Avoid These Patterns" section (#963) in a reflect proposal: one
|
|
338
|
+
* made before reflect kept the body, or a body that already had it. Reflect
|
|
339
|
+
* keeps a body as it is, so a person accepting has no way to remove it.
|
|
340
|
+
*/
|
|
335
341
|
const reflectPromptScaffoldingValidator = {
|
|
336
342
|
name: "reflect-prompt-scaffolding",
|
|
337
343
|
appliesTo(proposal) {
|
|
@@ -359,15 +365,15 @@ function advisory(validator) {
|
|
|
359
365
|
validate: (proposal, ctx) => validator.validate(proposal, ctx).map((finding) => ({ ...finding, severity: "warn" })),
|
|
360
366
|
};
|
|
361
367
|
}
|
|
362
|
-
/** The quality validators `validateProposal` runs; the last
|
|
368
|
+
/** The quality validators `validateProposal` runs; the last two protect durable content and block. */
|
|
363
369
|
export const defaultProposalQualityValidators = [
|
|
364
370
|
...[
|
|
365
371
|
descriptionQualityValidator,
|
|
366
372
|
lessonContentQualityValidator,
|
|
367
373
|
sourceNotSupersededValidator,
|
|
368
374
|
reflectSizeGuardValidator,
|
|
375
|
+
reflectPromptScaffoldingValidator,
|
|
369
376
|
].map(advisory),
|
|
370
377
|
reflectTruncationMarkerValidator,
|
|
371
378
|
redactedContentValidator,
|
|
372
|
-
reflectPromptScaffoldingValidator,
|
|
373
379
|
];
|
|
@@ -131,14 +131,13 @@ export const defaultProposalValidators = [
|
|
|
131
131
|
* and was previously safe to run in full there because every quality
|
|
132
132
|
* validator was advisory (`advisory()` downgrades findings to `severity:
|
|
133
133
|
* "warn"`, which {@link runProposalValidators}'s `ok` never treats as
|
|
134
|
-
* failing). Blocking durable-content validators (the #952 truncation marker
|
|
135
|
-
* #962 redaction marker
|
|
134
|
+
* failing). Blocking durable-content validators (the #952 truncation marker
|
|
135
|
+
* and #962 redaction marker) deliberately
|
|
136
136
|
* remain outside this subset, so running the full
|
|
137
137
|
* {@link defaultProposalValidators} list at mint time could throw
|
|
138
138
|
* `invalid_canonical_structure` for any lesson/task/workflow reflect
|
|
139
|
-
* proposal whose body
|
|
140
|
-
*
|
|
141
|
-
* `reflect-truncation-leak`, per the #952 design. Quality validators (prose
|
|
139
|
+
* proposal whose body carries the truncation marker — instead of minting it
|
|
140
|
+
* for a person to review. Quality validators (prose
|
|
142
141
|
* shape, reflect size ratio, and durable-content guards) belong at
|
|
143
142
|
* `proposal accept` / drain-promotion time, which already calls
|
|
144
143
|
* {@link validateProposal} (the full list) via `preflightProposalPromotion`
|
|
@@ -81,20 +81,6 @@ export const searchCommand = defineJsonCommand({
|
|
|
81
81
|
description: "Include session assets (excluded from default search results via config.search.defaultExcludeTypes).",
|
|
82
82
|
default: false,
|
|
83
83
|
},
|
|
84
|
-
// Declared as the POSITIVE name with `default: true` so citty's native
|
|
85
|
-
// `--no-<name>` negation (it strips a leading `--no-` from ANY token and
|
|
86
|
-
// negates the remainder BEFORE consulting the declared-args table — see
|
|
87
|
-
// node_modules/citty/dist/index.mjs) does the work, the same pattern
|
|
88
|
-
// `sync --push/--no-push` uses. A flag DECLARED as `no-track-usage` could
|
|
89
|
-
// never be negated: `--no-track-usage` parses as "negate `track-usage`",
|
|
90
|
-
// a name nothing declared, leaving the real key at its default forever
|
|
91
|
-
// (F1/A1).
|
|
92
|
-
"track-usage": {
|
|
93
|
-
type: "boolean",
|
|
94
|
-
default: true,
|
|
95
|
-
description: "A successful search records usage-events telemetry. Default: on. Use --no-track-usage to run a " +
|
|
96
|
-
"search that records nothing.",
|
|
97
|
-
},
|
|
98
84
|
},
|
|
99
85
|
async run({ args }) {
|
|
100
86
|
rejectRetiredSourceFlag();
|
|
@@ -108,7 +94,6 @@ export const searchCommand = defineJsonCommand({
|
|
|
108
94
|
const filters = parseScopeFilterFlags(filterTokens, "--filter");
|
|
109
95
|
const includeProposed = args["include-proposed"] === true;
|
|
110
96
|
const belief = parseBeliefFilterMode(typeof args.belief === "string" ? args.belief : undefined);
|
|
111
|
-
const skipLogging = args["track-usage"] === false;
|
|
112
97
|
const includeSessions = args["include-sessions"];
|
|
113
98
|
const assets = args.assets === true;
|
|
114
99
|
const outputMode = getOutputMode();
|
|
@@ -121,7 +106,6 @@ export const searchCommand = defineJsonCommand({
|
|
|
121
106
|
includeProposed,
|
|
122
107
|
belief,
|
|
123
108
|
includeSessions,
|
|
124
|
-
skipLogging,
|
|
125
109
|
assets,
|
|
126
110
|
eventSource: resolveUsageEventSource(),
|
|
127
111
|
attributionProjection: outputMode.shape === "agent" ? "agent" : outputMode.detail,
|
|
@@ -154,15 +138,6 @@ export const curateCommand = defineJsonCommand({
|
|
|
154
138
|
"with a workflow asset's own `budget` field (a run-cost cap) — this is a context-size target for this " +
|
|
155
139
|
"one curate call.",
|
|
156
140
|
},
|
|
157
|
-
// Declared as the POSITIVE name with `default: true` — see the
|
|
158
|
-
// `track-usage` comment on `searchCommand` above for why a flag NAME
|
|
159
|
-
// must never start with `no-`.
|
|
160
|
-
"track-usage": {
|
|
161
|
-
type: "boolean",
|
|
162
|
-
default: true,
|
|
163
|
-
description: "A successful curate records usage-events telemetry for the curated items. Default: on. Use " +
|
|
164
|
-
"--no-track-usage to run a curate that records nothing.",
|
|
165
|
-
},
|
|
166
141
|
},
|
|
167
142
|
async run({ args }) {
|
|
168
143
|
rejectRetiredSourceFlag();
|
|
@@ -173,7 +148,6 @@ export const curateCommand = defineJsonCommand({
|
|
|
173
148
|
const limitParsed = parsePositiveIntFlag(args.limit ?? undefined);
|
|
174
149
|
const limit = limitParsed && limitParsed > 0 ? limitParsed : 4;
|
|
175
150
|
const source = parseSearchSource(args.from ?? "local");
|
|
176
|
-
const skipLogging = args["track-usage"] === false;
|
|
177
151
|
const outputMode = getOutputMode();
|
|
178
152
|
const packBudget = parsePositiveIntFlag(args.pack ?? undefined, "--pack");
|
|
179
153
|
const curated = await akmCurate({
|
|
@@ -181,7 +155,6 @@ export const curateCommand = defineJsonCommand({
|
|
|
181
155
|
type,
|
|
182
156
|
limit,
|
|
183
157
|
source,
|
|
184
|
-
skipLogging,
|
|
185
158
|
eventSource: resolveUsageEventSource(),
|
|
186
159
|
attributionProjection: outputMode.shape === "agent" ? "agent" : outputMode.detail,
|
|
187
160
|
});
|
|
@@ -285,15 +258,6 @@ export const showCommand = defineJsonCommand({
|
|
|
285
258
|
type: "string",
|
|
286
259
|
description: "Exact context budget in characters. Requires --context lead; mutually exclusive with --max-tokens.",
|
|
287
260
|
},
|
|
288
|
-
// Declared as the POSITIVE name with `default: true` — see the
|
|
289
|
-
// `track-usage` comment on `searchCommand` above for why a flag NAME
|
|
290
|
-
// must never start with `no-`.
|
|
291
|
-
"track-usage": {
|
|
292
|
-
type: "boolean",
|
|
293
|
-
default: true,
|
|
294
|
-
description: "A successful show records usage-events telemetry, including the search-selection linkage when this " +
|
|
295
|
-
"show follows a recent search. Default: on. Use --no-track-usage to run a show that records nothing.",
|
|
296
|
-
},
|
|
297
261
|
},
|
|
298
262
|
async run({ args }) {
|
|
299
263
|
// `[origin//]meta[:name]` targets the stash `.meta/` convention, which is
|
|
@@ -342,14 +306,12 @@ export const showCommand = defineJsonCommand({
|
|
|
342
306
|
if (maxContextChars !== undefined && !Number.isSafeInteger(maxContextChars)) {
|
|
343
307
|
throw new UsageError("Fragment context budget is too large.", "INVALID_FLAG_VALUE");
|
|
344
308
|
}
|
|
345
|
-
const skipLogging = args["track-usage"] === false;
|
|
346
309
|
const result = await akmShowUnified({
|
|
347
310
|
ref: args.ref,
|
|
348
311
|
detail: showDetail,
|
|
349
312
|
contextMode,
|
|
350
313
|
maxContextChars,
|
|
351
314
|
scope,
|
|
352
|
-
skipLogging,
|
|
353
315
|
eventSource: resolveUsageEventSource(),
|
|
354
316
|
});
|
|
355
317
|
output("show", result);
|
|
@@ -95,7 +95,7 @@ export function assembleAsset(frontmatter, body) {
|
|
|
95
95
|
* - exactly one `\n` terminates the file
|
|
96
96
|
*
|
|
97
97
|
* This helper is the single point of truth for the fence-and-body template.
|
|
98
|
-
*
|
|
98
|
+
* Two command surfaces (`distill`, `consolidate`) call it
|
|
99
99
|
* directly because their inputs are pre-validated LLM payloads where the
|
|
100
100
|
* full `yamlStringify` may emit shapes (`|`-block scalars, anchors) that
|
|
101
101
|
* the project's hand-rolled `parseFrontmatter` subset parser cannot read.
|
|
@@ -13,7 +13,7 @@ import { z } from "zod";
|
|
|
13
13
|
// `config-types`, which type-derives from this barrel via
|
|
14
14
|
// `typeof import("./config-schema")` — routing through config-types would mint
|
|
15
15
|
// a config-schema ↔ config-types type cycle that collapses inference.
|
|
16
|
-
import { HARNESS_AGENT_DISPATCH_IDS,
|
|
16
|
+
import { HARNESS_AGENT_DISPATCH_IDS, VALID_HARNESS_IDS } from "../../../integrations/harnesses/ids.js";
|
|
17
17
|
import { WORKFLOW_MAX_TIMEOUT_MS } from "../../../workflows/resource-limits.js";
|
|
18
18
|
import { chatCompletionsEndpoint, ExtraParamsSchema, engineName, nonEmptyString, positiveInt, symbolicOrWarnApiKey, } from "./primitives.js";
|
|
19
19
|
/**
|
|
@@ -105,20 +105,6 @@ const LlmEngineSchema = z
|
|
|
105
105
|
});
|
|
106
106
|
}
|
|
107
107
|
});
|
|
108
|
-
/**
|
|
109
|
-
* The inference fields an agent engine may set: the ones its platform
|
|
110
|
-
* translates (`harnesses/ids.ts`). An asset's or a caller's inference reaches
|
|
111
|
-
* every engine and reports what the engine does not translate as a lowering
|
|
112
|
-
* notice; an engine the operator configures for a platform names only what
|
|
113
|
-
* that platform carries, so the rest is an error here.
|
|
114
|
-
*/
|
|
115
|
-
const AGENT_INFERENCE_KEYS = [
|
|
116
|
-
"temperature",
|
|
117
|
-
"maxTokens",
|
|
118
|
-
"contextLength",
|
|
119
|
-
"enableThinking",
|
|
120
|
-
"reasoningEffort",
|
|
121
|
-
];
|
|
122
108
|
const AgentEngineSchema = z
|
|
123
109
|
.object({
|
|
124
110
|
kind: z.literal("agent"),
|
|
@@ -131,30 +117,26 @@ const AgentEngineSchema = z
|
|
|
131
117
|
model: nonEmptyString.optional(),
|
|
132
118
|
timeoutMs: timeoutMsField,
|
|
133
119
|
llmEngine: engineName.optional(),
|
|
134
|
-
temperature: z.number().finite().optional(),
|
|
135
|
-
maxTokens: positiveInt.optional(),
|
|
136
|
-
contextLength: positiveInt.optional(),
|
|
137
|
-
enableThinking: z.boolean().optional(),
|
|
138
|
-
reasoningEffort: nonEmptyString.optional(),
|
|
139
120
|
})
|
|
140
121
|
.passthrough()
|
|
141
122
|
.superRefine((value, ctx) => {
|
|
142
|
-
for (const key of [
|
|
123
|
+
for (const key of [
|
|
124
|
+
"provider",
|
|
125
|
+
"endpoint",
|
|
126
|
+
"apiKey",
|
|
127
|
+
"apiKeyFile",
|
|
128
|
+
"temperature",
|
|
129
|
+
"maxTokens",
|
|
130
|
+
"concurrency",
|
|
131
|
+
"extraParams",
|
|
132
|
+
"contextLength",
|
|
133
|
+
"enableThinking",
|
|
134
|
+
"reasoningEffort",
|
|
135
|
+
"modelAliases",
|
|
136
|
+
]) {
|
|
143
137
|
if (key in value)
|
|
144
138
|
ctx.addIssue({ code: z.ZodIssueCode.custom, path: [key], message: `${key} is not valid on an agent engine` });
|
|
145
139
|
}
|
|
146
|
-
const translated = harnessInferenceKeys(value.platform);
|
|
147
|
-
for (const key of AGENT_INFERENCE_KEYS) {
|
|
148
|
-
if (key in value && !translated.includes(key)) {
|
|
149
|
-
ctx.addIssue({
|
|
150
|
-
code: z.ZodIssueCode.custom,
|
|
151
|
-
path: [key],
|
|
152
|
-
message: `${key} is not valid on a ${value.platform} engine: ${translated.length > 0
|
|
153
|
-
? `the platform translates only ${translated.join(", ")}`
|
|
154
|
-
: "the platform translates no inference fields"}`,
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
140
|
if (value.platform !== "opencode-sdk" && value.llmEngine !== undefined) {
|
|
159
141
|
ctx.addIssue({
|
|
160
142
|
code: z.ZodIssueCode.custom,
|
|
@@ -103,6 +103,21 @@ const fidelityCheckField = z.object({ enabled: z.boolean().optional() }).passthr
|
|
|
103
103
|
* byte-identical behaviour). Reflect process only.
|
|
104
104
|
*/
|
|
105
105
|
const lowValueFilterField = z.object({ enabled: z.boolean().optional() }).passthrough().optional();
|
|
106
|
+
/**
|
|
107
|
+
* The wording lists of reflect's pre-judge defect filter (`findReflectDefect`).
|
|
108
|
+
* Each list is optional: one that is set replaces that rule's default list, and
|
|
109
|
+
* an empty one turns the rule off. Phrases (`placeholders`, `metaCommentary`)
|
|
110
|
+
* match as whole words in any case; `frontmatterKeys` are exact key names.
|
|
111
|
+
* Reflect process only.
|
|
112
|
+
*/
|
|
113
|
+
const defectFilterField = z
|
|
114
|
+
.object({
|
|
115
|
+
placeholders: z.array(nonEmptyString).optional(),
|
|
116
|
+
metaCommentary: z.array(nonEmptyString).optional(),
|
|
117
|
+
frontmatterKeys: z.array(nonEmptyString).optional(),
|
|
118
|
+
})
|
|
119
|
+
.passthrough()
|
|
120
|
+
.optional();
|
|
106
121
|
/**
|
|
107
122
|
* #626 — extract process: pre-LLM heuristic triage gate. When enabled, a
|
|
108
123
|
* deterministic scorer decides BEFORE the extraction LLM call whether a
|
|
@@ -159,6 +174,7 @@ const REFLECT_PROCESS_FIELDS = {
|
|
|
159
174
|
limit: processLimitField,
|
|
160
175
|
qualityGate: qualityGateField,
|
|
161
176
|
lowValueFilter: lowValueFilterField,
|
|
177
|
+
defectFilter: defectFilterField,
|
|
162
178
|
};
|
|
163
179
|
const DISTILL_PROCESS_FIELDS = {
|
|
164
180
|
allowedTypes: allowedTypesField,
|
|
@@ -6,36 +6,12 @@ export const REDACTED_CONTENT_MARKER = "[REDACTED]";
|
|
|
6
6
|
/** Reflect prompt section that contains run diagnostics, not proposed asset content. */
|
|
7
7
|
export const REFLECT_AVOID_PATTERNS_HEADING = "Avoid These Patterns";
|
|
8
8
|
const REFLECT_AVOID_PATTERNS_RE = /^##[ \t]+Avoid These Patterns[ \t]*$/i;
|
|
9
|
-
const SECTION_BOUNDARY_RE = /^#{1,2}(?:[ \t]+|$)/;
|
|
10
9
|
export function containsRedactedContent(content) {
|
|
11
10
|
return content.includes(REDACTED_CONTENT_MARKER);
|
|
12
11
|
}
|
|
13
12
|
export function containsReflectPromptScaffolding(content) {
|
|
14
13
|
return content.split(/\r?\n/).some((line) => REFLECT_AVOID_PATTERNS_RE.test(line));
|
|
15
14
|
}
|
|
16
|
-
/**
|
|
17
|
-
* Remove every echoed run-only "Avoid These Patterns" section while preserving
|
|
18
|
-
* the next peer/top-level section. Reflect alone calls this sanitizer; authored
|
|
19
|
-
* source assets are never rewritten by this helper.
|
|
20
|
-
*/
|
|
21
|
-
export function stripReflectPromptScaffolding(content) {
|
|
22
|
-
const newline = content.includes("\r\n") ? "\r\n" : "\n";
|
|
23
|
-
const lines = content.split(/\r?\n/);
|
|
24
|
-
const kept = [];
|
|
25
|
-
let stripped = false;
|
|
26
|
-
for (let index = 0; index < lines.length;) {
|
|
27
|
-
if (!REFLECT_AVOID_PATTERNS_RE.test(lines[index] ?? "")) {
|
|
28
|
-
kept.push(lines[index] ?? "");
|
|
29
|
-
index += 1;
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
32
|
-
stripped = true;
|
|
33
|
-
index += 1;
|
|
34
|
-
while (index < lines.length && !SECTION_BOUNDARY_RE.test(lines[index] ?? ""))
|
|
35
|
-
index += 1;
|
|
36
|
-
}
|
|
37
|
-
return { content: kept.join(newline).replace(/(?:\r?\n){3,}/g, `${newline}${newline}`), stripped };
|
|
38
|
-
}
|
|
39
15
|
/**
|
|
40
16
|
* Return a secret-free rejection reason when generated content is unsafe to
|
|
41
17
|
* persist. `redactedContent` is the same body after applying the dispatch
|
package/dist/core/redaction.js
CHANGED
|
@@ -19,6 +19,10 @@ const ENV_PASSTHROUGH_REDACTION_POLICY = {
|
|
|
19
19
|
OPENCODE_CONFIG: "path",
|
|
20
20
|
CLAUDE_CONFIG: "path",
|
|
21
21
|
CODEX_CONFIG: "path",
|
|
22
|
+
XDG_CONFIG_HOME: "path",
|
|
23
|
+
XDG_DATA_HOME: "path",
|
|
24
|
+
XDG_CACHE_HOME: "path",
|
|
25
|
+
XDG_STATE_HOME: "path",
|
|
22
26
|
AWS_PROFILE: "identifier",
|
|
23
27
|
AWS_REGION: "identifier",
|
|
24
28
|
LLM_MODEL: "identifier",
|
package/dist/core/spawn-env.js
CHANGED
|
@@ -45,6 +45,31 @@ export const COMMON_SPAWN_ENV_PASSTHROUGH = [
|
|
|
45
45
|
"TMPDIR",
|
|
46
46
|
"AKM_EVENT_SOURCE",
|
|
47
47
|
];
|
|
48
|
+
/**
|
|
49
|
+
* The XDG base-directory variables. opencode resolves its config, data, cache
|
|
50
|
+
* and state directories from them, so it must receive them: an akm that runs
|
|
51
|
+
* under a custom `XDG_CONFIG_HOME` otherwise spawns an opencode that reads
|
|
52
|
+
* `$HOME/.config/opencode` and misses the provider config its caller named.
|
|
53
|
+
*
|
|
54
|
+
* Deliberately NOT part of {@link COMMON_SPAWN_ENV_PASSTHROUGH}, which is every
|
|
55
|
+
* harness's baseline, the workflow exec unit's default allowlist (a documented
|
|
56
|
+
* list) and, through profile `envPassthrough`, frozen into workflow plans.
|
|
57
|
+
* codex, gemini and pi keep their own dotdirs under `$HOME`, and handing the
|
|
58
|
+
* names to a shell command would redirect the `git` and `gh` config it reads. A
|
|
59
|
+
* harness that reads them asks for them by name: the opencode profile's list,
|
|
60
|
+
* and the opencode-sdk server's allowlist (`opencodeSdkServerEnvironmentNames`).
|
|
61
|
+
*
|
|
62
|
+
* A name added to a profile's list changes the plans frozen after it (their
|
|
63
|
+
* bytes, so their `plan_hash`); a stored plan keeps the list it was frozen with
|
|
64
|
+
* and still resumes, because nothing gates on that hash. The SDK server's
|
|
65
|
+
* allowlist is not part of a plan, so it takes the names by code.
|
|
66
|
+
*/
|
|
67
|
+
export const XDG_BASE_DIR_ENV_PASSTHROUGH = [
|
|
68
|
+
"XDG_CONFIG_HOME",
|
|
69
|
+
"XDG_DATA_HOME",
|
|
70
|
+
"XDG_CACHE_HOME",
|
|
71
|
+
"XDG_STATE_HOME",
|
|
72
|
+
];
|
|
48
73
|
/**
|
|
49
74
|
* The names Windows itself requires of ANY child, whatever the caller's
|
|
50
75
|
* allowlist says. Applied at build time rather than added to
|
package/dist/core/structured.js
CHANGED
|
@@ -34,7 +34,7 @@ import { parseEmbeddedJsonResponse } from "./parse.js";
|
|
|
34
34
|
export function withSchemaInstruction(prompt, schema) {
|
|
35
35
|
return `${prompt}\n\nRespond with ONLY a JSON value matching this JSON Schema (no prose, no code fences):\n${JSON.stringify(schema)}`;
|
|
36
36
|
}
|
|
37
|
-
function defaultFeedback(failure) {
|
|
37
|
+
export function defaultFeedback(failure) {
|
|
38
38
|
if (failure.reason === "parse_error") {
|
|
39
39
|
return "Your previous response contained no parseable JSON. Respond with ONLY a JSON value that matches the requested schema — no prose, no code fences.";
|
|
40
40
|
}
|
package/dist/execution/source.js
CHANGED
|
@@ -6,19 +6,15 @@ export const EXECUTION_SOURCE_SCHEMA_VERSION = 1;
|
|
|
6
6
|
/** Current internal adapter identifiers are lowercase kebab-case registry keys. */
|
|
7
7
|
export const EXECUTION_ADAPTER_ID_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
8
8
|
/**
|
|
9
|
-
* The model-work tool policy: unattended model work (improve, the judges,
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
9
|
+
* The model-work tool policy: unattended model work (improve, the judges, index
|
|
10
|
+
* passes, remember) may read, edit only inside the dispatch's own scratch
|
|
11
|
+
* working directory, and run `akm search` and `akm show`; the stash stays
|
|
12
|
+
* read-only to it. A transport grants what it can confine and refuses the policy
|
|
13
|
+
* at build when it can confine nothing; an LLM has no tools. A request carries it
|
|
14
|
+
* as `authorization.policy.id`, set only when its caller asks (`modelWork`): no
|
|
15
|
+
* `tools` value names it, so an asset's own `tools:` cannot.
|
|
14
16
|
*/
|
|
15
|
-
export const
|
|
16
|
-
/** Whether a selection names the model-work tool policy. */
|
|
17
|
-
export function isModelWorkTools(tools) {
|
|
18
|
-
return (Array.isArray(tools) &&
|
|
19
|
-
tools.length === MODEL_WORK_TOOLS.length &&
|
|
20
|
-
tools.every((tool, index) => tool === MODEL_WORK_TOOLS[index]));
|
|
21
|
-
}
|
|
17
|
+
export const MODEL_WORK_POLICY_ID = "model-work";
|
|
22
18
|
function requireRecord(value, path) {
|
|
23
19
|
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
24
20
|
throw new TypeError(`${path} must be an object`);
|