@tea-agent/loop-agent 0.42.0-next.14 → 0.42.0-next.16
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 +19 -3
- package/dist/application/task-lifecycle/advance.js +9 -4
- package/dist/build-stamp.json +3 -3
- package/dist/commands/task-source-prepare.js +3 -1
- package/dist/executors/dag-pi-executor.js +1437 -154
- package/dist/executors/shell-executor.js +107 -35
- package/dist/shared/dag-failure-category.js +6 -0
- package/dist/task/contract/apply.js +36 -2
- package/dist/task/source-prepare/parse-intent.js +7 -0
- package/dist/worker/console/chat/pi-runtime.js +45 -2
- package/dist/worker/console/chat/resource-loader.js +4 -1
- package/dist/worker/console/chat/routes.js +8 -0
- package/dist/worker/console/chat/subagents/agent-tool.js +62 -0
- package/dist/worker/console/chat/subagents/explore-agent.js +169 -0
- package/dist/worker/console/chat/subagents/index.js +5 -0
- package/dist/worker/console/chat/subagents/orchestrator.js +273 -0
- package/dist/worker/console/chat/subagents/tool-policy.js +53 -0
- package/dist/worker/console/chat/subagents/types.js +13 -0
- package/dist/worker/console/chat/tool-preview.js +10 -0
- package/dist/worker/console/chat/tools.js +11 -1
- package/dist/worker/console/interview/tools.js +1 -0
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-BGMbAdAG.js → abnfDiagram-N423BO3Z-CgXb0EVO.js} +1 -1
- package/dist/worker/console/static/assets/{arc-CJu1LND8.js → arc-DN59MZqN.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-BmJh9gMQ.js → architectureDiagram-T3A2C74G-BUk3sWpn.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-BWjaX3ZF.js → blockDiagram-VBNYF7ZC-IH-cPBFE.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-CLEOAxLB.js → c4Diagram-5PPSVZJV-BJWf1nGo.js} +1 -1
- package/dist/worker/console/static/assets/channel-i1DjpDIw.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-CydnyQUX.js → chunk-2GRJ4B5K-BCPb5H0y.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-tjJ1IoZ1.js → chunk-2Q5K7J3B-Cfs3jeW2.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DR358Phd.js → chunk-5RXB4S5H-BhQI1B_T.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-B-LuWFZD.js → chunk-5VM5RSS4-Cc_m4gyV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-FG53fA6v.js → chunk-6Q2QTUOP-JPqDS3IV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-Bkt-w8SZ.js → chunk-GF5L2VYU-AeGX6EAg.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-WxHo0z2I.js → chunk-JWPE2WC7-pEyTjekV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-CqHrBJhp.js → chunk-KBJHAD2P-Bi5UopKd.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-D4MVae_f.js → chunk-RYQCIY6F-FkGFJxgQ.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CamBh0Ll.js → chunk-XXDRQBXY-D-7LGrV2.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DgDRdE1V.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DgDRdE1V.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-C3qheCuW.js → cose-bilkent-JH36ORCC-CCEDNDaX.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-DQlWfkS7.js → cynefin-VYW2F7L2-DDd-t7fm.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-BAxa9rFb.js → cynefinDiagram-MW4NZA55-LWe4Ikzr.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-YM09nLJw.js → dagre-VZM6K2ZE-BBCZScc-.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-I4W9EJj2.js → diagram-7IWD3JNH-B6liRVig.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-DH8eJ8Ff.js → diagram-B4RE2ZJO-DO_hIrpW.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-D0D0ntDW.js → diagram-LBJQPF4R-dmCy93uR.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BIISKq1s.js → diagram-Q27KOJAE-D6mFTIxP.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3--xyAWj0w.js → diagram-UB23O5K3-DCteVUfY.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-B3sYXLJ4.js → ebnfDiagram-BXEA7PRR-DTv530VK.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-zRSYWhJP.js → erDiagram-JOGREHBK-DCXDMxCr.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-BUYlie03.js → flowDiagram-UKHOOZJN-BwEBLOJh.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DP4kzzrM.js → ganttDiagram-PKOTCBZU-Ck-Vymjm.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-CAPoUxTF.js → gitGraphDiagram-DS77QQ5N-Hqs6X_3L.js} +1 -1
- package/dist/worker/console/static/assets/{index-CWhTyxvc.js → index-BmMi-Bve.js} +71 -71
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-BXPfjekV.js → infoDiagram-6WML65LV-Cjc6M9eg.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-aOI3pV6t.js → ishikawaDiagram-WSZJBQD7-CPchYZMl.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DAPZuvi4.js → journeyDiagram-NVQOT4AX-BABdJNNC.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-atwDzL3C.js → kanban-definition-27J2QSJJ-1oXhbM4j.js} +1 -1
- package/dist/worker/console/static/assets/{linear-D5E09qji.js → linear-PTmQ9LkV.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-CE4idY-s.js → mermaid.core-B6Lxil0V.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-CbZxtPca.js → mindmap-definition-FAOFIHXS-BUqjNGEw.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-De-FYEte.js → pegDiagram-VL7TDLO6-8D1N-orJ.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-Bs0USobz.js → pieDiagram-7S7Q4E2Y-BlPMN9d9.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-Bq3ZrGCJ.js → quadrantDiagram-CIZ2JOQS-BIL5YDes.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Bk8ojyIj.js → railroadDiagram-AXF67PYL-Dqmil3ie.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-CGhoyws1.js → requirementDiagram-LRYGKXZP-C_-Dp4mH.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-DmVSQs3b.js → sankeyDiagram-W5VNT64P-C0s5-bQS.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-C80h1u0Q.js → sequenceDiagram-SI44F4Z6-DiVCHPfi.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-CcbksQVZ.js → sizeCapture-X5ZJPWSS-CxQ9WVvx.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-CFTTTQKj.js → stateDiagram-OKZ733FA-D9mFHA-Y.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BqXQxfFC.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-Cw7Jr7Tg.js → swimlanes-SLNWSIFB-DDnC5l-M.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CXwWNRJl.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CVcdTVa5.js → timeline-definition-Z64GVDOM-C8QySPC-.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BpLbmWp9.js → vennDiagram-T6HMQDX7-BeYdDiz6.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-6PXfbwj-.js → wardleyDiagram-T6FBY63Y-D8hjFWLH.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-Bjkcz4ih.js → xychartDiagram-ELKLHX3M-ClqG_JGK.js} +1 -1
- package/dist/worker/console/static/index.html +1 -1
- package/dist/worker/console/static-src/operator-chat/tools-catalog.js +21 -2
- package/dist/workflows/dag/dag-retry-schema.js +3 -0
- package/dist/workflows/dag/frontend-contract-facts.js +130 -0
- package/dist/workflows/dag/frontend-design-policy.js +100 -15
- package/dist/workflows/dag/frontend-implementation-contract.js +56 -8
- package/dist/workflows/dag/frontend-plan-render.js +13 -2
- package/dist/workflows/dag/frontend-risk.js +2 -0
- package/dist/workflows/dag/frontend-shadow-dual-write.js +17 -1
- package/dist/workflows/dag/frontend-shape.js +16 -6
- package/dist/workflows/dag/frontend-test-execution-evidence.js +48 -0
- package/dist/workflows/dag/frontend-typed-event-store.js +3 -0
- package/dist/workflows/dag/frontend-verification-trace.js +32 -0
- package/dist/workflows/dag/init-hybrid.js +16 -13
- package/dist/workflows/dag/node-execution.js +178 -88
- package/dist/workflows/dag/rerun-feedback.js +1 -0
- package/dist/workflows/dag/rerun-plan.js +90 -4
- package/dist/workflows/dag/rerun-run.js +7 -0
- package/dist/workflows/dag/retry-policy.js +16 -10
- package/dist/workflows/dag/runner-exit-diagnostics.js +125 -0
- package/dist/workflows/dag/runner.js +67 -7
- package/dist/workflows/dag/structured-output-repair.js +4 -1
- package/dist/workflows/dag/validate.js +10 -8
- package/dist/workflows/dag/workspace-checkpoint.js +66 -0
- package/docs/operations/README.md +1 -0
- package/docs/templates/agent-dag.schema.json +2 -2
- package/docs/templates/frontend-implementation-contract.schema.json +34 -2
- package/package.json +2 -3
- package/skills/frontend-plan/SKILL.md +14 -1
- package/skills/frontend-plan/references/decision-contract.md +84 -10
- package/skills/frontend-plan/references/design-decisions.md +32 -0
- package/dist/worker/console/static/assets/channel-DOsaUk1k.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-D4-D49vY.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-D4-D49vY.js +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-C4YCesmi.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-lN2U_DLl.js +0 -8
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import { createHash } from "node:crypto";
|
|
2
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
+
import { withFrontendTestReporter, parseFrontendTestExecutionReport } from "../workflows/dag/frontend-test-execution-evidence.js";
|
|
3
4
|
import { appendFileSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
4
5
|
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
5
6
|
import path from "node:path";
|
|
@@ -1950,6 +1951,23 @@ async function executeFrontendVerificationBundle(input, meta) {
|
|
|
1950
1951
|
const bundle = shell.frontendVerificationBundle;
|
|
1951
1952
|
const cwd = resolveShellCwd(input.cwd, shell.cwd);
|
|
1952
1953
|
const results = [];
|
|
1954
|
+
const commandResultsByKey = new Map();
|
|
1955
|
+
const commandKey = (command) => `${cwd}\u0000${command.trim()}`;
|
|
1956
|
+
const reuseCommandResult = (result, command) => {
|
|
1957
|
+
const reusedAt = new Date().toISOString();
|
|
1958
|
+
return {
|
|
1959
|
+
...result,
|
|
1960
|
+
command,
|
|
1961
|
+
durationMs: 0,
|
|
1962
|
+
wallDurationMs: 0,
|
|
1963
|
+
executionDurationMs: result.executionDurationMs ?? result.durationMs,
|
|
1964
|
+
startedAt: reusedAt,
|
|
1965
|
+
finishedAt: reusedAt,
|
|
1966
|
+
stdoutArtifactPath: undefined,
|
|
1967
|
+
stderrArtifactPath: undefined,
|
|
1968
|
+
reused: true,
|
|
1969
|
+
};
|
|
1970
|
+
};
|
|
1953
1971
|
let beforeStatus;
|
|
1954
1972
|
try {
|
|
1955
1973
|
beforeStatus = await readGitStatusPorcelain(input.cwd);
|
|
@@ -1958,22 +1976,32 @@ async function executeFrontendVerificationBundle(input, meta) {
|
|
|
1958
1976
|
beforeStatus = undefined;
|
|
1959
1977
|
}
|
|
1960
1978
|
const lintResults = [];
|
|
1979
|
+
const successfulLabels = new Map();
|
|
1980
|
+
const successfulCommandTexts = new Map();
|
|
1981
|
+
const executedTests = new Map();
|
|
1982
|
+
const commandExecutionEvidence = new Map();
|
|
1961
1983
|
for (const command of bundle.lintCommands ?? []) {
|
|
1962
|
-
const
|
|
1963
|
-
const
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1984
|
+
const key = commandKey(command);
|
|
1985
|
+
const cached = commandResultsByKey.get(key);
|
|
1986
|
+
let result = cached ? reuseCommandResult(cached, command) : undefined;
|
|
1987
|
+
if (!result) {
|
|
1988
|
+
const commandNumber = results.length + 1;
|
|
1989
|
+
result = await executeShellCommand({
|
|
1990
|
+
command,
|
|
1991
|
+
cwd,
|
|
1992
|
+
timeoutMs: shell.timeoutMs ?? DEFAULT_SHELL_TIMEOUT_MS,
|
|
1993
|
+
envAllowlist: shell.envAllowlist,
|
|
1994
|
+
dagRunMeta: { runDir: meta.runDir, runId: meta.runId },
|
|
1995
|
+
reportActivity: input.reportActivity,
|
|
1996
|
+
outputArtifacts: {
|
|
1997
|
+
stdoutPath: path.join(meta.runDir, input.task.id, "commands", `${commandNumber}.stdout.txt`),
|
|
1998
|
+
stderrPath: path.join(meta.runDir, input.task.id, "commands", `${commandNumber}.stderr.txt`),
|
|
1999
|
+
},
|
|
2000
|
+
});
|
|
2001
|
+
commandResultsByKey.set(key, result);
|
|
2002
|
+
}
|
|
1976
2003
|
results.push(result);
|
|
2004
|
+
lintResults.push(result);
|
|
1977
2005
|
}
|
|
1978
2006
|
let lintAssessment;
|
|
1979
2007
|
if ((bundle.lintCommands?.length ?? 0) > 0 &&
|
|
@@ -2007,30 +2035,64 @@ async function executeFrontendVerificationBundle(input, meta) {
|
|
|
2007
2035
|
labels: bundle.behaviorEvidence.commandLabels,
|
|
2008
2036
|
},
|
|
2009
2037
|
];
|
|
2010
|
-
const
|
|
2038
|
+
const failedGroups = new Set();
|
|
2039
|
+
const groupFailures = [];
|
|
2011
2040
|
for (const group of lintBlocked ? [] : groups) {
|
|
2012
2041
|
for (const [index, command] of group.commands.entries()) {
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2042
|
+
if (failedGroups.has(group.name))
|
|
2043
|
+
break;
|
|
2044
|
+
const key = commandKey(command);
|
|
2045
|
+
const cached = commandResultsByKey.get(key);
|
|
2046
|
+
let result = cached ? reuseCommandResult(cached, command) : undefined;
|
|
2047
|
+
if (!result) {
|
|
2048
|
+
const commandNumber = results.length + 1;
|
|
2049
|
+
const reportPath = path.join(meta.runDir, input.task.id, "commands", `${commandNumber}-${randomUUID()}.vitest.json`);
|
|
2050
|
+
const observedCommand = await withFrontendTestReporter({ command, cwd, reportPath });
|
|
2051
|
+
if (observedCommand)
|
|
2052
|
+
await mkdir(path.dirname(reportPath), { recursive: true });
|
|
2053
|
+
result = await executeShellCommand({
|
|
2054
|
+
command: observedCommand ?? command,
|
|
2055
|
+
cwd,
|
|
2056
|
+
timeoutMs: shell.timeoutMs ?? DEFAULT_SHELL_TIMEOUT_MS,
|
|
2057
|
+
envAllowlist: shell.envAllowlist,
|
|
2058
|
+
dagRunMeta: { runDir: meta.runDir, runId: meta.runId },
|
|
2059
|
+
reportActivity: input.reportActivity,
|
|
2060
|
+
outputArtifacts: {
|
|
2061
|
+
stdoutPath: path.join(meta.runDir, input.task.id, "commands", `${commandNumber}.stdout.txt`),
|
|
2062
|
+
stderrPath: path.join(meta.runDir, input.task.id, "commands", `${commandNumber}.stderr.txt`),
|
|
2063
|
+
},
|
|
2064
|
+
});
|
|
2065
|
+
let reportError;
|
|
2066
|
+
if (observedCommand && result.ok) {
|
|
2067
|
+
try {
|
|
2068
|
+
commandExecutionEvidence.set(key, parseFrontendTestExecutionReport(JSON.parse(await readFile(reportPath, "utf8")), cwd));
|
|
2069
|
+
}
|
|
2070
|
+
catch (error) {
|
|
2071
|
+
reportError = `frontend test execution report unavailable: ${error instanceof Error ? error.message : String(error)}`;
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
if (reportError) {
|
|
2075
|
+
result = { ...result, ok: false, failureCategory: "invalid-output", stderr: reportError };
|
|
2076
|
+
}
|
|
2077
|
+
commandResultsByKey.set(key, result);
|
|
2078
|
+
}
|
|
2026
2079
|
results.push(result);
|
|
2027
2080
|
if (result.ok && group.labels[index]) {
|
|
2081
|
+
const observations = commandExecutionEvidence.get(key);
|
|
2082
|
+
if (observations !== undefined)
|
|
2083
|
+
executedTests.set(group.labels[index], observations);
|
|
2028
2084
|
const labels = successfulLabels.get(group.name) ?? [];
|
|
2029
2085
|
labels.push(group.labels[index]);
|
|
2030
2086
|
successfulLabels.set(group.name, labels);
|
|
2087
|
+
const texts = successfulCommandTexts.get(group.name) ?? [];
|
|
2088
|
+
texts.push(command);
|
|
2089
|
+
successfulCommandTexts.set(group.name, texts);
|
|
2031
2090
|
}
|
|
2032
|
-
if (!result.ok)
|
|
2091
|
+
if (!result.ok) {
|
|
2092
|
+
groupFailures.push(result);
|
|
2093
|
+
failedGroups.add(group.name);
|
|
2033
2094
|
break;
|
|
2095
|
+
}
|
|
2034
2096
|
}
|
|
2035
2097
|
}
|
|
2036
2098
|
if (beforeStatus !== undefined) {
|
|
@@ -2060,12 +2122,15 @@ async function executeFrontendVerificationBundle(input, meta) {
|
|
|
2060
2122
|
exitCode: result.exitCode,
|
|
2061
2123
|
failureCategory: result.failureCategory,
|
|
2062
2124
|
command: result.command,
|
|
2125
|
+
reused: result.reused === true,
|
|
2063
2126
|
}));
|
|
2127
|
+
// Lint baseline-debt is tolerated, so a lint-only failure must not become the
|
|
2128
|
+
// verification failure. A shared lint/static or lint/behavior command is
|
|
2129
|
+
// different: once reused by a non-lint lane, its failure belongs to that lane
|
|
2130
|
+
// and must remain the primary failure evidence.
|
|
2064
2131
|
const firstFailure = lintBlocked
|
|
2065
2132
|
? lintResults.find((result) => !result.ok)
|
|
2066
|
-
:
|
|
2067
|
-
.filter((result) => !lintResults.includes(result))
|
|
2068
|
-
.find((result) => !result.ok);
|
|
2133
|
+
: groupFailures[0];
|
|
2069
2134
|
const lintSyntheticFailure = lintBlocked && !firstFailure
|
|
2070
2135
|
? {
|
|
2071
2136
|
failureCategory: "invalid-output",
|
|
@@ -2082,7 +2147,7 @@ async function executeFrontendVerificationBundle(input, meta) {
|
|
|
2082
2147
|
mock: {
|
|
2083
2148
|
nodeId: input.task.id,
|
|
2084
2149
|
commandLabels: successfulLabels.get("mock") ?? [],
|
|
2085
|
-
commandTexts:
|
|
2150
|
+
commandTexts: successfulCommandTexts.get("mock") ?? [],
|
|
2086
2151
|
},
|
|
2087
2152
|
static: {
|
|
2088
2153
|
nodeId: input.task.id,
|
|
@@ -2091,6 +2156,7 @@ async function executeFrontendVerificationBundle(input, meta) {
|
|
|
2091
2156
|
behavior: {
|
|
2092
2157
|
nodeId: input.task.id,
|
|
2093
2158
|
commandLabels: successfulLabels.get("behavior") ?? [],
|
|
2159
|
+
executedTests,
|
|
2094
2160
|
},
|
|
2095
2161
|
},
|
|
2096
2162
|
});
|
|
@@ -2123,7 +2189,9 @@ async function executeFrontendVerificationBundle(input, meta) {
|
|
|
2123
2189
|
nodeId: failedNodeId,
|
|
2124
2190
|
rawFailureCategory: failureCategory,
|
|
2125
2191
|
});
|
|
2126
|
-
const failureOwner = classifiedOwner === "unknown"
|
|
2192
|
+
const failureOwner = classifiedOwner === "unknown" &&
|
|
2193
|
+
["typecheck", "build", "lint", "component-test", "unit-test", "trace"].includes(failureClass)
|
|
2194
|
+
? "implementation" : classifiedOwner;
|
|
2127
2195
|
const restartPhase = classifyRestartPhase(failureOwner);
|
|
2128
2196
|
// A+B (AC-008): verification-config routing. Produce structured
|
|
2129
2197
|
// unresolved entrypoint refs from committed plan facts and the frozen
|
|
@@ -2603,6 +2671,10 @@ async function executeFrontendDesignPolicy(input, meta) {
|
|
|
2603
2671
|
[],
|
|
2604
2672
|
allowedDependencies: config.allowedDependencies,
|
|
2605
2673
|
writeSetPatterns: config.implementationWriteSet ?? [],
|
|
2674
|
+
// Fresh-existence evidence at gate time (Scout pathEvidence.fresh
|
|
2675
|
+
// semantics): a reuse-existing evidencePath must name a file that
|
|
2676
|
+
// exists NOW; greenfield paths are decision=new only.
|
|
2677
|
+
hasFreshPathEvidence: (candidate) => existsSync(path.resolve(input.cwd, candidate)),
|
|
2606
2678
|
});
|
|
2607
2679
|
// 8. Write canonical contract + policy result.
|
|
2608
2680
|
const contractArtifact = await writeFrontendImplementationContractArtifact({
|
|
@@ -63,6 +63,12 @@ const RAW_TO_NORMALIZED = {
|
|
|
63
63
|
// compaction / fresh context before retrying — an executor-side
|
|
64
64
|
// environment condition, not a plan-quality defect.
|
|
65
65
|
"context-overflow": "executor",
|
|
66
|
+
// stopReason=length output-capacity truncation: same executor-side
|
|
67
|
+
// capacity class as context-overflow. Legacy thinking-exhausted labels
|
|
68
|
+
// (pre-output-limit artifacts) map to the same class.
|
|
69
|
+
"output-limit": "executor",
|
|
70
|
+
"writer-thinking-exhausted": "executor",
|
|
71
|
+
"planner-thinking-exhausted": "executor",
|
|
66
72
|
// Deterministic capability/policy refusal: missing verified launcher JS
|
|
67
73
|
// entry, capability allowlist rejection, or missing bounded write tool
|
|
68
74
|
// profile. These are environment/config defects the operator repairs
|
|
@@ -147,7 +147,7 @@ export async function applyTaskContract(input) {
|
|
|
147
147
|
ref,
|
|
148
148
|
changedFiles: [],
|
|
149
149
|
idempotentReplay: true,
|
|
150
|
-
txId: existing.txId,
|
|
150
|
+
...(existing.txId ? { txId: existing.txId } : { noOp: true }),
|
|
151
151
|
};
|
|
152
152
|
}
|
|
153
153
|
const lock = await acquireTaskContractLock({
|
|
@@ -175,7 +175,9 @@ export async function applyTaskContract(input) {
|
|
|
175
175
|
ref,
|
|
176
176
|
changedFiles: [],
|
|
177
177
|
idempotentReplay: true,
|
|
178
|
-
|
|
178
|
+
...(existingUnderLock.txId
|
|
179
|
+
? { txId: existingUnderLock.txId }
|
|
180
|
+
: { noOp: true }),
|
|
179
181
|
};
|
|
180
182
|
}
|
|
181
183
|
const state = await observeTaskContractState(input.repoRoot, input.taskId);
|
|
@@ -251,6 +253,38 @@ export async function applyTaskContract(input) {
|
|
|
251
253
|
sourceHashes,
|
|
252
254
|
taskConfigSha256,
|
|
253
255
|
});
|
|
256
|
+
// Content-level no-op: when the freshly projected draft hashes
|
|
257
|
+
// identically to both the recorded contract and the on-disk observation,
|
|
258
|
+
// the journaled commit would only churn revision/updatedAt metadata and
|
|
259
|
+
// force downstream consumers (writeSet gate digest binds
|
|
260
|
+
// contractRevision+contractCanonicalHash) to re-issue approvals for
|
|
261
|
+
// zero content change. Return the current ref untouched instead. A
|
|
262
|
+
// drifted disk (observed !== ref) never reaches this short-circuit: the
|
|
263
|
+
// apply is then a repair projection and must run.
|
|
264
|
+
if (state.ref &&
|
|
265
|
+
canonicalHash === state.ref.canonicalHash &&
|
|
266
|
+
state.observedCanonicalHash === state.ref.canonicalHash) {
|
|
267
|
+
await assertLockHeld(lock);
|
|
268
|
+
await appendRequestLedgerEntry({
|
|
269
|
+
repoRoot: input.repoRoot,
|
|
270
|
+
taskId: input.taskId,
|
|
271
|
+
entry: {
|
|
272
|
+
schemaVersion: 1,
|
|
273
|
+
requestId: input.requestId,
|
|
274
|
+
requestPayloadSha256: input.requestPayloadSha256,
|
|
275
|
+
revision: state.ref.revision,
|
|
276
|
+
canonicalHash: state.ref.canonicalHash,
|
|
277
|
+
recordedAt: new Date().toISOString(),
|
|
278
|
+
resultSummary: { ok: true, operation: "apply" },
|
|
279
|
+
},
|
|
280
|
+
});
|
|
281
|
+
return {
|
|
282
|
+
ref: state.ref,
|
|
283
|
+
changedFiles: [],
|
|
284
|
+
idempotentReplay: false,
|
|
285
|
+
noOp: true,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
254
288
|
const nextRevision = currentRevision + 1;
|
|
255
289
|
const nextRef = {
|
|
256
290
|
schemaVersion: TASK_CONTRACT_REF_SCHEMA_VERSION,
|
|
@@ -13,6 +13,13 @@ const HEADING_ALIASES = {
|
|
|
13
13
|
"需求范围",
|
|
14
14
|
"已确认范围",
|
|
15
15
|
"已确认需求",
|
|
16
|
+
// Common zh-CN frontend-PRD variants seen in real PRDs; without these a
|
|
17
|
+
// raw-PRD fallback parse (semantic intake unavailable) yields an empty
|
|
18
|
+
// scope and flips the import to incomplete (EMPTY scope deadlock).
|
|
19
|
+
"目标表面",
|
|
20
|
+
"交付范围",
|
|
21
|
+
"实现范围",
|
|
22
|
+
"交付内容",
|
|
16
23
|
]),
|
|
17
24
|
nonGoals: new Set(["非目标", "out of scope", "non-goals", "non goals"]),
|
|
18
25
|
acceptance: new Set([
|
|
@@ -32,7 +32,8 @@ import { extractAssistantContentBlocks, extractAssistantText, isAssistantSdkMess
|
|
|
32
32
|
import { parseProviderError, } from "./provider-error.js";
|
|
33
33
|
import { projectCompactSnapshot, } from "./chat-event-store.js";
|
|
34
34
|
import { extractUsageSample } from "./usage.js";
|
|
35
|
-
import { OPERATOR_CHAT_ALLOWED_TOOLS, authorizeOperatorChatTool, assertNoWriteToolInList, } from "./tools.js";
|
|
35
|
+
import { OPERATOR_CHAT_ALLOWED_TOOLS, OPERATOR_CHAT_BUILTIN_CUSTOM_TOOLS, authorizeOperatorChatTool, assertNoWriteToolInList, } from "./tools.js";
|
|
36
|
+
import { buildAgentCustomTool, ExploreOrchestrator, } from "./subagents/index.js";
|
|
36
37
|
import { createOperatorChatResourceLoader, } from "./resource-loader.js";
|
|
37
38
|
import { applySkillAutoInvocationPreferences, isAutoInvocationEnabled, loadResourcePreferences, skillResourceId, } from "./resource-preferences-store.js";
|
|
38
39
|
import { buildModelCallableToolSchemas } from "./tool-adapter.js";
|
|
@@ -115,6 +116,7 @@ export function OPERATOR_CHAT_ACTIVE_TOOL_NAMES() {
|
|
|
115
116
|
...OPERATOR_CHAT_ALLOWED_TOOLS,
|
|
116
117
|
...OPERATOR_CHAT_BUILTIN_EXPLORE_TOOLS,
|
|
117
118
|
...OPERATOR_CHAT_SAFE_EXPLORE_TOOL_IDS,
|
|
119
|
+
...OPERATOR_CHAT_BUILTIN_CUSTOM_TOOLS,
|
|
118
120
|
];
|
|
119
121
|
}
|
|
120
122
|
/**
|
|
@@ -1024,14 +1026,33 @@ export class ConsolePiRuntime {
|
|
|
1024
1026
|
compactionControllers = new Map();
|
|
1025
1027
|
siftStates = new Map();
|
|
1026
1028
|
/** Session-less tool definition caches (no AgentSession state). */
|
|
1027
|
-
/** Session-less tool definition caches (no AgentSession state). */
|
|
1028
1029
|
operatorCustomToolsPromise;
|
|
1029
1030
|
exploreCustomToolsPromise;
|
|
1031
|
+
exploreOrchestrator;
|
|
1030
1032
|
bindings;
|
|
1031
1033
|
constructor(options) {
|
|
1032
1034
|
this.options = options;
|
|
1033
1035
|
this.loader = createOperatorChatResourceLoader();
|
|
1034
1036
|
this.bindings = options.bindings ?? createDefaultPiSdkBindings();
|
|
1037
|
+
this.exploreOrchestrator = new ExploreOrchestrator({
|
|
1038
|
+
cwd: this.options.cwd,
|
|
1039
|
+
bindings: this.bindings,
|
|
1040
|
+
loadSafeExploreCustomTools: () => this.loadExploreCustomTools(),
|
|
1041
|
+
// Fallback when the parent session's model is unknown at Agent-call
|
|
1042
|
+
// time (e.g. turn started before materialization finished): resolve
|
|
1043
|
+
// the default Chat model from the child services' modelRuntime.
|
|
1044
|
+
resolveModel: async (modelRuntime) => {
|
|
1045
|
+
const available = this.bindings.listAvailableModels({ modelRuntime });
|
|
1046
|
+
const descriptor = await resolveDefaultChatModel(this.options.cwd, available);
|
|
1047
|
+
if (!descriptor)
|
|
1048
|
+
return undefined;
|
|
1049
|
+
return this.bindings.resolveModel({
|
|
1050
|
+
modelRuntime,
|
|
1051
|
+
provider: descriptor.provider,
|
|
1052
|
+
modelId: descriptor.modelId,
|
|
1053
|
+
});
|
|
1054
|
+
},
|
|
1055
|
+
});
|
|
1035
1056
|
}
|
|
1036
1057
|
/** Absolute path for repo-scoped resource preferences (may be undefined in unit tests). */
|
|
1037
1058
|
get resourcePreferencesPath() {
|
|
@@ -1526,12 +1547,31 @@ export class ConsolePiRuntime {
|
|
|
1526
1547
|
const goalTools = this.options.goalRegistry
|
|
1527
1548
|
? await buildGoalTools({ sessionId, registry: this.options.goalRegistry })
|
|
1528
1549
|
: [];
|
|
1550
|
+
const agentTool = await buildAgentCustomTool({
|
|
1551
|
+
parentSessionId: sessionId,
|
|
1552
|
+
orchestrator: this.exploreOrchestrator,
|
|
1553
|
+
resolveModel: () => {
|
|
1554
|
+
const model = this.sessionModels.get(sessionId);
|
|
1555
|
+
const modelRuntime = this.modelRuntimes.get(sessionId);
|
|
1556
|
+
if (!model || !modelRuntime)
|
|
1557
|
+
return undefined;
|
|
1558
|
+
return this.bindings.resolveModel({
|
|
1559
|
+
modelRuntime,
|
|
1560
|
+
provider: model.provider,
|
|
1561
|
+
modelId: model.modelId,
|
|
1562
|
+
});
|
|
1563
|
+
},
|
|
1564
|
+
}).catch((error) => {
|
|
1565
|
+
process.stderr.write(`[console] chat Agent tool build failed: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
1566
|
+
return undefined;
|
|
1567
|
+
});
|
|
1529
1568
|
return [
|
|
1530
1569
|
...operatorCustomTools,
|
|
1531
1570
|
...exploreCustomTools,
|
|
1532
1571
|
...(questionTool ? [questionTool] : []),
|
|
1533
1572
|
...(todoTool ? [todoTool] : []),
|
|
1534
1573
|
...goalTools,
|
|
1574
|
+
...(agentTool ? [agentTool] : []),
|
|
1535
1575
|
];
|
|
1536
1576
|
}
|
|
1537
1577
|
loadOperatorCustomTools() {
|
|
@@ -2595,6 +2635,7 @@ export class ConsolePiRuntime {
|
|
|
2595
2635
|
catch {
|
|
2596
2636
|
// ignore
|
|
2597
2637
|
}
|
|
2638
|
+
void this.exploreOrchestrator.abortByParent(sessionId);
|
|
2598
2639
|
};
|
|
2599
2640
|
try {
|
|
2600
2641
|
if (options?.turnId && this.options.questionRegistry) {
|
|
@@ -2970,10 +3011,12 @@ export class ConsolePiRuntime {
|
|
|
2970
3011
|
this.thinkingSelections.delete(sessionId);
|
|
2971
3012
|
this.reloadInFlight.delete(sessionId);
|
|
2972
3013
|
this.pendingSteering.delete(sessionId);
|
|
3014
|
+
void this.exploreOrchestrator.abortByParent(sessionId);
|
|
2973
3015
|
}
|
|
2974
3016
|
disposeAll() {
|
|
2975
3017
|
for (const id of [...this.sessions.keys()])
|
|
2976
3018
|
this.dispose(id);
|
|
3019
|
+
void this.exploreOrchestrator.abortAll();
|
|
2977
3020
|
}
|
|
2978
3021
|
}
|
|
2979
3022
|
function mapAssistantMessageEvent(type, sessionId, message) {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* The three-gate authorization (authorizeOperatorChatTool) is the actual
|
|
16
16
|
* enforcement; this adapter describes what that gate admits.
|
|
17
17
|
*/
|
|
18
|
-
import { OPERATOR_CHAT_ALLOWED_TOOLS, OPERATOR_CHAT_DENIED_TOOLS, OPERATOR_CHAT_DENIED_OPERATOR_ACTIONS, OPERATOR_CHAT_PI_BUILTIN_TOOLS, authorizeOperatorChatTool, filterOperatorChatTools, assertNoWriteToolInList, } from "./tools.js";
|
|
18
|
+
import { OPERATOR_CHAT_ALLOWED_TOOLS, OPERATOR_CHAT_DENIED_TOOLS, OPERATOR_CHAT_DENIED_OPERATOR_ACTIONS, OPERATOR_CHAT_PI_BUILTIN_TOOLS, OPERATOR_CHAT_BUILTIN_CUSTOM_TOOLS, authorizeOperatorChatTool, filterOperatorChatTools, assertNoWriteToolInList, } from "./tools.js";
|
|
19
19
|
/** Built-in Pi tool names allowed in every Chat session (ADR 0011). */
|
|
20
20
|
export const OPERATOR_CHAT_BUILTIN_EXPLORE_TOOL_IDS = Object.freeze([
|
|
21
21
|
...OPERATOR_CHAT_PI_BUILTIN_TOOLS,
|
|
@@ -42,6 +42,9 @@ export function createOperatorChatResourceLoader() {
|
|
|
42
42
|
if (explore) {
|
|
43
43
|
return { ok: true, toolId };
|
|
44
44
|
}
|
|
45
|
+
if (OPERATOR_CHAT_BUILTIN_CUSTOM_TOOLS.includes(toolId)) {
|
|
46
|
+
return { ok: true, toolId };
|
|
47
|
+
}
|
|
45
48
|
const decision = authorizeOperatorChatTool(toolId);
|
|
46
49
|
if (decision.ok)
|
|
47
50
|
active.add(decision.toolId);
|
|
@@ -517,6 +517,14 @@ export async function handleChatCapabilities(_req, res, deps) {
|
|
|
517
517
|
deniedTools: OPERATOR_CHAT_DENIED_TOOLS,
|
|
518
518
|
deniedOperatorActions: OPERATOR_CHAT_DENIED_OPERATOR_ACTIONS,
|
|
519
519
|
tools: toolSchemas,
|
|
520
|
+
builtinSubagents: [
|
|
521
|
+
{
|
|
522
|
+
toolId: "Agent",
|
|
523
|
+
agent: "Explore",
|
|
524
|
+
kind: "subagent",
|
|
525
|
+
description: "Start the built-in read-only Explore subagent for codebase search, symbol lookup, and call-chain tracing.",
|
|
526
|
+
},
|
|
527
|
+
],
|
|
520
528
|
// ADR 0011: full Pi tools including bash are default-active.
|
|
521
529
|
hasBash: true,
|
|
522
530
|
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parent-session Agent custom tool. First slice: Explore only, one-shot.
|
|
3
|
+
*/
|
|
4
|
+
import { AGENT_TOOL_DESCRIPTION, AGENT_TOOL_PROMPT_GUIDELINES, AGENT_TOOL_PROMPT_SNIPPET, parseExploreAgentParams, } from "./explore-agent.js";
|
|
5
|
+
import { OPERATOR_CHAT_AGENT_TOOL_NAME } from "./types.js";
|
|
6
|
+
function textResult(payload, isError = false) {
|
|
7
|
+
return {
|
|
8
|
+
content: [{ type: "text", text: JSON.stringify(payload) }],
|
|
9
|
+
details: payload,
|
|
10
|
+
...(isError ? { isError: true } : {}),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export async function buildAgentCustomTool(options) {
|
|
14
|
+
const [{ Type }, { defineTool }] = await Promise.all([
|
|
15
|
+
import("typebox"),
|
|
16
|
+
import("@earendil-works/pi-coding-agent"),
|
|
17
|
+
]);
|
|
18
|
+
return defineTool({
|
|
19
|
+
name: OPERATOR_CHAT_AGENT_TOOL_NAME,
|
|
20
|
+
label: "Explore",
|
|
21
|
+
description: AGENT_TOOL_DESCRIPTION,
|
|
22
|
+
promptSnippet: AGENT_TOOL_PROMPT_SNIPPET,
|
|
23
|
+
promptGuidelines: [...AGENT_TOOL_PROMPT_GUIDELINES],
|
|
24
|
+
executionMode: "parallel",
|
|
25
|
+
parameters: Type.Object({
|
|
26
|
+
description: Type.String({
|
|
27
|
+
description: "Short 3-5 word summary shown on the tool card",
|
|
28
|
+
}),
|
|
29
|
+
prompt: Type.String({
|
|
30
|
+
description: "Complete Explore brief. Include goal, known clues, exclusions, success criteria, and thoroughness (quick | medium | very thorough).",
|
|
31
|
+
}),
|
|
32
|
+
subagent_type: Type.Optional(Type.String({
|
|
33
|
+
description: "Built-in agent name. Omit or set Explore.",
|
|
34
|
+
})),
|
|
35
|
+
}, { additionalProperties: false }),
|
|
36
|
+
async execute(_toolCallId, params, signal) {
|
|
37
|
+
const parsed = parseExploreAgentParams(params);
|
|
38
|
+
if (!parsed.ok) {
|
|
39
|
+
const failure = {
|
|
40
|
+
schemaVersion: 1,
|
|
41
|
+
kind: "subagent",
|
|
42
|
+
agent: "Explore",
|
|
43
|
+
status: "failed",
|
|
44
|
+
description: typeof params.description === "string" ? params.description : "",
|
|
45
|
+
error: parsed.message,
|
|
46
|
+
runId: "",
|
|
47
|
+
};
|
|
48
|
+
return textResult({
|
|
49
|
+
...failure,
|
|
50
|
+
code: parsed.code,
|
|
51
|
+
}, true);
|
|
52
|
+
}
|
|
53
|
+
const result = await options.orchestrator.launch({
|
|
54
|
+
parentSessionId: options.parentSessionId,
|
|
55
|
+
request: parsed.request,
|
|
56
|
+
signal,
|
|
57
|
+
model: options.resolveModel?.(),
|
|
58
|
+
});
|
|
59
|
+
return textResult(result, result.status !== "completed");
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}
|