@tea-agent/loop-agent 0.26.2 → 0.26.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/CHANGELOG.md +43 -0
- package/dist/executors/dag-pi-executor.js +182 -10
- package/dist/executors/pi-executor.js +233 -147
- package/dist/executors/pi-sdk-executor.js +140 -81
- package/dist/executors/pi-writer-tool-policy.js +266 -0
- package/dist/executors/shell-executor.js +355 -52
- package/dist/executors/shell-write-guard.js +145 -12
- package/dist/governance/document-index-closure.js +164 -0
- package/dist/worker/observe/node-input.js +8 -11
- package/dist/workflows/dag/convergence/controller.js +100 -3
- package/dist/workflows/dag/frontend-repair.js +29 -29
- package/dist/workflows/dag/frontend-verification-trace.js +12 -2
- package/dist/workflows/dag/governance-profile.js +1 -1
- package/dist/workflows/dag/init-hybrid.js +70 -16
- package/dist/workflows/dag/repair-artifact.js +100 -4
- package/dist/workflows/dag/rerun-plan.js +14 -7
- package/dist/workflows/dag/types.js +7 -1
- package/dist/workflows/dag/workspace-checkpoint.js +42 -0
- package/docs/templates/init-managed-agents.md +3 -3
- package/harness.json +1 -1
- package/package.json +1 -1
- package/skills/loop-agent/references/orchestrator-and-interventions.md +13 -6
|
@@ -37,6 +37,9 @@ function symbolEvidenceCandidates(symbol) {
|
|
|
37
37
|
return [symbol, describeTitle, "describe("];
|
|
38
38
|
return [symbol];
|
|
39
39
|
}
|
|
40
|
+
function isConfigurationVerificationFile(file) {
|
|
41
|
+
return /(?:^|\/)(?:package\.json|tsconfig(?:\.[^/]+)?\.json|(?:vite|webpack|rollup|docusaurus)\.config\.[cm]?[jt]s)$/.test(file.replace(/\\/g, "/"));
|
|
42
|
+
}
|
|
40
43
|
async function assertFileAndSymbol(input) {
|
|
41
44
|
const issues = [];
|
|
42
45
|
const absolute = path.resolve(input.workspaceRoot, input.file);
|
|
@@ -94,6 +97,7 @@ export async function runFrontendVerificationTraceGate(input) {
|
|
|
94
97
|
.join("; ")}`);
|
|
95
98
|
}
|
|
96
99
|
const contract = parsed.data;
|
|
100
|
+
const requiresBehaviorVerification = contract.verificationTargets.some((target) => target.type !== "static");
|
|
97
101
|
// Prefer post-repair reverify nodes when present (M3); else initial verify nodes (M2).
|
|
98
102
|
const staticCandidates = [
|
|
99
103
|
"frontend-static-reverify-shell",
|
|
@@ -146,7 +150,8 @@ export async function runFrontendVerificationTraceGate(input) {
|
|
|
146
150
|
if (input.evidence.static.commandLabels.length === 0) {
|
|
147
151
|
throw new Error("trace: no successful static verification commands in current run");
|
|
148
152
|
}
|
|
149
|
-
if (
|
|
153
|
+
if (requiresBehaviorVerification &&
|
|
154
|
+
input.evidence.behavior.commandLabels.length === 0) {
|
|
150
155
|
throw new Error("trace: no successful behavior verification commands in current run");
|
|
151
156
|
}
|
|
152
157
|
}
|
|
@@ -181,7 +186,12 @@ export async function runFrontendVerificationTraceGate(input) {
|
|
|
181
186
|
const fileIssues = await assertFileAndSymbol({
|
|
182
187
|
workspaceRoot: input.workspaceRoot,
|
|
183
188
|
file: target.file,
|
|
184
|
-
|
|
189
|
+
// Configuration files prove that the command's entrypoint exists;
|
|
190
|
+
// they do not expose source symbols. LLMs sometimes derive a
|
|
191
|
+
// filename fragment such as "build" from tsconfig.build.json.
|
|
192
|
+
symbol: isConfigurationVerificationFile(target.file)
|
|
193
|
+
? undefined
|
|
194
|
+
: target.symbol,
|
|
185
195
|
});
|
|
186
196
|
issues.push(...fileIssues);
|
|
187
197
|
const status = issues.length ? "failed" : "ok";
|
|
@@ -106,7 +106,7 @@ function writeSetMatchesAnyPattern(writeSet, patterns) {
|
|
|
106
106
|
function shellCommandLooksDeterministic(command) {
|
|
107
107
|
const normalized = command.replace(/["']/g, "");
|
|
108
108
|
return (/\bnpx vitest run\b/.test(normalized) ||
|
|
109
|
-
/\bnpm
|
|
109
|
+
/\bnpm(?:\s+--prefix\s+\S+)*\s+(?:run\s+)?(?:lint|typecheck|test|build)\b/.test(normalized) ||
|
|
110
110
|
/check-repo\.sh/.test(normalized) ||
|
|
111
111
|
/\bshell\.preset\b/.test(normalized) ||
|
|
112
112
|
/loop-agent-standard-verify/.test(normalized));
|
|
@@ -13,6 +13,7 @@ import { DEFAULT_READ_ONLY_PI_RETRY_POLICY, PROTOCOL_AWARE_PI_RETRY_POLICY, STRU
|
|
|
13
13
|
import { REVIEW_JSON_VERDICT_OUTPUT_PROTOCOL, REVIEW_VERDICT_OUTPUT_PROTOCOL, } from "./output-protocol.js";
|
|
14
14
|
import { resolveAdapter } from "../../adapters/index.js";
|
|
15
15
|
import { loadHarnessManifest } from "../../governance/harness.js";
|
|
16
|
+
import { mergeDocumentIndexCompanions } from "../../governance/document-index-closure.js";
|
|
16
17
|
import { buildAuthoritySurfaceAuditNode, buildAuthoritySurfaceGateNode, resolveAuthoritySurfaceAudit, } from "./authority-surface.js";
|
|
17
18
|
import { applySddEmbeddedEnhancements, probeRepoLocalSddSkills, } from "./sdd-embedded.js";
|
|
18
19
|
import { discoverProjectGovernancePresence } from "./project-governance-context.js";
|
|
@@ -721,9 +722,28 @@ function mergeForbiddenPaths(taskConfig) {
|
|
|
721
722
|
]);
|
|
722
723
|
return [...merged];
|
|
723
724
|
}
|
|
724
|
-
function resolveImplementPaths(taskConfig) {
|
|
725
|
+
function resolveImplementPaths(taskConfig, options = {}) {
|
|
725
726
|
if (taskConfig.allowedPaths.length > 0) {
|
|
726
727
|
const allowed = [...taskConfig.allowedPaths];
|
|
728
|
+
const forbidden = mergeForbiddenPaths(taskConfig);
|
|
729
|
+
const repoRoot = options.repoRoot;
|
|
730
|
+
// Only consult the catalog when writer paths can touch docs/* so non-docs
|
|
731
|
+
// temp fixtures without a catalog keep working; docs writers still fail closed.
|
|
732
|
+
const mayNeedDocIndex = allowed.some((entry) => {
|
|
733
|
+
const normalized = entry.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
734
|
+
return normalized === "docs" || normalized.startsWith("docs/");
|
|
735
|
+
});
|
|
736
|
+
if (repoRoot && mayNeedDocIndex) {
|
|
737
|
+
const merged = mergeDocumentIndexCompanions({
|
|
738
|
+
repoRoot,
|
|
739
|
+
paths: allowed,
|
|
740
|
+
forbiddenPaths: forbidden,
|
|
741
|
+
});
|
|
742
|
+
return {
|
|
743
|
+
allowedPaths: merged.paths,
|
|
744
|
+
writeSet: [...merged.paths],
|
|
745
|
+
};
|
|
746
|
+
}
|
|
727
747
|
return {
|
|
728
748
|
allowedPaths: allowed,
|
|
729
749
|
writeSet: [...allowed],
|
|
@@ -786,7 +806,9 @@ function applyMavenVerificationPlanning(input) {
|
|
|
786
806
|
if (!input.repoRoot || !input.commands || input.commands.length === 0) {
|
|
787
807
|
return { commands: input.commands };
|
|
788
808
|
}
|
|
789
|
-
const implement = resolveImplementPaths(input.taskConfig
|
|
809
|
+
const implement = resolveImplementPaths(input.taskConfig, {
|
|
810
|
+
repoRoot: input.repoRoot,
|
|
811
|
+
});
|
|
790
812
|
const planned = planMavenVerification({
|
|
791
813
|
repoRoot: input.repoRoot,
|
|
792
814
|
commands: input.commands,
|
|
@@ -890,7 +912,9 @@ function chooseFrontendVerifyCommands(input) {
|
|
|
890
912
|
if (input.parsedCommands.length > 0) {
|
|
891
913
|
return { commands: input.parsedCommands, commandSource: "inline" };
|
|
892
914
|
}
|
|
893
|
-
if (input.
|
|
915
|
+
if (input.allowAdapter !== false &&
|
|
916
|
+
input.adapterCommands &&
|
|
917
|
+
input.adapterCommands.length > 0) {
|
|
894
918
|
return { commands: input.adapterCommands, commandSource: "adapter" };
|
|
895
919
|
}
|
|
896
920
|
return { commandSource: "inline" };
|
|
@@ -1513,7 +1537,9 @@ function mergeFinalVerifyCommands(repoRoot, taskConfig, adapterCommands) {
|
|
|
1513
1537
|
export function buildStandardHybridDagFromTask(sources) {
|
|
1514
1538
|
const { taskConfig } = sources;
|
|
1515
1539
|
const forbiddenPaths = mergeForbiddenPaths(taskConfig);
|
|
1516
|
-
const implementPaths = resolveImplementPaths(taskConfig
|
|
1540
|
+
const implementPaths = resolveImplementPaths(taskConfig, {
|
|
1541
|
+
repoRoot: sources.repoRoot,
|
|
1542
|
+
});
|
|
1517
1543
|
const scoutPaths = deriveParallelScoutPaths(taskConfig);
|
|
1518
1544
|
const scoutComplexity = mapTaskComplexity(taskConfig.complexity);
|
|
1519
1545
|
const implementComplexity = resolveWriterComplexity(taskConfig);
|
|
@@ -2074,7 +2100,9 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2074
2100
|
frontendMockMode: mockMode,
|
|
2075
2101
|
};
|
|
2076
2102
|
const forbiddenPaths = mergeForbiddenPaths(taskConfig);
|
|
2077
|
-
const implementPaths = resolveImplementPaths(taskConfig
|
|
2103
|
+
const implementPaths = resolveImplementPaths(taskConfig, {
|
|
2104
|
+
repoRoot: sources.repoRoot,
|
|
2105
|
+
});
|
|
2078
2106
|
const implementId = frontendImplementationNodeId();
|
|
2079
2107
|
const mockContextBlock = resolveFrontendMockContextBlock(frontendSources);
|
|
2080
2108
|
const capabilityContextBlock = resolveFrontendCapabilityContextBlock(frontendSources);
|
|
@@ -2158,7 +2186,7 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2158
2186
|
"Frontend planning must consume the read-only Mock assessment strategy produced after scouting; MOCK_STRATEGY: blocked must not pass the deterministic Mock contract gate.",
|
|
2159
2187
|
"Mock implementations must preserve the real request path as the default, require explicit test/dev activation, and never rely on commenting out the real request.",
|
|
2160
2188
|
"Mock-backed behavior evidence proves only the documented frontend contract, never real API integration.",
|
|
2161
|
-
"frontend-implementation DAGs must complete deterministic static verification
|
|
2189
|
+
"frontend-implementation DAGs must complete deterministic static verification before final review. Behavior verification is also required when the task declares a behavior entrypoint or the implementation contract contains a non-static verification target; static-only contracts must map every target to the declared static entrypoint.",
|
|
2162
2190
|
"frontend review must block closeout unless review verdict is exactly VERDICT: pass.",
|
|
2163
2191
|
`Frontend risk classification: ${frontendRisk.selectedRisk} — ${frontendRisk.reason}`,
|
|
2164
2192
|
frontendRisk.forceFullGates
|
|
@@ -2183,6 +2211,10 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2183
2211
|
...explicitFrontendVerifyCommands.behaviorCommands,
|
|
2184
2212
|
].map(verifyCommandKey));
|
|
2185
2213
|
const adapterVerifyCommands = (sources.verifyCommands?.final ?? []).filter((command) => !explicitCommandKeys.has(verifyCommandKey(command)));
|
|
2214
|
+
const hasDeclaredFrontendVerification = explicitFrontendVerifyCommands.staticCommands.length > 0 ||
|
|
2215
|
+
explicitFrontendVerifyCommands.behaviorCommands.length > 0 ||
|
|
2216
|
+
parsedFrontendVerifyCommands.staticCommands.length > 0 ||
|
|
2217
|
+
parsedFrontendVerifyCommands.behaviorCommands.length > 0;
|
|
2186
2218
|
const staticVerifyCommands = chooseFrontendVerifyCommands({
|
|
2187
2219
|
explicitCommands: explicitFrontendVerifyCommands.staticCommands,
|
|
2188
2220
|
parsedCommands: parsedFrontendVerifyCommands.staticCommands,
|
|
@@ -2197,6 +2229,9 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2197
2229
|
explicitCommands: explicitFrontendVerifyCommands.behaviorCommands,
|
|
2198
2230
|
parsedCommands: parsedFrontendVerifyCommands.behaviorCommands,
|
|
2199
2231
|
adapterCommands: adapterVerifyCommands,
|
|
2232
|
+
// A declared task verifier owns this task's verification boundary. A
|
|
2233
|
+
// static-only task must not inherit unrelated root-level test commands.
|
|
2234
|
+
allowAdapter: !hasDeclaredFrontendVerification,
|
|
2200
2235
|
});
|
|
2201
2236
|
const staticShellCommands = buildVerifyShellCommands({
|
|
2202
2237
|
repoRoot: sources.repoRoot,
|
|
@@ -2211,8 +2246,13 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2211
2246
|
const behaviorShellCommands = buildVerifyShellCommands({
|
|
2212
2247
|
repoRoot: sources.repoRoot,
|
|
2213
2248
|
commands: behaviorVerifyCommands.commands,
|
|
2214
|
-
fallbackCommands:
|
|
2249
|
+
fallbackCommands: hasDeclaredFrontendVerification
|
|
2250
|
+
? []
|
|
2251
|
+
: behaviorFallbackCommands,
|
|
2215
2252
|
});
|
|
2253
|
+
const effectiveBehaviorFallbackCommands = hasDeclaredFrontendVerification
|
|
2254
|
+
? []
|
|
2255
|
+
: behaviorFallbackCommands;
|
|
2216
2256
|
const staticVerifyEvidence = buildVerifyEvidence({
|
|
2217
2257
|
phase: "intermediate",
|
|
2218
2258
|
quota: strategy.intermediateQuota ?? "full",
|
|
@@ -2238,7 +2278,7 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2238
2278
|
quota: "full",
|
|
2239
2279
|
commandSource: behaviorVerifyCommands.commandSource,
|
|
2240
2280
|
commands: behaviorVerifyCommands.commands,
|
|
2241
|
-
fallbackCommands:
|
|
2281
|
+
fallbackCommands: effectiveBehaviorFallbackCommands,
|
|
2242
2282
|
commandTexts: behaviorShellCommands,
|
|
2243
2283
|
finalFullRequired: true,
|
|
2244
2284
|
commandTimeoutMs: DEFAULT_VERIFY_TIMEOUT_MS,
|
|
@@ -2352,7 +2392,7 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2352
2392
|
subtask_prompt: [
|
|
2353
2393
|
"Audit the frontend plan before implementation.",
|
|
2354
2394
|
"First non-empty line must be exactly VERDICT: pass or VERDICT: request-revision.",
|
|
2355
|
-
"Request revision when the Mock strategy is MOCK_STRATEGY: blocked, missing, unsupported by repository evidence, inconsistent with the API contract, outside authorized paths/dependencies, unable to prove production-default-off behavior with the fixed production/default-real-path static check, or missing deterministic behavior verification for
|
|
2395
|
+
"Request revision when the Mock strategy is MOCK_STRATEGY: blocked, missing, unsupported by repository evidence, inconsistent with the API contract, outside authorized paths/dependencies, unable to prove production-default-off behavior with the fixed production/default-real-path static check, or missing deterministic behavior verification for a declared behavior target or selected Mock strategy. Mock strategies require Mock-backed evidence. A static-only contract is allowed only when every verification target is static and maps to a declared static entrypoint. not-needed otherwise requires applicable real/no-remote behavior evidence unless auto mode explicitly skipped Mock because no project Mock capability exists; in that case the plan must preserve the real request path and record the Real Integration Gap.",
|
|
2356
2396
|
"Also request revision for missing applicable UI states, unsupported dependency additions, design-system drift without reason, weak interaction coverage, broad scope, inline fake data, schema drift, or missing deterministic verification commands.",
|
|
2357
2397
|
"Read-only: do not modify repository files.",
|
|
2358
2398
|
fixedVerificationContext,
|
|
@@ -3586,7 +3626,7 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
3586
3626
|
outputContract: "Convert every final automatable Markdown case into pytest assets whose actual test function region contains the exact Case ID, preferably in the function name or docstring. Each testcase/md/<module>.md (excluding README.md) maps one-to-one to testcase/test_<module>.py; never merge or split modules. No JSON and no pytest execution.",
|
|
3587
3627
|
subtask_prompt: [
|
|
3588
3628
|
"Convert testcase/md/** to pytest using upstream environment and advisory validation evidence plus only bounded pytest config/conftest. A FAIL advisory report does not authorize inventing missing behavior; use the final Markdown facts that are present.",
|
|
3589
|
-
|
|
3629
|
+
'Ensure every final Markdown Case ID appears in exactly one primary pytest test function or pytest test class method region, using the exact `primary symbol` declared by Markdown. The symbol must start with `test_BE_<MODULE>_<NNN>_` so every parameterized collected item remains associated with its Case. Module-level functions and class-based pytest methods are both supported. Only `变体测试点` may use stable `pytest.param(..., id="TP-...")` IDs, and every atomic variant ID must appear exactly once with a genuine input/state/outcome change. Use `pytest.param(..., id=...)` for every row; do not use decorator-level `ids=[...]`, generated suffixes, or IDs that extend/shorten the exact Markdown TP. Do not parameterize `场景断言测试点` or `横切证据测试点`; execute all assertion checkpoints within the same business journey/item and use shared helpers for cross-cutting evidence. The primary symbol docstring must contain exact metadata lines `Case-ID: BE-...`, `Assertion-Test-Points: TP-...;TP-...` and `Cross-Cutting-Test-Points: TP-...;TP-...` (use `none` when empty). No Test Point may be invented, renamed, omitted or bound in two modes. The generated pytest collection shape must equal the Markdown prediction `sum(max(1, variant count per Case))`; keep it at or below the task\'s explicit budget by removing duplicate execution, never by collapsing multiple parameter rows under a coarse family TP. Assertions come only from 预期结果 and setup comes only from 前置条件/测试数据/自动化映射.',
|
|
3590
3630
|
"Name each generated pytest file so it corresponds one-to-one with its source Markdown module file: for each `testcase/md/<module>.md` (excluding README.md), emit exactly one `testcase/test_<module>.py`. The <module> stem is the Markdown filename without the `.md` extension, lowercased and with non-alphanumeric characters replaced by underscores. For example, `testcase/md/resource_notes.md` maps to `testcase/test_resource_notes.py`, `testcase/md/health.md` maps to `testcase/test_health.py`, `testcase/md/BE-HEALTH.md` maps to `testcase/test_be_health.py`, and `testcase/md/order-api.md` maps to `testcase/test_order_api.py`. If Markdown automation mapping names a different path than this module stem path, still write the module stem path and do not invent prefixes such as `test_be_*` unless the module filename itself normalizes to that stem. Never merge multiple Markdown modules into one pytest file, never split one module across several files, and never invent pytest filenames unrelated to the Markdown modules.",
|
|
3591
3631
|
"Generate a reusable HTTP logging helper (or equivalent client wrapper) and call it for every interface request. The request log must include method, URL/path, and request parameters (query plus JSON/body/payload summary). The response log must include status code and response result (JSON/text/body summary), and both records must be visible in pytest stdout/stderr without changing assertions.",
|
|
3592
3632
|
"HTTP response header names are case-insensitive. If the helper stores a lower-case normalized header map, every Content-Type or other header assertion must query the lower-case key (for example `content-type`) or use an explicitly case-insensitive accessor; never call a case-sensitive plain dict with `Content-Type` when the stored key is lower-case. Preserve the actual media-type assertion rather than dropping it.",
|
|
@@ -4125,12 +4165,20 @@ function buildFrontendTestHybridDag(sources) {
|
|
|
4125
4165
|
forbiddenPaths: forbidden,
|
|
4126
4166
|
outputContract: "Deterministic frontend L-5 Markdown and self-contained HTML dashboard derived only from frontend-test-result-v1.",
|
|
4127
4167
|
subtask_prompt: "Render the authoritative frontend L-5 report from frontend-test-result-v1. Do not use Pi prose or invent code coverage. Missing line/branch coverage remains unavailable and makes L-5 NOT READY.",
|
|
4128
|
-
shell: {
|
|
4168
|
+
shell: {
|
|
4169
|
+
frontendTestL5Report: {},
|
|
4170
|
+
commands: [],
|
|
4171
|
+
cwd: ".",
|
|
4172
|
+
timeoutMs: 120000,
|
|
4173
|
+
},
|
|
4129
4174
|
});
|
|
4130
4175
|
if (strictOutcomeGate) {
|
|
4131
4176
|
tasks.push({
|
|
4132
4177
|
id: "frontend-test-result-outcome-gate-shell",
|
|
4133
|
-
depends_on: [
|
|
4178
|
+
depends_on: [
|
|
4179
|
+
"materialize-frontend-test-result-shell",
|
|
4180
|
+
"frontend-test-l5-report-shell",
|
|
4181
|
+
],
|
|
4134
4182
|
role: "verifier",
|
|
4135
4183
|
executor: "shell",
|
|
4136
4184
|
complexity: "LOW",
|
|
@@ -4148,7 +4196,10 @@ function buildFrontendTestHybridDag(sources) {
|
|
|
4148
4196
|
}
|
|
4149
4197
|
tasks.push({
|
|
4150
4198
|
id: "frontend-test-retrospect-pi",
|
|
4151
|
-
depends_on: [
|
|
4199
|
+
depends_on: [
|
|
4200
|
+
"materialize-frontend-test-result-shell",
|
|
4201
|
+
"frontend-test-l5-report-shell",
|
|
4202
|
+
],
|
|
4152
4203
|
role: "closeout",
|
|
4153
4204
|
executor: "pi",
|
|
4154
4205
|
toolProfile: "write",
|
|
@@ -5379,7 +5430,7 @@ function buildReviewVerdictRecoveryNode(sources) {
|
|
|
5379
5430
|
writePolicy: "read-only",
|
|
5380
5431
|
allowedPaths: commonReadOnlyPaths(sources),
|
|
5381
5432
|
forbiddenPaths: commonForbiddenPaths(sources),
|
|
5382
|
-
outputContract:
|
|
5433
|
+
outputContract: "Structured JSON review verdict only, preserving the original review conclusion/findings without substantive changes. No file writes.",
|
|
5383
5434
|
outputProtocol: REVIEW_JSON_VERDICT_OUTPUT_PROTOCOL,
|
|
5384
5435
|
subtask_prompt: [
|
|
5385
5436
|
"Normalize the output format of review-pi; this is the single read-only format-recovery attempt for the review verdict protocol.",
|
|
@@ -5734,8 +5785,9 @@ function buildProcessSupervisorNode(sources) {
|
|
|
5734
5785
|
"Supervise the implementation process after soft verification.",
|
|
5735
5786
|
"First non-empty line must be exactly VERDICT: pass or VERDICT: request-revision.",
|
|
5736
5787
|
"Immediately after the verdict, include one fenced block labelled REPAIR_ARTIFACT_JSON with this JSON shape:",
|
|
5737
|
-
'{"schemaVersion":1,"verdict":"pass|request-revision","failureClass":"syntax|runtime|logic|boundary|environment|governance|unknown","rootCause":"one concise reason","fixScope":["path/or/component"],"invariant":"behavior or contract to preserve","evidenceRefs":["relative/path/or/node"],"rawLogFallbackAllowed":false}',
|
|
5788
|
+
'{"schemaVersion":1,"verdict":"pass|request-revision","failureClass":"syntax|runtime|logic|boundary|environment|governance|unknown","rootCause":"one concise reason","fixScope":["path/or/component"],"invariant":"behavior or contract to preserve","evidenceRefs":["relative/path/or/node"],"rawLogFallbackAllowed":false,"disposition":"repairable|blocked-boundary|no-op-pass","requiredScope":["out-of-boundary/path"]}',
|
|
5738
5789
|
`For request-revision, fixScope must be inside ${repairId} allowedPaths/writeSet. For pass, use an empty fixScope array.`,
|
|
5790
|
+
"disposition defaults to repairable when omitted (backward compatible). Use blocked-boundary when the needed fix is outside repair write authority; put the out-of-boundary paths in requiredScope (report-only, never expands write authority). Use no-op-pass only when no code change is warranted.",
|
|
5739
5791
|
`Audit boundary drift, verification gaps, and whether ${repairId} should perform bounded fixes. Read-only: do not modify files.`,
|
|
5740
5792
|
buildSourceContextBlock(sources),
|
|
5741
5793
|
].join("\n\n"),
|
|
@@ -5771,7 +5823,9 @@ function buildProcessGateNode(sources) {
|
|
|
5771
5823
|
};
|
|
5772
5824
|
}
|
|
5773
5825
|
function buildRepairNode(sources) {
|
|
5774
|
-
const implement = resolveImplementPaths(sources.taskConfig
|
|
5826
|
+
const implement = resolveImplementPaths(sources.taskConfig, {
|
|
5827
|
+
repoRoot: sources.repoRoot,
|
|
5828
|
+
});
|
|
5775
5829
|
return {
|
|
5776
5830
|
id: repairNodeId(),
|
|
5777
5831
|
depends_on: ["process-gate-shell", "process-supervisor-pi"],
|
|
@@ -9,17 +9,31 @@ export const repairFailureClassSchema = z.enum([
|
|
|
9
9
|
"governance",
|
|
10
10
|
"unknown",
|
|
11
11
|
]);
|
|
12
|
+
export const repairDispositionSchema = z.enum([
|
|
13
|
+
"repairable",
|
|
14
|
+
"blocked-boundary",
|
|
15
|
+
"no-op-pass",
|
|
16
|
+
]);
|
|
12
17
|
export const repairArtifactSchema = z.object({
|
|
13
18
|
schemaVersion: z.literal(1),
|
|
14
19
|
verdict: z.enum(["pass", "request-revision"]),
|
|
15
20
|
failureClass: repairFailureClassSchema,
|
|
16
|
-
rootCause: z
|
|
17
|
-
|
|
18
|
-
|
|
21
|
+
rootCause: z.string().min(1, {
|
|
22
|
+
message: 'repair artifact field "rootCause" must be a non-empty string',
|
|
23
|
+
}),
|
|
19
24
|
fixScope: z.array(z.string().min(1)).default([]),
|
|
20
25
|
invariant: z.string().min(1),
|
|
21
26
|
evidenceRefs: z.array(z.string().min(1)).default([]),
|
|
22
27
|
rawLogFallbackAllowed: z.boolean().default(false),
|
|
28
|
+
/**
|
|
29
|
+
* Optional disposition for convergence routing. Omitted values are treated as
|
|
30
|
+
* `repairable` for backward compatibility with older artifacts.
|
|
31
|
+
*/
|
|
32
|
+
disposition: repairDispositionSchema.optional(),
|
|
33
|
+
/**
|
|
34
|
+
* Report-only out-of-boundary paths. Never expands write authority.
|
|
35
|
+
*/
|
|
36
|
+
requiredScope: z.array(z.string().min(1)).optional(),
|
|
23
37
|
});
|
|
24
38
|
/**
|
|
25
39
|
* Canonical rootCause substituted when a `verdict: "pass"` repair artifact
|
|
@@ -117,7 +131,23 @@ function scopeWithinPatterns(scope, patterns) {
|
|
|
117
131
|
}
|
|
118
132
|
export function validateRepairArtifactScope(input) {
|
|
119
133
|
const { artifact, repairTask } = input;
|
|
134
|
+
const disposition = artifact.disposition ?? "repairable";
|
|
135
|
+
const requiredScope = artifact.requiredScope ?? [];
|
|
136
|
+
for (const scope of requiredScope) {
|
|
137
|
+
if (!isPathLikeScope(scope)) {
|
|
138
|
+
return {
|
|
139
|
+
ok: false,
|
|
140
|
+
reason: `requiredScope "${scope}" must be a concrete path or glob (report-only; does not expand write authority)`,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
}
|
|
120
144
|
if (artifact.verdict === "pass") {
|
|
145
|
+
if (disposition === "blocked-boundary") {
|
|
146
|
+
return {
|
|
147
|
+
ok: false,
|
|
148
|
+
reason: "pass repair artifact cannot use blocked-boundary disposition",
|
|
149
|
+
};
|
|
150
|
+
}
|
|
121
151
|
if (artifact.fixScope.length > 0) {
|
|
122
152
|
return {
|
|
123
153
|
ok: false,
|
|
@@ -126,6 +156,67 @@ export function validateRepairArtifactScope(input) {
|
|
|
126
156
|
}
|
|
127
157
|
return { ok: true, artifact };
|
|
128
158
|
}
|
|
159
|
+
if (disposition === "no-op-pass") {
|
|
160
|
+
return {
|
|
161
|
+
ok: false,
|
|
162
|
+
reason: "request-revision repair artifact cannot use no-op-pass disposition",
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
// blocked-boundary may report empty fixScope (nothing in-boundary to fix)
|
|
166
|
+
// while requiredScope describes the out-of-boundary need.
|
|
167
|
+
if (disposition === "blocked-boundary") {
|
|
168
|
+
if (requiredScope.length === 0) {
|
|
169
|
+
return {
|
|
170
|
+
ok: false,
|
|
171
|
+
reason: "blocked-boundary request-revision requires non-empty requiredScope (report-only)",
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
if (!repairTask) {
|
|
175
|
+
return {
|
|
176
|
+
ok: false,
|
|
177
|
+
reason: "repair artifact gate cannot find downstream repair task",
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
const allowed = repairTask.allowedPaths ?? [];
|
|
181
|
+
const writeSet = repairTask.writeSet ?? [];
|
|
182
|
+
const forbidden = repairTask.forbiddenPaths ?? [];
|
|
183
|
+
const hasOutOfBoundaryRequiredScope = requiredScope.some((scope) => {
|
|
184
|
+
const normalized = normalizePath(scope);
|
|
185
|
+
return (!scopeWithinPatterns(scope, allowed) ||
|
|
186
|
+
!scopeWithinPatterns(scope, writeSet) ||
|
|
187
|
+
forbidden.some((pattern) => pathMatchesPattern(normalized, pattern)));
|
|
188
|
+
});
|
|
189
|
+
if (!hasOutOfBoundaryRequiredScope) {
|
|
190
|
+
return {
|
|
191
|
+
ok: false,
|
|
192
|
+
reason: "blocked-boundary requiredScope must include a path outside repair task allowedPaths/writeSet",
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
if (artifact.fixScope.length > 0) {
|
|
196
|
+
for (const scope of artifact.fixScope) {
|
|
197
|
+
if (!isPathLikeScope(scope)) {
|
|
198
|
+
return {
|
|
199
|
+
ok: false,
|
|
200
|
+
reason: `fixScope "${scope}" must be a concrete path or glob inside repair task allowedPaths/writeSet`,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
if (!scopeWithinPatterns(scope, allowed) ||
|
|
204
|
+
!scopeWithinPatterns(scope, writeSet)) {
|
|
205
|
+
return {
|
|
206
|
+
ok: false,
|
|
207
|
+
reason: `fixScope "${scope}" is outside repair task allowedPaths/writeSet`,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
if (forbidden.some((pattern) => pathMatchesPattern(normalizePath(scope), pattern))) {
|
|
211
|
+
return {
|
|
212
|
+
ok: false,
|
|
213
|
+
reason: `fixScope "${scope}" matches repair task forbiddenPaths`,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return { ok: true, artifact };
|
|
219
|
+
}
|
|
129
220
|
if (artifact.fixScope.length === 0) {
|
|
130
221
|
return {
|
|
131
222
|
ok: false,
|
|
@@ -148,7 +239,8 @@ export function validateRepairArtifactScope(input) {
|
|
|
148
239
|
reason: `fixScope "${scope}" must be a concrete path or glob inside repair task allowedPaths/writeSet`,
|
|
149
240
|
};
|
|
150
241
|
}
|
|
151
|
-
if (!scopeWithinPatterns(scope, allowed) ||
|
|
242
|
+
if (!scopeWithinPatterns(scope, allowed) ||
|
|
243
|
+
!scopeWithinPatterns(scope, writeSet)) {
|
|
152
244
|
return {
|
|
153
245
|
ok: false,
|
|
154
246
|
reason: `fixScope "${scope}" is outside repair task allowedPaths/writeSet`,
|
|
@@ -255,12 +347,16 @@ export function resolveRepairTaskForGate(input) {
|
|
|
255
347
|
};
|
|
256
348
|
}
|
|
257
349
|
export function formatRepairArtifactForPrompt(artifact) {
|
|
350
|
+
const disposition = artifact.disposition ?? "repairable";
|
|
351
|
+
const requiredScope = artifact.requiredScope ?? [];
|
|
258
352
|
return [
|
|
259
353
|
"Repair artifact:",
|
|
260
354
|
`- verdict: ${artifact.verdict}`,
|
|
261
355
|
`- failureClass: ${artifact.failureClass}`,
|
|
262
356
|
`- rootCause: ${artifact.rootCause}`,
|
|
357
|
+
`- disposition: ${disposition}`,
|
|
263
358
|
`- fixScope: ${artifact.fixScope.length > 0 ? artifact.fixScope.join(", ") : "(none)"}`,
|
|
359
|
+
`- requiredScope: ${requiredScope.length > 0 ? requiredScope.join(", ") : "(none)"}`,
|
|
264
360
|
`- invariant: ${artifact.invariant}`,
|
|
265
361
|
`- evidenceRefs: ${artifact.evidenceRefs.length > 0 ? artifact.evidenceRefs.join(", ") : "(none)"}`,
|
|
266
362
|
].join("\n");
|
|
@@ -254,7 +254,10 @@ export function computeResetClosure(spec, effectiveFromNodeId) {
|
|
|
254
254
|
if (!tasks.has(effectiveFromNodeId)) {
|
|
255
255
|
return [];
|
|
256
256
|
}
|
|
257
|
-
return [
|
|
257
|
+
return [
|
|
258
|
+
effectiveFromNodeId,
|
|
259
|
+
...collectReachableDescendants(spec, effectiveFromNodeId),
|
|
260
|
+
].sort();
|
|
258
261
|
}
|
|
259
262
|
export function assessResetClosureSafety(spec, resetNodeIds) {
|
|
260
263
|
const tasks = taskById(spec);
|
|
@@ -355,7 +358,8 @@ function computeVerificationPolicy(spec, parentRunId, resetNodeIds, importedNode
|
|
|
355
358
|
function deriveSuggestedAction(input) {
|
|
356
359
|
if (input.eligible)
|
|
357
360
|
return "dag-rerun";
|
|
358
|
-
if (input.workerManaged ||
|
|
361
|
+
if (input.workerManaged ||
|
|
362
|
+
input.reasonCodes.includes("worker-managed-rerun-unsupported")) {
|
|
359
363
|
return "worker-task-retry";
|
|
360
364
|
}
|
|
361
365
|
if (input.parentLifecycle === "paused")
|
|
@@ -449,7 +453,8 @@ export async function evaluateDagRerunPlan(input) {
|
|
|
449
453
|
input.currentControllerFingerprint === input.parentControllerFingerprint;
|
|
450
454
|
const workspaceMatched = input.parentTerminalWorkspace?.fingerprint !== undefined &&
|
|
451
455
|
input.currentWorkspace?.fingerprint !== undefined &&
|
|
452
|
-
input.parentTerminalWorkspace.fingerprint ===
|
|
456
|
+
input.parentTerminalWorkspace.fingerprint ===
|
|
457
|
+
input.currentWorkspace.fingerprint;
|
|
453
458
|
const skillSnapshotVerified = input.skillSnapshotOk !== false;
|
|
454
459
|
const sourceBindingMatched = input.bindingStatus?.sourceBindingMatched ?? true;
|
|
455
460
|
const taskContractBindingMatched = input.bindingStatus?.taskContractBindingMatched ?? true;
|
|
@@ -499,7 +504,8 @@ export async function evaluateDagRerunPlan(input) {
|
|
|
499
504
|
blockedReasons.push("workspace-checkpoint-missing");
|
|
500
505
|
}
|
|
501
506
|
else if (input.currentWorkspace?.fingerprint &&
|
|
502
|
-
input.parentTerminalWorkspace.fingerprint !==
|
|
507
|
+
input.parentTerminalWorkspace.fingerprint !==
|
|
508
|
+
input.currentWorkspace.fingerprint) {
|
|
503
509
|
reasonCodes.push("workspace-drift");
|
|
504
510
|
blockedReasons.push("workspace-drift");
|
|
505
511
|
}
|
|
@@ -566,9 +572,10 @@ export async function evaluateDagRerunPlan(input) {
|
|
|
566
572
|
const totalPi = countExecutorNodes(input.parentSpec, allNodeIds, "pi");
|
|
567
573
|
const resetPi = countExecutorNodes(input.parentSpec, resetNodeIds, "pi");
|
|
568
574
|
const resetShell = countExecutorNodes(input.parentSpec, resetNodeIds, "shell");
|
|
569
|
-
const workerAssociation = input.workerAssociation ??
|
|
570
|
-
|
|
571
|
-
|
|
575
|
+
const workerAssociation = input.workerAssociation ??
|
|
576
|
+
(input.workerManaged
|
|
577
|
+
? { kind: "worker-managed" }
|
|
578
|
+
: { kind: "standalone" });
|
|
572
579
|
const planWithoutHash = {
|
|
573
580
|
schemaVersion: 1,
|
|
574
581
|
eligible,
|
|
@@ -163,7 +163,7 @@ export const dagFrontendVerificationBundleSchema = z
|
|
|
163
163
|
mockCommands: z.array(z.string()).default([]),
|
|
164
164
|
lintCommands: z.array(z.string().min(1)).optional(),
|
|
165
165
|
staticCommands: z.array(z.string()).min(1),
|
|
166
|
-
behaviorCommands: z.array(z.string()).
|
|
166
|
+
behaviorCommands: z.array(z.string()).default([]),
|
|
167
167
|
mockEvidence: dagShellVerifyEvidenceSchema.optional(),
|
|
168
168
|
lintEvidence: dagShellVerifyEvidenceSchema.optional(),
|
|
169
169
|
staticEvidence: dagShellVerifyEvidenceSchema,
|
|
@@ -391,6 +391,12 @@ export const dagShellConfigSchema = z.object({
|
|
|
391
391
|
repairArtifactGate: dagRepairArtifactGateSchema.optional(),
|
|
392
392
|
/** fail (default): any nonzero command fails the node. record: finish node FINISHED with failure facts for downstream assess/repair. */
|
|
393
393
|
nonZeroExitPolicy: z.enum(["fail", "record"]).optional(),
|
|
394
|
+
/**
|
|
395
|
+
* When true/omitted, stop after the first failed command (runtime default true).
|
|
396
|
+
* When false, continue executing remaining commands and aggregate failures.
|
|
397
|
+
* Optional on the schema so existing shell literals stay valid; executor applies default.
|
|
398
|
+
*/
|
|
399
|
+
failFast: z.boolean().optional(),
|
|
394
400
|
envAllowlist: z.array(z.string()).optional(),
|
|
395
401
|
timeoutMs: z.number().optional(),
|
|
396
402
|
cwd: z.string().optional(),
|
|
@@ -94,11 +94,47 @@ function computeFingerprint(input) {
|
|
|
94
94
|
gitHeadSha: input.gitHeadSha,
|
|
95
95
|
statusPorcelainSha256: input.statusPorcelainSha256,
|
|
96
96
|
changedPaths: input.changedPaths,
|
|
97
|
+
stableGovernanceInputs: input.stableGovernanceInputs,
|
|
97
98
|
});
|
|
98
99
|
return sha256Hex(payload);
|
|
99
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Stable Task Contract governance inputs for same-run hard-verify reuse.
|
|
103
|
+
* Explicitly excludes `.harness/dag-runs/**` and other runner-owned volatile facts.
|
|
104
|
+
*/
|
|
105
|
+
export function listStableGovernanceInputPaths(taskId) {
|
|
106
|
+
const safeTaskId = String(taskId ?? "").trim();
|
|
107
|
+
if (!safeTaskId ||
|
|
108
|
+
safeTaskId.includes("..") ||
|
|
109
|
+
safeTaskId.includes("/") ||
|
|
110
|
+
safeTaskId.includes("\\")) {
|
|
111
|
+
return [];
|
|
112
|
+
}
|
|
113
|
+
const base = `.harness/tasks/${safeTaskId}`;
|
|
114
|
+
return [
|
|
115
|
+
`${base}/task.json`,
|
|
116
|
+
`${base}/dag.json`,
|
|
117
|
+
`${base}/source/需求.md`,
|
|
118
|
+
`${base}/source/执行约束.md`,
|
|
119
|
+
`${base}/source/source-manifest.json`,
|
|
120
|
+
];
|
|
121
|
+
}
|
|
122
|
+
async function hashStableGovernanceInputs(repoRoot, taskId) {
|
|
123
|
+
const paths = listStableGovernanceInputPaths(taskId);
|
|
124
|
+
const entries = [];
|
|
125
|
+
for (const relPath of paths) {
|
|
126
|
+
const hashed = await hashWorkspacePath(repoRoot, relPath, "modified");
|
|
127
|
+
entries.push({
|
|
128
|
+
path: relPath,
|
|
129
|
+
contentSha256: hashed.contentSha256 ?? sha256Hex(`missing:${relPath}`),
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
entries.sort((a, b) => a.path.localeCompare(b.path));
|
|
133
|
+
return entries;
|
|
134
|
+
}
|
|
100
135
|
/**
|
|
101
136
|
* Capture a workspace checkpoint for the current working tree.
|
|
137
|
+
* When `taskId` is provided, fingerprint includes stable ignored Task Contract inputs.
|
|
102
138
|
*/
|
|
103
139
|
export async function captureWorkspaceCheckpoint(repoRoot, options) {
|
|
104
140
|
const capturedAt = (options?.now ?? new Date()).toISOString();
|
|
@@ -127,10 +163,15 @@ export async function captureWorkspaceCheckpoint(repoRoot, options) {
|
|
|
127
163
|
});
|
|
128
164
|
}
|
|
129
165
|
changedPaths.sort((a, b) => a.path.localeCompare(b.path));
|
|
166
|
+
const taskId = options?.taskId?.trim();
|
|
167
|
+
const stableGovernanceInputs = taskId
|
|
168
|
+
? await hashStableGovernanceInputs(repoRoot, taskId)
|
|
169
|
+
: undefined;
|
|
130
170
|
const fingerprint = computeFingerprint({
|
|
131
171
|
gitHeadSha,
|
|
132
172
|
statusPorcelainSha256,
|
|
133
173
|
changedPaths,
|
|
174
|
+
stableGovernanceInputs,
|
|
134
175
|
});
|
|
135
176
|
return {
|
|
136
177
|
schemaVersion: 1,
|
|
@@ -138,6 +179,7 @@ export async function captureWorkspaceCheckpoint(repoRoot, options) {
|
|
|
138
179
|
gitHeadSha,
|
|
139
180
|
statusPorcelainSha256,
|
|
140
181
|
changedPaths,
|
|
182
|
+
...(stableGovernanceInputs ? { stableGovernanceInputs } : {}),
|
|
141
183
|
fingerprint,
|
|
142
184
|
};
|
|
143
185
|
}
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
主会话(含 openCode、Cursor Chat、其他宿主 agent)= **operator-only**;skills 与本文件是纪律文档,**不能**替代 `task.json` / DAG `writeSet` / runtime 执法。
|
|
30
30
|
|
|
31
31
|
| 类别 | 规则 |
|
|
32
|
-
|
|
32
|
+
| --- | --- |
|
|
33
33
|
| **允许** | 已发布 `loop-agent` / `agent-worker` CLI;只读 status/doctor/report/inspect/observe;准备 `source/*` 与 `task.json` 边界;human gate;shell 验证与 handoff。 |
|
|
34
34
|
| **禁止** | 绕过 CLI 用宿主 Edit/Write/ApplyPatch 直接改业务实现;CLI/DAG 失败后「救火改文件」;用聊天自述代替 shell 验证。 |
|
|
35
35
|
| **失败时** | `dag doctor` / `dag report` / `dag reconcile-run`(及适用 worker reconcile);修正任务源/`task.json`/DAG 后经 CLI 重跑。 |
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
### 自然语言入口路由
|
|
41
41
|
|
|
42
42
|
| 用户表达 | 入口 | 执行动作 |
|
|
43
|
-
|
|
43
|
+
| --- | --- | --- |
|
|
44
44
|
| loop-agent 初始化 / loop agent 初始化 / loop agent初始化 / 初始化 loop-agent | 初始化 | 完成确定性初始化闭环 |
|
|
45
45
|
| 初始化更新校验 / 检查初始化更新 / loop-agent 初始化更新校验 / loop agent初始化更新校验 | 更新校验 | 只读报告,不写入 |
|
|
46
46
|
| 初始化对齐 / 升级后对齐 / reconcile 初始化 / loop-agent 初始化对齐 | 升级对齐 | 自动应用确定性安全动作;surface 缺失、人工决策、活跃 DAG/Worker 或 Worker 状态无法确认时零写入 |
|
|
@@ -127,7 +127,7 @@ agent-worker console serve --repo . --port 8790 # 兼容入口,等价于上
|
|
|
127
127
|
|
|
128
128
|
live run 先用 `loop-agent dag status --run-id <run-id>` 看 lifecycle 与 liveness;用 `loop-agent dag report --run-id <run-id> --markdown` 读 facts;失败/paused 用 `loop-agent dag doctor --run-id <run-id> --markdown`。生命周期对齐先只读运行 `loop-agent dag reconcile-run --run-id <run-id>`;只有 runner 已停止且 operator 明确提供 `--action supersede|abandon --reason "..."` 时才允许变更。失败 run 用 `loop-agent dag closeout-draft --run-id <run-id>` 生成 failure handoff,不要写成成功 closeout。
|
|
129
129
|
|
|
130
|
-
Runner heartbeat 只证明 lease
|
|
130
|
+
Operator 须持续监控 live run,直到 controller 报告 run 已结束(节点/流程终态如 `FINISHED`、`FAILED` 或 `partial_failed`),或 Decision Gate **需要 approve**;不要在节点仍运行时假定完成。判活须组合 runner heartbeat、session events 与 `dag doctor` liveness/provider meaningful progress;Runner heartbeat 只证明 lease,alone ≠ progress,不得仅凭运行时长结束节点。exclusive writer(如 `implement-pi` / `repair-pi`)运行期间:主会话与其他 writer **不得并发修改工作区**,以免 write-guard 错误归因;只读 status/doctor/report/observe 与 approve/reject/resume CLI 仍允许。恢复:status/doctor/report → classify → reconcile/replan → CLI 重跑 → shell verify。**禁止**把主会话直接 Edit 业务代码当作恢复手段。
|
|
131
131
|
|
|
132
132
|
### 运行态与验证
|
|
133
133
|
|
package/harness.json
CHANGED
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"pi": {
|
|
60
60
|
"description": "Pi planning, review, diagnosis, and bounded writing when DAG toolProfile=write",
|
|
61
61
|
"LOW": "minimax-m3",
|
|
62
|
-
"MED":
|
|
62
|
+
"MED": "grok-4.5",
|
|
63
63
|
"HIGH": "gpt-5.6-sol"
|
|
64
64
|
}
|
|
65
65
|
}
|