@tea-agent/loop-agent 0.38.2 → 0.38.4
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/AGENTS.md +4 -1
- package/CHANGELOG.md +171 -96
- package/README.md +11 -5
- package/bin/loop-agent.js +7 -3
- package/dist/application/task-lifecycle/advance.js +4 -0
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +16 -5
- package/dist/cli/program.js +16 -2
- package/dist/cli/update/notifier.js +2 -2
- package/dist/cli.js +2 -1
- package/dist/commands/dag-rerun.js +55 -1
- package/dist/commands/init-model-catalog.js +464 -0
- package/dist/commands/init-upgrade.js +265 -97
- package/dist/commands/init.js +428 -27
- package/dist/commands/inspect-next.js +8 -0
- package/dist/commands/task-advance.js +31 -0
- package/dist/executors/dag-pi-executor.js +76 -10
- package/dist/executors/pi-event-serializer.js +42 -1
- package/dist/executors/pi-executor.js +51 -0
- package/dist/executors/pi-extension-resolver.js +233 -0
- package/dist/executors/pi-playwright-cli-tool.js +51 -12
- package/dist/executors/pi-sdk-executor.js +187 -59
- package/dist/executors/shell-executor.js +265 -15
- package/dist/executors/shell-write-guard.js +7 -0
- package/dist/shared/dag-prompt-override.js +28 -0
- package/dist/shared/operator/capabilities.js +32 -0
- package/dist/shared/playwright-cli-command-policy.js +15 -0
- package/dist/shared/update/console-notifier.js +100 -0
- package/dist/{cli → shared}/update/npm-client.js +41 -8
- package/dist/{cli → shared}/update/state.js +41 -13
- package/dist/task/contract/constants.js +1 -0
- package/dist/task/contract/diff.js +26 -0
- package/dist/task/contract/project.js +5 -0
- package/dist/task/contract/schema.js +6 -1
- package/dist/task/source-prepare/build-draft.js +15 -0
- package/dist/task/source-prepare/parse-intent.js +24 -4
- package/dist/worker/cli.js +31 -1
- package/dist/worker/console/chat/routes.js +1 -0
- package/dist/worker/console/dag-execution-receipt.js +33 -0
- package/dist/worker/console/operator-actions.js +116 -0
- package/dist/worker/console/operator-user-error.js +2 -2
- package/dist/worker/console/pi-readiness.js +4 -4
- package/dist/worker/console/routes.js +43 -2
- package/dist/worker/console/security.js +63 -9
- package/dist/worker/console/server.js +36 -5
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4bmo3iY.js → abnfDiagram-N423BO3Z-gauhD9kD.js} +1 -1
- package/dist/worker/console/static/assets/{arc-SAD7McOS.js → arc-pm3c2Rsl.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DAVLUdK1.js → architectureDiagram-T3A2C74G-D_JAg3wH.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-CPPdv4vj.js → blockDiagram-VBNYF7ZC-Dk7-ex52.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-D1GRGZax.js → c4Diagram-5PPSVZJV-Uval9oRI.js} +1 -1
- package/dist/worker/console/static/assets/channel-CVrnUdZl.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-D_Bq2ZqK.js → chunk-2GRJ4B5K-BSYjHs-v.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-CPLVtamK.js → chunk-2Q5K7J3B-KGUuBXI8.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DnaZWcL3.js → chunk-5RXB4S5H-JqAtxD9N.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BRTmFgnq.js → chunk-5VM5RSS4-D6EJOVQK.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BRlfYZoH.js → chunk-6Q2QTUOP-B1JriSjm.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CjNybD4u.js → chunk-GF5L2VYU-D62b96Jd.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-D3e-74fG.js → chunk-JWPE2WC7-BsNKEC_r.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-Br3ir6f2.js → chunk-KBJHAD2P-B56v6-ZP.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-1NgVuRbh.js → chunk-RYQCIY6F-D2KPnd8K.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-BTYuvuWN.js → chunk-XXDRQBXY-kzKSCaaL.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-aX91WTya.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-aX91WTya.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BC6Bgosf.js → cose-bilkent-JH36ORCC-C6N6KNJd.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-DV-HypfL.js → cynefin-VYW2F7L2-DiyymT2D.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-DhTaddO-.js → cynefinDiagram-MW4NZA55-BeKAXvf3.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BnEcOmiI.js → dagre-VZM6K2ZE-TkIdXfPF.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-xphGRVKp.js → diagram-7IWD3JNH-BFmSG3ks.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-CrdD1mRG.js → diagram-B4RE2ZJO-DqpJT2Fs.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-D4YdLsaF.js → diagram-LBJQPF4R-r5ATN43t.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-LCKyGZ5U.js → diagram-Q27KOJAE-DEcSCMuR.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BeiTe7t0.js → diagram-UB23O5K3-VOvYRLVc.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BO2cq8GS.js → ebnfDiagram-BXEA7PRR-D7f4Cb1u.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-CmWww3Nt.js → erDiagram-JOGREHBK-ziUf3U0G.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-C2kkUYg5.js → flowDiagram-UKHOOZJN-BoRdQ-1y.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DY_JZ2iQ.js → ganttDiagram-PKOTCBZU-B1-GncNm.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-BizhCSw9.js → gitGraphDiagram-DS77QQ5N-CTW-ph8W.js} +1 -1
- package/dist/worker/console/static/assets/index-C8rOIAJr.css +1 -0
- package/dist/worker/console/static/assets/{index-BXN89VPX.js → index-DEbyrEfW.js} +147 -68
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-Dlo2eK_d.js → infoDiagram-6WML65LV-BFA337tZ.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-3EIlIIuZ.js → ishikawaDiagram-WSZJBQD7-BtEujgRR.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DiMDe5Yh.js → journeyDiagram-NVQOT4AX-C8ZwD2Jp.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-sN553oIN.js → kanban-definition-27J2QSJJ-Ck-z8B11.js} +1 -1
- package/dist/worker/console/static/assets/{linear-D9Py31Ld.js → linear-CAyXjQjN.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-D7Occzk_.js → mermaid.core-CcWKn6tS.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DjDRboiR.js → mindmap-definition-FAOFIHXS-C2NuDO7E.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-Cgc4qlzp.js → pegDiagram-VL7TDLO6-BFub3PDz.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DxZj8pKd.js → pieDiagram-7S7Q4E2Y-BJx5THZ5.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CJ50MngR.js → quadrantDiagram-CIZ2JOQS-DhalkDWV.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BY-OD8Tr.js → railroadDiagram-AXF67PYL-CVcBM36o.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-CQFButfK.js → requirementDiagram-LRYGKXZP-D_vy4n0w.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-BB_QUK_b.js → sankeyDiagram-W5VNT64P-DGYdJL1D.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-CqhXZjyx.js → sequenceDiagram-SI44F4Z6-C1xJFg3k.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-2l1U4rgJ.js → sizeCapture-X5ZJPWSS-D_Fc43df.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-BX1FbrhJ.js → stateDiagram-OKZ733FA-BhGbIjbu.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DUt1lyFL.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DOSnT7hb.js → swimlanes-SLNWSIFB-DVkNcO-V.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BM6SGr7j.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-DVJQA7AN.js → timeline-definition-Z64GVDOM-BQU9ufV0.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-74uxl9gx.js → vennDiagram-T6HMQDX7-BCQhRmYB.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DsBxlcg_.js → wardleyDiagram-T6FBY63Y-DWerOyHO.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-snuW9Mjw.js → xychartDiagram-ELKLHX3M-DxLeebQU.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/mutation-gate.js +1 -1
- package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +328 -0
- package/dist/worker/loop-agent/loop-agent-client.js +7 -3
- package/dist/worker/observe/node-input.js +74 -3
- package/dist/worker/observe/node-process.js +377 -0
- package/dist/worker/observe/routes.js +36 -3
- package/dist/worker/observe/shell-handler-keys.js +34 -0
- package/dist/worker/observe/static/api.js +66 -0
- package/dist/worker/observe/static/constants.js +8 -1
- package/dist/worker/observe/static/dag-history-labels.js +4 -0
- package/dist/worker/observe/static/format.js +23 -0
- package/dist/worker/observe/static/markdown-render.js +20 -1
- package/dist/worker/observe/static/operator-chrome.js +4 -0
- package/dist/worker/observe/static/state.js +13 -2
- package/dist/worker/observe/static/styles.css +73 -0
- package/dist/worker/observe/static/views/dag-inspector.js +612 -8
- package/dist/worker/observe/static/views/dag.js +8 -1
- package/dist/worker/observe/static/views/failures.js +5 -2
- package/dist/worker/observe/static/views/session-timeline.js +78 -9
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +76 -6
- package/dist/workflows/dag/backend-test-scenario-partitions.js +73 -1
- package/dist/workflows/dag/contract-output-registry.js +15 -0
- package/dist/workflows/dag/failure-category.js +12 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +145 -40
- package/dist/workflows/dag/frontend-prewrite-gate.js +87 -135
- package/dist/workflows/dag/frontend-repair.js +14 -0
- package/dist/workflows/dag/frontend-risk.js +15 -2
- package/dist/workflows/dag/frontend-test-case-checklist.js +26 -2
- package/dist/workflows/dag/frontend-test-case-manifest.js +5 -0
- package/dist/workflows/dag/frontend-test-case-quality.js +5 -13
- package/dist/workflows/dag/frontend-test-environment-probe.js +227 -0
- package/dist/workflows/dag/frontend-test-markdown.js +61 -0
- package/dist/workflows/dag/frontend-test-result-contract.js +160 -28
- package/dist/workflows/dag/frontend-test-standard-scenarios.js +68 -0
- package/dist/workflows/dag/frontend-verification-trace.js +29 -4
- package/dist/workflows/dag/init-hybrid.js +196 -117
- package/dist/workflows/dag/node-execution.js +139 -10
- package/dist/workflows/dag/prompt.js +15 -1
- package/dist/workflows/dag/rerun-plan.js +134 -3
- package/dist/workflows/dag/rerun-run.js +58 -6
- package/dist/workflows/dag/retry-policy.js +91 -0
- package/dist/workflows/dag/structured-output-repair.js +712 -0
- package/dist/workflows/dag/types.js +32 -1
- package/dist/workflows/dag/validate.js +41 -1
- package/docs/init-surface.manifest.json +5 -0
- package/docs/operations/README.md +1 -1
- package/docs/operations/local-development-environment.md +1 -5
- package/docs/skills/vetted-skill-registry.md +14 -0
- package/docs/templates/README.md +1 -1
- package/docs/templates/agent-dag.schema.json +44 -0
- package/docs/templates/backend-test-dag.json +2 -2
- package/docs/templates/frontend-test-dag.json +8 -10
- package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
- package/docs/templates/init-managed-agents.md +13 -3
- package/harness.json +2 -2
- package/package.json +12 -4
- package/scripts/next-info.mjs +356 -0
- package/scripts/next-publish-gate.mjs +238 -0
- package/scripts/release-source-binding.mjs +251 -0
- package/skills/analyze-product-requirements/SKILL.md +8 -5
- package/skills/analyze-product-requirements/references/acceptance-criteria.md +1 -1
- package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +10 -6
- package/skills/analyze-product-requirements/references/example.md +50 -0
- package/skills/analyze-product-requirements/references/forward-test-cases.md +11 -7
- package/skills/analyze-product-requirements/references/kb-integration.md +5 -5
- package/skills/analyze-product-requirements/references/product-analysis-schema.md +8 -4
- package/skills/analyze-product-requirements/references/product-requirement-schema.md +2 -2
- package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +1 -1
- package/skills/analyze-product-requirements/scripts/test-validators.mjs +11 -1
- package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +20 -1
- package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +9 -0
- package/skills/codebase-scout/SKILL.md +1 -1
- package/skills/fe-test-ui-scout/SKILL.md +65 -0
- package/skills/fe-test-ui-scout/references/ledger-schema.md +62 -0
- package/skills/fe-test-ui-scout/references/recon-protocol.md +54 -0
- package/skills/improve-codebase-architecture/SKILL.md +81 -0
- package/skills/improve-codebase-architecture/deepening.md +37 -0
- package/skills/improve-codebase-architecture/html-report.md +123 -0
- package/skills/improve-codebase-architecture/interface-design.md +44 -0
- package/skills/improve-codebase-architecture/language.md +53 -0
- package/skills/loop-agent/references/command-reference.md +11 -4
- package/skills/playwright-cli/SKILL.md +22 -0
- package/skills/playwright-cli-case-generator/SKILL.md +20 -1
- package/dist/worker/console/static/assets/channel-R6oQIDkw.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BPXlmPWI.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BPXlmPWI.js +0 -1
- package/dist/worker/console/static/assets/index-CuH5CNFu.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DJKi0Qel.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-YYe7EEm5.js +0 -8
package/dist/cli/program.js
CHANGED
|
@@ -33,6 +33,7 @@ import { runHarvest } from "../commands/harvest.js";
|
|
|
33
33
|
import { runInit } from "../commands/init.js";
|
|
34
34
|
import { readInitRuntimeActivity } from "./update/runtime-activity.js";
|
|
35
35
|
import { runInspect } from "../commands/inspect.js";
|
|
36
|
+
import { runInspectNext } from "../commands/inspect-next.js";
|
|
36
37
|
import { runInstructions } from "../commands/instructions.js";
|
|
37
38
|
import { runKnowledge } from "../commands/knowledge.js";
|
|
38
39
|
import { runLoop } from "../commands/loop.js";
|
|
@@ -97,6 +98,9 @@ async function dispatchRegisteredCommand(commandObject, defaultRepoRoot, rawArgs
|
|
|
97
98
|
[...rest, ...rawArgs].some((token) => token === "--json")) ||
|
|
98
99
|
(command === "stats" &&
|
|
99
100
|
subcommand === "context" &&
|
|
101
|
+
[...rest, ...rawArgs].some((token) => token === "--json")) ||
|
|
102
|
+
(command === "inspect" &&
|
|
103
|
+
subcommand === "next" &&
|
|
100
104
|
[...rest, ...rawArgs].some((token) => token === "--json"));
|
|
101
105
|
const repoRoot = resolveRepoRootOnly(commandObject, defaultRepoRoot, {
|
|
102
106
|
quiet,
|
|
@@ -125,8 +129,15 @@ async function runTaskCommand(repoRoot, subcommand, rest) {
|
|
|
125
129
|
async function runCommanderAction(ctx, command, subcommand, rest) {
|
|
126
130
|
switch (command) {
|
|
127
131
|
case "inspect":
|
|
128
|
-
|
|
129
|
-
|
|
132
|
+
if (subcommand === "next") {
|
|
133
|
+
await runInspectNext(ctx.repoRoot, rest);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (subcommand === undefined || subcommand.startsWith("-")) {
|
|
137
|
+
await runInspect(ctx.adapter, ctx.repoRoot);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
throw new Error("usage: inspect | inspect next [--json]");
|
|
130
141
|
case "doctor":
|
|
131
142
|
await runDoctor(ctx.adapter, ctx.repoRoot, compactArgs([subcommand, ...rest]));
|
|
132
143
|
return;
|
|
@@ -595,6 +606,9 @@ export function buildLoopAgentProgram(options) {
|
|
|
595
606
|
.option("--no-merge", "skip existing files")
|
|
596
607
|
.option("--provider <provider>", "provider paired with a bare --model (otherwise use provider/model)")
|
|
597
608
|
.option("--model <model>", "qualified provider/model, or a bare model paired with --provider")
|
|
609
|
+
.option("--low-model <provider/model>", "credential-ready Pi model for LOW")
|
|
610
|
+
.option("--med-model <provider/model>", "credential-ready Pi model for MED")
|
|
611
|
+
.option("--high-model <provider/model>", "credential-ready Pi model for HIGH")
|
|
598
612
|
.option("--json", "print JSON")
|
|
599
613
|
.option("--markdown", "print Markdown")
|
|
600
614
|
.option("--bootstrap-surface", "write an inferred .harness/init-surface.json baseline")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createInterface } from "node:readline/promises";
|
|
2
2
|
import { evaluateLatestVersion, shouldCheckForUpdates } from "./policy.js";
|
|
3
|
-
import { acquireUpdateLock, readUpdateState, updateLockPath, updateStatePath, writeUpdateState, } from "
|
|
3
|
+
import { acquireUpdateLock, readUpdateState, updateLockPath, updateStatePath, writeUpdateState, } from "../../shared/update/state.js";
|
|
4
4
|
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
5
5
|
const FAILURE_COOLDOWN_MS = 10 * 60 * 1000;
|
|
6
6
|
export async function runSelfUpdateNotifier(options) {
|
|
@@ -69,7 +69,7 @@ export async function runSelfUpdateNotifier(options) {
|
|
|
69
69
|
}
|
|
70
70
|
writeStderr(`loop-agent 已更新到 ${decision.targetVersion};新版本将在下一次调用时生效。\n`);
|
|
71
71
|
await safeWriteState(statePath, {
|
|
72
|
-
|
|
72
|
+
...state,
|
|
73
73
|
lastCheckedAt: now().toISOString(),
|
|
74
74
|
latestVersion: decision.targetVersion,
|
|
75
75
|
});
|
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@ import os from "node:os";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { buildLoopAgentProgram } from "./cli/program.js";
|
|
7
|
-
import { createNpmUpdateClient } from "./
|
|
7
|
+
import { createNpmUpdateClient } from "./shared/update/npm-client.js";
|
|
8
8
|
import { runSelfUpdateNotifier } from "./cli/update/notifier.js";
|
|
9
9
|
import { resolveInitSurfaceRepoRoot, runInitSurfaceNotifier, } from "./cli/update/init-surface-notifier.js";
|
|
10
10
|
async function main() {
|
|
@@ -29,6 +29,7 @@ async function main() {
|
|
|
29
29
|
client: createNpmUpdateClient({
|
|
30
30
|
currentVersion,
|
|
31
31
|
packageRoot,
|
|
32
|
+
cwd: defaultRepoRoot,
|
|
32
33
|
env: process.env,
|
|
33
34
|
}),
|
|
34
35
|
});
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { isDagRerunPromptOverrideEnabled } from "../shared/dag-prompt-override.js";
|
|
1
3
|
import { executeDagRerun, planDagRerun, } from "../workflows/dag/rerun-run.js";
|
|
2
4
|
export function parseDagRerunArgs(args) {
|
|
3
5
|
if (args.length === 0) {
|
|
@@ -9,6 +11,8 @@ export function parseDagRerunArgs(args) {
|
|
|
9
11
|
let planHash;
|
|
10
12
|
let requestId;
|
|
11
13
|
let reason;
|
|
14
|
+
const promptOverrideSpecs = [];
|
|
15
|
+
const promptOverrideAppendSpecs = [];
|
|
12
16
|
for (let index = 0; index < args.length; index += 1) {
|
|
13
17
|
const arg = args[index];
|
|
14
18
|
if (arg === "--run-id")
|
|
@@ -33,6 +37,14 @@ export function parseDagRerunArgs(args) {
|
|
|
33
37
|
reason = args[++index];
|
|
34
38
|
else if (arg.startsWith("--reason="))
|
|
35
39
|
reason = arg.slice("--reason=".length);
|
|
40
|
+
else if (arg === "--prompt-override")
|
|
41
|
+
promptOverrideSpecs.push(args[++index] ?? "");
|
|
42
|
+
else if (arg.startsWith("--prompt-override="))
|
|
43
|
+
promptOverrideSpecs.push(arg.slice("--prompt-override=".length));
|
|
44
|
+
else if (arg === "--prompt-override-append")
|
|
45
|
+
promptOverrideAppendSpecs.push(args[++index] ?? "");
|
|
46
|
+
else if (arg.startsWith("--prompt-override-append="))
|
|
47
|
+
promptOverrideAppendSpecs.push(arg.slice("--prompt-override-append=".length));
|
|
36
48
|
else if (arg === "--json")
|
|
37
49
|
continue;
|
|
38
50
|
else if (arg.startsWith("-"))
|
|
@@ -50,7 +62,13 @@ export function parseDagRerunArgs(args) {
|
|
|
50
62
|
if (planHash || requestId || reason) {
|
|
51
63
|
throw new Error("dag rerun --plan cannot be combined with execute flags");
|
|
52
64
|
}
|
|
53
|
-
return {
|
|
65
|
+
return {
|
|
66
|
+
parentRunId,
|
|
67
|
+
selectedNodeId,
|
|
68
|
+
planOnly: true,
|
|
69
|
+
promptOverrideSpecs,
|
|
70
|
+
promptOverrideAppendSpecs,
|
|
71
|
+
};
|
|
54
72
|
}
|
|
55
73
|
if (!planHash?.trim()) {
|
|
56
74
|
throw new Error("dag rerun execute requires --plan-hash <hash>");
|
|
@@ -68,15 +86,50 @@ export function parseDagRerunArgs(args) {
|
|
|
68
86
|
planHash: planHash.trim(),
|
|
69
87
|
requestId: requestId.trim(),
|
|
70
88
|
reason: reason.trim(),
|
|
89
|
+
promptOverrideSpecs,
|
|
90
|
+
promptOverrideAppendSpecs,
|
|
71
91
|
};
|
|
72
92
|
}
|
|
93
|
+
async function loadPromptOverrides(specs, mode) {
|
|
94
|
+
const overrides = [];
|
|
95
|
+
for (const spec of specs) {
|
|
96
|
+
const separator = spec.indexOf("@");
|
|
97
|
+
if (separator <= 0 || separator >= spec.length - 1) {
|
|
98
|
+
throw new Error(`dag rerun --prompt-override${mode === "append" ? "-append" : ""} expects <nodeId>@<filePath>, got: ${spec}`);
|
|
99
|
+
}
|
|
100
|
+
const nodeId = spec.slice(0, separator);
|
|
101
|
+
const filePath = spec.slice(separator + 1);
|
|
102
|
+
let prompt;
|
|
103
|
+
try {
|
|
104
|
+
prompt = await readFile(filePath, "utf-8");
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
throw new Error(`cannot read prompt override file for node ${nodeId} (${filePath}): ${error instanceof Error ? error.message : String(error)}`);
|
|
108
|
+
}
|
|
109
|
+
overrides.push(mode === "append" ? { nodeId, prompt, mode } : { nodeId, prompt });
|
|
110
|
+
}
|
|
111
|
+
return overrides;
|
|
112
|
+
}
|
|
73
113
|
export async function runDagRerun(repoRoot, rawArgs) {
|
|
74
114
|
const parsed = parseDagRerunArgs(rawArgs);
|
|
115
|
+
let promptOverrides;
|
|
116
|
+
const hasOverrides = parsed.promptOverrideSpecs.length > 0 ||
|
|
117
|
+
parsed.promptOverrideAppendSpecs.length > 0;
|
|
118
|
+
if (hasOverrides) {
|
|
119
|
+
if (!isDagRerunPromptOverrideEnabled()) {
|
|
120
|
+
throw new Error("prompt override rerun is disabled; set HARNESS_DAG_RERUN_PROMPT_OVERRIDE=1 to enable this experimental capability");
|
|
121
|
+
}
|
|
122
|
+
promptOverrides = [
|
|
123
|
+
...(await loadPromptOverrides(parsed.promptOverrideSpecs, "replace")),
|
|
124
|
+
...(await loadPromptOverrides(parsed.promptOverrideAppendSpecs, "append")),
|
|
125
|
+
];
|
|
126
|
+
}
|
|
75
127
|
if (parsed.planOnly) {
|
|
76
128
|
const plan = await planDagRerun({
|
|
77
129
|
cwd: repoRoot,
|
|
78
130
|
parentRunId: parsed.parentRunId,
|
|
79
131
|
selectedNodeId: parsed.selectedNodeId,
|
|
132
|
+
promptOverrides,
|
|
80
133
|
});
|
|
81
134
|
const result = {
|
|
82
135
|
ok: plan.eligible,
|
|
@@ -95,6 +148,7 @@ export async function runDagRerun(repoRoot, rawArgs) {
|
|
|
95
148
|
planHash: parsed.planHash,
|
|
96
149
|
requestId: parsed.requestId,
|
|
97
150
|
reason: parsed.reason,
|
|
151
|
+
promptOverrides,
|
|
98
152
|
});
|
|
99
153
|
console.log(JSON.stringify(result, null, 2));
|
|
100
154
|
if (!result.ok) {
|
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
const execFileAsync = promisify(execFile);
|
|
6
|
+
function isRecord(value) {
|
|
7
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8
|
+
}
|
|
9
|
+
function stringField(value) {
|
|
10
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
11
|
+
}
|
|
12
|
+
function numberField(value) {
|
|
13
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
14
|
+
}
|
|
15
|
+
function booleanField(value) {
|
|
16
|
+
return typeof value === "boolean" ? value : undefined;
|
|
17
|
+
}
|
|
18
|
+
function compactRecord(value) {
|
|
19
|
+
return Object.values(value).some((entry) => entry !== undefined) ? value : undefined;
|
|
20
|
+
}
|
|
21
|
+
function sanitizeInputKinds(value) {
|
|
22
|
+
if (Array.isArray(value)) {
|
|
23
|
+
const entries = value.filter((entry) => typeof entry === "string");
|
|
24
|
+
return entries.length > 0 ? [...new Set(entries)].sort() : undefined;
|
|
25
|
+
}
|
|
26
|
+
if (!isRecord(value))
|
|
27
|
+
return undefined;
|
|
28
|
+
const entries = Object.entries(value)
|
|
29
|
+
.filter(([, enabled]) => enabled === true)
|
|
30
|
+
.map(([kind]) => kind)
|
|
31
|
+
.sort();
|
|
32
|
+
return entries.length > 0 ? entries : undefined;
|
|
33
|
+
}
|
|
34
|
+
function createCatalogEntry(input) {
|
|
35
|
+
const piCompatible = input.source === "pi";
|
|
36
|
+
return {
|
|
37
|
+
reference: `${input.provider}/${input.modelId}`,
|
|
38
|
+
provider: input.provider,
|
|
39
|
+
modelId: input.modelId,
|
|
40
|
+
...(input.name ? { name: input.name } : {}),
|
|
41
|
+
sources: [input.source],
|
|
42
|
+
credentialReady: input.credentialReady,
|
|
43
|
+
piCompatible,
|
|
44
|
+
eligibleForHarness: piCompatible && input.credentialReady === true && input.tools !== false,
|
|
45
|
+
...(compactRecord({
|
|
46
|
+
reasoning: input.reasoning,
|
|
47
|
+
tools: input.tools,
|
|
48
|
+
attachments: input.attachments,
|
|
49
|
+
input: input.inputKinds,
|
|
50
|
+
})
|
|
51
|
+
? {
|
|
52
|
+
capabilities: compactRecord({
|
|
53
|
+
reasoning: input.reasoning,
|
|
54
|
+
tools: input.tools,
|
|
55
|
+
attachments: input.attachments,
|
|
56
|
+
input: input.inputKinds,
|
|
57
|
+
}),
|
|
58
|
+
}
|
|
59
|
+
: {}),
|
|
60
|
+
...(compactRecord({
|
|
61
|
+
contextWindow: input.contextWindow,
|
|
62
|
+
maxOutputTokens: input.maxOutputTokens,
|
|
63
|
+
})
|
|
64
|
+
? {
|
|
65
|
+
limits: compactRecord({
|
|
66
|
+
contextWindow: input.contextWindow,
|
|
67
|
+
maxOutputTokens: input.maxOutputTokens,
|
|
68
|
+
}),
|
|
69
|
+
}
|
|
70
|
+
: {}),
|
|
71
|
+
...(compactRecord({ input: input.inputCost, output: input.outputCost })
|
|
72
|
+
? {
|
|
73
|
+
cost: compactRecord({
|
|
74
|
+
input: input.inputCost,
|
|
75
|
+
output: input.outputCost,
|
|
76
|
+
}),
|
|
77
|
+
}
|
|
78
|
+
: {}),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function sourceFailure(code) {
|
|
82
|
+
return {
|
|
83
|
+
report: {
|
|
84
|
+
status: code === "not-installed" || code === "sdk-unavailable" ? "unavailable" : "failed",
|
|
85
|
+
modelCount: 0,
|
|
86
|
+
code,
|
|
87
|
+
},
|
|
88
|
+
models: [],
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
async function readConfiguredPiModelReferences(modelsPath) {
|
|
92
|
+
try {
|
|
93
|
+
const parsed = JSON.parse(await readFile(modelsPath, "utf-8"));
|
|
94
|
+
if (!isRecord(parsed) || !isRecord(parsed.providers))
|
|
95
|
+
return [];
|
|
96
|
+
const references = [];
|
|
97
|
+
for (const [provider, rawProvider] of Object.entries(parsed.providers)) {
|
|
98
|
+
if (!isRecord(rawProvider) || !Array.isArray(rawProvider.models))
|
|
99
|
+
continue;
|
|
100
|
+
for (const rawModel of rawProvider.models) {
|
|
101
|
+
if (!isRecord(rawModel))
|
|
102
|
+
continue;
|
|
103
|
+
const modelId = stringField(rawModel.id);
|
|
104
|
+
if (modelId)
|
|
105
|
+
references.push(`${provider}/${modelId}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return [...new Set(references)];
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return [];
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
export async function discoverPiInitModels(options) {
|
|
115
|
+
let sdk;
|
|
116
|
+
try {
|
|
117
|
+
sdk = options?.loadSdk
|
|
118
|
+
? await options.loadSdk()
|
|
119
|
+
: (await import("@earendil-works/pi-coding-agent"));
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return sourceFailure("sdk-unavailable");
|
|
123
|
+
}
|
|
124
|
+
if (typeof sdk.getAgentDir !== "function" ||
|
|
125
|
+
typeof sdk.ModelRuntime?.create !== "function") {
|
|
126
|
+
return sourceFailure("sdk-unavailable");
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
const agentDir = sdk.getAgentDir();
|
|
130
|
+
const modelsPath = path.join(agentDir, "models.json");
|
|
131
|
+
const runtime = await sdk.ModelRuntime.create({
|
|
132
|
+
authPath: path.join(agentDir, "auth.json"),
|
|
133
|
+
modelsPath,
|
|
134
|
+
});
|
|
135
|
+
const snapshot = runtime.snapshot ?? {};
|
|
136
|
+
const available = Array.isArray(snapshot.available) ? snapshot.available : [];
|
|
137
|
+
const all = Array.isArray(snapshot.all) ? snapshot.all : available;
|
|
138
|
+
const availableReferences = new Set(available.flatMap((raw) => {
|
|
139
|
+
if (!isRecord(raw))
|
|
140
|
+
return [];
|
|
141
|
+
const provider = stringField(raw.provider);
|
|
142
|
+
const modelId = stringField(raw.id);
|
|
143
|
+
return provider && modelId ? [`${provider}/${modelId}`] : [];
|
|
144
|
+
}));
|
|
145
|
+
const configuredReferences = await readConfiguredPiModelReferences(modelsPath);
|
|
146
|
+
const includedReferences = new Set([
|
|
147
|
+
...configuredReferences,
|
|
148
|
+
...(options?.includeReferences ?? []),
|
|
149
|
+
]);
|
|
150
|
+
const selected = new Map();
|
|
151
|
+
for (const raw of available) {
|
|
152
|
+
if (!isRecord(raw))
|
|
153
|
+
continue;
|
|
154
|
+
const provider = stringField(raw.provider);
|
|
155
|
+
const modelId = stringField(raw.id);
|
|
156
|
+
if (provider && modelId)
|
|
157
|
+
selected.set(`${provider}/${modelId}`, raw);
|
|
158
|
+
}
|
|
159
|
+
for (const raw of all) {
|
|
160
|
+
if (!isRecord(raw))
|
|
161
|
+
continue;
|
|
162
|
+
const provider = stringField(raw.provider);
|
|
163
|
+
const modelId = stringField(raw.id);
|
|
164
|
+
const reference = provider && modelId ? `${provider}/${modelId}` : undefined;
|
|
165
|
+
if (reference && includedReferences.has(reference) && !selected.has(reference)) {
|
|
166
|
+
selected.set(reference, raw);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const models = [...selected.values()].flatMap((raw) => {
|
|
170
|
+
if (!isRecord(raw))
|
|
171
|
+
return [];
|
|
172
|
+
const provider = stringField(raw.provider);
|
|
173
|
+
const modelId = stringField(raw.id);
|
|
174
|
+
if (!provider || !modelId)
|
|
175
|
+
return [];
|
|
176
|
+
const cost = isRecord(raw.cost) ? raw.cost : {};
|
|
177
|
+
return [
|
|
178
|
+
createCatalogEntry({
|
|
179
|
+
source: "pi",
|
|
180
|
+
provider,
|
|
181
|
+
modelId,
|
|
182
|
+
name: stringField(raw.name),
|
|
183
|
+
credentialReady: availableReferences.has(`${provider}/${modelId}`),
|
|
184
|
+
reasoning: booleanField(raw.reasoning),
|
|
185
|
+
inputKinds: sanitizeInputKinds(raw.input),
|
|
186
|
+
contextWindow: numberField(raw.contextWindow),
|
|
187
|
+
maxOutputTokens: numberField(raw.maxTokens),
|
|
188
|
+
inputCost: numberField(cost.input),
|
|
189
|
+
outputCost: numberField(cost.output),
|
|
190
|
+
}),
|
|
191
|
+
];
|
|
192
|
+
});
|
|
193
|
+
return {
|
|
194
|
+
report: {
|
|
195
|
+
status: "available",
|
|
196
|
+
modelCount: models.length,
|
|
197
|
+
knownModelCount: all.length,
|
|
198
|
+
availableModelCount: availableReferences.size,
|
|
199
|
+
configuredModelCount: configuredReferences.length,
|
|
200
|
+
credentialReadyModelCount: models.filter((model) => model.credentialReady).length,
|
|
201
|
+
},
|
|
202
|
+
models,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
return sourceFailure("runtime-failed");
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const ANSI_PATTERN = /[\u001b\u009b][[\]()#;?]*(?:(?:(?:[a-zA-Z\d]*(?:;[-a-zA-Z\d/#&.:=?%@~_]+)*)?\u0007)|(?:(?:\d{1,4}(?:[;:]\d{0,4})*)?[\dA-PR-TZcf-nq-uy=><~]))/g;
|
|
210
|
+
function stripAnsi(value) {
|
|
211
|
+
return value.replace(ANSI_PATTERN, "");
|
|
212
|
+
}
|
|
213
|
+
function normalizeProviderLabel(value) {
|
|
214
|
+
return value.toLocaleLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
215
|
+
}
|
|
216
|
+
/** Parse provider labels only; credential values and paths never leave this parser. */
|
|
217
|
+
export function parseOpenCodeCredentialProviders(value) {
|
|
218
|
+
const providers = new Set();
|
|
219
|
+
for (const line of stripAnsi(value).split(/\r?\n/)) {
|
|
220
|
+
const match = line.match(/^\s*[•*]\s+(.+?)\s+(?:api|oauth|[A-Z][A-Z0-9_]+)\s*$/);
|
|
221
|
+
if (match?.[1])
|
|
222
|
+
providers.add(normalizeProviderLabel(match[1]));
|
|
223
|
+
}
|
|
224
|
+
return providers;
|
|
225
|
+
}
|
|
226
|
+
export function parseOpenCodeModelReferences(value, credentialProviders = new Set()) {
|
|
227
|
+
const models = new Map();
|
|
228
|
+
for (const rawLine of stripAnsi(value).split(/\r?\n/)) {
|
|
229
|
+
const line = rawLine.trim();
|
|
230
|
+
const match = line.match(/^([A-Za-z0-9._-]+)\/(\S+)$/);
|
|
231
|
+
if (!match)
|
|
232
|
+
continue;
|
|
233
|
+
const [, provider, modelId] = match;
|
|
234
|
+
const model = createCatalogEntry({
|
|
235
|
+
source: "opencode",
|
|
236
|
+
provider,
|
|
237
|
+
modelId,
|
|
238
|
+
credentialReady: credentialProviders.has(normalizeProviderLabel(provider))
|
|
239
|
+
? true
|
|
240
|
+
: null,
|
|
241
|
+
});
|
|
242
|
+
models.set(model.reference, model);
|
|
243
|
+
}
|
|
244
|
+
return [...models.values()];
|
|
245
|
+
}
|
|
246
|
+
export async function discoverOpenCodeInitModels(options) {
|
|
247
|
+
const runCommand = options.runCommand ??
|
|
248
|
+
(async (command, args, commandOptions) => (await execFileAsync(command, args, {
|
|
249
|
+
cwd: commandOptions.cwd,
|
|
250
|
+
timeout: commandOptions.timeout,
|
|
251
|
+
maxBuffer: commandOptions.maxBuffer,
|
|
252
|
+
encoding: "utf8",
|
|
253
|
+
windowsHide: true,
|
|
254
|
+
})));
|
|
255
|
+
const commandOptions = {
|
|
256
|
+
cwd: options.repoRoot,
|
|
257
|
+
timeout: 15_000,
|
|
258
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
259
|
+
};
|
|
260
|
+
const executable = process.platform === "win32" ? (process.env.ComSpec ?? "cmd.exe") : "opencode";
|
|
261
|
+
const commandArgs = (args) => process.platform === "win32"
|
|
262
|
+
? ["/d", "/s", "/c", "opencode.cmd", ...args]
|
|
263
|
+
: args;
|
|
264
|
+
try {
|
|
265
|
+
const [modelsResult, authResult] = await Promise.all([
|
|
266
|
+
runCommand(executable, commandArgs(["models", "--pure"]), commandOptions),
|
|
267
|
+
runCommand(executable, commandArgs(["auth", "list", "--pure"]), commandOptions).catch(() => ({ stdout: "" })),
|
|
268
|
+
]);
|
|
269
|
+
const models = parseOpenCodeModelReferences(modelsResult.stdout, parseOpenCodeCredentialProviders(authResult.stdout));
|
|
270
|
+
if (models.length === 0)
|
|
271
|
+
return sourceFailure("invalid-output");
|
|
272
|
+
return {
|
|
273
|
+
report: { status: "available", modelCount: models.length },
|
|
274
|
+
models,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
catch (error) {
|
|
278
|
+
const code = isRecord(error) ? error.code : undefined;
|
|
279
|
+
return sourceFailure(code === "ENOENT" ? "not-installed" : "command-failed");
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
function mergeCatalogModels(models) {
|
|
283
|
+
const merged = new Map();
|
|
284
|
+
for (const model of models) {
|
|
285
|
+
const current = merged.get(model.reference);
|
|
286
|
+
if (!current) {
|
|
287
|
+
merged.set(model.reference, model);
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
const sources = [...new Set([...current.sources, ...model.sources])].sort();
|
|
291
|
+
const piModel = model.sources.includes("pi") ? model : current;
|
|
292
|
+
merged.set(model.reference, {
|
|
293
|
+
...current,
|
|
294
|
+
...model,
|
|
295
|
+
name: piModel.name ?? model.name ?? current.name,
|
|
296
|
+
sources,
|
|
297
|
+
credentialReady: piModel.credentialReady,
|
|
298
|
+
piCompatible: true,
|
|
299
|
+
eligibleForHarness: piModel.eligibleForHarness,
|
|
300
|
+
capabilities: { ...current.capabilities, ...model.capabilities, ...piModel.capabilities },
|
|
301
|
+
limits: { ...current.limits, ...model.limits, ...piModel.limits },
|
|
302
|
+
cost: { ...current.cost, ...model.cost, ...piModel.cost },
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
return [...merged.values()].sort((left, right) => left.reference.localeCompare(right.reference));
|
|
306
|
+
}
|
|
307
|
+
function buildPiSetupGuidance(report) {
|
|
308
|
+
if (report.status !== "available") {
|
|
309
|
+
return {
|
|
310
|
+
status: "discovery-failed",
|
|
311
|
+
reason: `Pi model discovery is ${report.status}${report.code ? ` (${report.code})` : ""}.`,
|
|
312
|
+
nextSteps: [
|
|
313
|
+
"Repair or install the Pi runtime, then rerun init model-catalog before changing harness model routing.",
|
|
314
|
+
],
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
if ((report.availableModelCount ?? report.modelCount) === 0 &&
|
|
318
|
+
(report.configuredModelCount ?? 0) === 0) {
|
|
319
|
+
return {
|
|
320
|
+
status: "model-definition-required",
|
|
321
|
+
reason: "Pi exposes no model definitions, so harness model routing cannot be selected safely.",
|
|
322
|
+
configPath: "~/.pi/agent/models.json",
|
|
323
|
+
requiredUserInput: [
|
|
324
|
+
"provider id and model id",
|
|
325
|
+
"provider API type and base URL when using a custom endpoint",
|
|
326
|
+
"context window and maximum output tokens",
|
|
327
|
+
"supported input types and whether reasoning is supported",
|
|
328
|
+
"cost constraints when they affect LOW/MED/HIGH selection",
|
|
329
|
+
"credential method or environment-variable name, never the credential value in chat or catalog output",
|
|
330
|
+
],
|
|
331
|
+
rules: [
|
|
332
|
+
"Ask the user only for facts that cannot be inferred from their provider documentation or existing agent configuration.",
|
|
333
|
+
"Merge a model definition into Pi models.json without replacing unrelated providers or models.",
|
|
334
|
+
"Do not copy secrets into harness.json, reports, prompts, or model-catalog output; prefer Pi login/auth storage or an environment-variable reference.",
|
|
335
|
+
],
|
|
336
|
+
nextSteps: [
|
|
337
|
+
"After the user supplies the missing facts, help configure Pi models.json and authentication using Pi's native schema.",
|
|
338
|
+
"Rerun init model-catalog and select harness tiers only after Pi reports credential-ready models.",
|
|
339
|
+
],
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
if ((report.credentialReadyModelCount ?? 0) === 0) {
|
|
343
|
+
return {
|
|
344
|
+
status: "credentials-required",
|
|
345
|
+
reason: "Pi knows model definitions but none are credential-ready.",
|
|
346
|
+
nextSteps: [
|
|
347
|
+
"Ask the user which existing Pi provider should be enabled and which credential method is allowed; if the intended model is not defined in Pi, also ask for its provider/model id, API/base URL, context window, maximum output tokens, inputs, reasoning support, and cost constraints.",
|
|
348
|
+
"Configure authentication through Pi login/auth storage or an environment-variable reference without exposing the credential to loop-agent.",
|
|
349
|
+
"Rerun init model-catalog before selecting or changing harness tiers.",
|
|
350
|
+
],
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
return { status: "ready" };
|
|
354
|
+
}
|
|
355
|
+
function configuredTierReference(value) {
|
|
356
|
+
const model = typeof value === "string"
|
|
357
|
+
? value
|
|
358
|
+
: isRecord(value) && typeof value.model === "string"
|
|
359
|
+
? value.model
|
|
360
|
+
: undefined;
|
|
361
|
+
if (!model)
|
|
362
|
+
return undefined;
|
|
363
|
+
const trimmed = model.trim();
|
|
364
|
+
return trimmed.length > 0 && trimmed !== "default" ? trimmed : undefined;
|
|
365
|
+
}
|
|
366
|
+
export function assessInitPiModelReadiness(pi, catalog) {
|
|
367
|
+
const tiers = {};
|
|
368
|
+
const preserveTiers = [];
|
|
369
|
+
const reselectTiers = [];
|
|
370
|
+
const piModels = catalog.models.filter((model) => model.sources.includes("pi"));
|
|
371
|
+
for (const tier of ["LOW", "MED", "HIGH"]) {
|
|
372
|
+
const configured = configuredTierReference(pi?.[tier]);
|
|
373
|
+
const matches = configured
|
|
374
|
+
? configured.includes("/")
|
|
375
|
+
? piModels.filter((model) => model.reference === configured)
|
|
376
|
+
: piModels.filter((model) => model.modelId === configured)
|
|
377
|
+
: [];
|
|
378
|
+
const resolved = matches.length === 1 ? matches[0] : undefined;
|
|
379
|
+
const credentialReady = resolved?.eligibleForHarness === true;
|
|
380
|
+
tiers[tier] = {
|
|
381
|
+
...(configured ? { configured } : {}),
|
|
382
|
+
...(resolved ? { resolvedReference: resolved.reference } : {}),
|
|
383
|
+
inCatalog: resolved !== undefined,
|
|
384
|
+
credentialReady,
|
|
385
|
+
};
|
|
386
|
+
if (resolved)
|
|
387
|
+
preserveTiers.push(tier);
|
|
388
|
+
else
|
|
389
|
+
reselectTiers.push(tier);
|
|
390
|
+
}
|
|
391
|
+
let status;
|
|
392
|
+
if (catalog.piSetup.status === "model-definition-required") {
|
|
393
|
+
status = "model-definition-required";
|
|
394
|
+
}
|
|
395
|
+
else if (catalog.piSetup.status === "credentials-required") {
|
|
396
|
+
status = "credentials-required";
|
|
397
|
+
}
|
|
398
|
+
else if (catalog.piSetup.status === "discovery-failed") {
|
|
399
|
+
status = "discovery-failed";
|
|
400
|
+
}
|
|
401
|
+
else if (preserveTiers.some((tier) => tiers[tier].credentialReady === false)) {
|
|
402
|
+
status = "credentials-required";
|
|
403
|
+
}
|
|
404
|
+
else if (reselectTiers.length === 0) {
|
|
405
|
+
status = "ready";
|
|
406
|
+
}
|
|
407
|
+
else if (preserveTiers.length === 0) {
|
|
408
|
+
status = "selection-required";
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
status = "reselection-required";
|
|
412
|
+
}
|
|
413
|
+
return {
|
|
414
|
+
status,
|
|
415
|
+
source: catalog.sources.pi,
|
|
416
|
+
eligibleReferences: catalog.eligibleReferences,
|
|
417
|
+
tiers,
|
|
418
|
+
preserveTiers,
|
|
419
|
+
reselectTiers,
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
export async function discoverInitModelCatalog(options) {
|
|
423
|
+
const [pi, opencode] = await Promise.all([
|
|
424
|
+
options.discoverPi?.() ??
|
|
425
|
+
discoverPiInitModels({ includeReferences: options.includePiReferences }),
|
|
426
|
+
options.discoverOpenCode?.() ??
|
|
427
|
+
discoverOpenCodeInitModels({ repoRoot: options.repoRoot }),
|
|
428
|
+
]);
|
|
429
|
+
const models = mergeCatalogModels([...pi.models, ...opencode.models]);
|
|
430
|
+
const piSetup = buildPiSetupGuidance(pi.report);
|
|
431
|
+
return {
|
|
432
|
+
schemaVersion: 1,
|
|
433
|
+
privacy: {
|
|
434
|
+
rawConfigurationExposed: false,
|
|
435
|
+
credentialMaterialExposed: false,
|
|
436
|
+
note: "Collectors use Pi runtime metadata and OpenCode's non-verbose model/provider listings, then emit only allowlisted model metadata and credential readiness; raw config, tokens, headers, URLs, options, stderr, and auth paths are excluded.",
|
|
437
|
+
},
|
|
438
|
+
sources: { pi: pi.report, opencode: opencode.report },
|
|
439
|
+
models,
|
|
440
|
+
eligibleReferences: models
|
|
441
|
+
.filter((model) => model.eligibleForHarness)
|
|
442
|
+
.map((model) => model.reference),
|
|
443
|
+
piSetup,
|
|
444
|
+
selectionGuidance: {
|
|
445
|
+
LOW: "Prefer low latency and low cost for classification, scouting, and routine lightweight work.",
|
|
446
|
+
MED: "Prefer balanced coding, planning, and review quality for ordinary repository work.",
|
|
447
|
+
HIGH: "Prefer the strongest reasoning and coding capability for architecture, critical review, and difficult repair.",
|
|
448
|
+
rules: [
|
|
449
|
+
"Choose only models with eligibleForHarness=true; OpenCode-only entries are context, not proof of Pi executor compatibility.",
|
|
450
|
+
"Use target-project evidence plus model capabilities, cost, context window, and credential readiness; tiers may intentionally share a model.",
|
|
451
|
+
"Preserve an existing complete usable matrix unless it is unavailable or the user asked to reselect it.",
|
|
452
|
+
"Ask the user only for a material unresolved credential, cost, compliance, or availability decision.",
|
|
453
|
+
],
|
|
454
|
+
},
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
export function validateInitModelMatrixAgainstCatalog(matrix, catalog) {
|
|
458
|
+
const eligible = new Set(catalog.eligibleReferences);
|
|
459
|
+
for (const [tier, reference] of Object.entries(matrix)) {
|
|
460
|
+
if (!eligible.has(reference)) {
|
|
461
|
+
throw new Error(`init --${tier.toLocaleLowerCase()}-model is not a credential-ready Pi model from init model-catalog: ${reference}`);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|