@tea-agent/loop-agent 0.42.0-next.9 → 0.43.0-next.1
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 +65 -59
- package/dist/application/dag/run-dag.js +3 -2
- package/dist/application/task-lifecycle/advance.js +9 -4
- package/dist/build-stamp.json +3 -3
- package/dist/cli/program.js +1 -1
- package/dist/commands/dag-rerun-task.js +2 -0
- package/dist/commands/task-source-prepare.js +3 -1
- package/dist/executors/dag-pi-executor.js +1514 -175
- package/dist/executors/pi-extension-resolver.js +14 -2
- package/dist/executors/shell-executor.js +131 -54
- 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/chat-event-store.js +4 -2
- 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 +28 -12
- package/dist/worker/console/chat/sdd-data-alignment.js +13 -2
- package/dist/worker/console/chat/session-store.js +5 -1
- 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/chat/turn-process.js +1 -0
- package/dist/worker/console/interview/tools.js +1 -0
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C6n9_m0P.js → abnfDiagram-N423BO3Z-B0Q0nClf.js} +1 -1
- package/dist/worker/console/static/assets/{arc-DQh-IfZ1.js → arc-DCPjC19G.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-54NnrwUC.js → architectureDiagram-T3A2C74G-CP5n9jmG.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-pivRALGK.js → blockDiagram-VBNYF7ZC-CmaWi_Wg.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BR7OV2NJ.js → c4Diagram-5PPSVZJV-Ct5tcMfZ.js} +1 -1
- package/dist/worker/console/static/assets/channel-DAS07MdS.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-B1Aq6BcK.js → chunk-2GRJ4B5K-C0osm1Zf.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-CbdU0rjo.js → chunk-2Q5K7J3B-CDWviED7.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-Dqi2GJeD.js → chunk-5RXB4S5H-mtSk1-j7.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BYn1Hu4R.js → chunk-5VM5RSS4-D5J9PHC7.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DRLjDL0k.js → chunk-6Q2QTUOP-Begg4WAa.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CY9Xx2jV.js → chunk-GF5L2VYU-sF1AEy7T.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BNCZs7_z.js → chunk-JWPE2WC7-BqbsWXZb.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DZ8AStLL.js → chunk-KBJHAD2P-BKJrPcJ6.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DsxjYrzz.js → chunk-RYQCIY6F-CcwNMRho.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-Ddx1KC1l.js → chunk-XXDRQBXY-BEJi3iIs.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Cwk-5jiW.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Cwk-5jiW.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-W9TveCnK.js → cose-bilkent-JH36ORCC-DRLgTvVu.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-l9j_ztZH.js → cynefin-VYW2F7L2-BoAVG3cs.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-BMJMKi4G.js → cynefinDiagram-MW4NZA55-Bzol06hR.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-Bb4mG9pH.js → dagre-VZM6K2ZE-UmliJM77.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-BMgL_Qv0.js → diagram-7IWD3JNH-BWLcJkFo.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-Bvo2T4OQ.js → diagram-B4RE2ZJO-CgApOm-O.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-_5kWRN9c.js → diagram-LBJQPF4R-Bh3RgTLs.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-DqdMrltM.js → diagram-Q27KOJAE-Dsh-D5nE.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-CDDYsNkp.js → diagram-UB23O5K3-Dkbbpcpb.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-c4nfnZUV.js → ebnfDiagram-BXEA7PRR-3EBaA3t3.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DnkUcNMs.js → erDiagram-JOGREHBK-Bq2Dc5ok.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-Dz0KGLZE.js → flowDiagram-UKHOOZJN-De7y55ha.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-Cj1t1uka.js → ganttDiagram-PKOTCBZU-fLiDbQRh.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-tp2FrBHd.js → gitGraphDiagram-DS77QQ5N-Du617mp1.js} +1 -1
- package/dist/worker/console/static/assets/index-C0O48S_P.js +449 -0
- package/dist/worker/console/static/assets/index-CzKf4U8P.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DQwJS8DH.js → infoDiagram-6WML65LV-BwmlF-XP.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-eimCQWD4.js → ishikawaDiagram-WSZJBQD7-zsGtS59u.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-Dd9Gbwgv.js → journeyDiagram-NVQOT4AX-DTTPTe3f.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-Qve3cyft.js → kanban-definition-27J2QSJJ-BkXg9aP-.js} +1 -1
- package/dist/worker/console/static/assets/{linear-UXKSe36Z.js → linear-7U2ue5IE.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-CWhj4JXN.js → mermaid.core-BUuGHmWO.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-yTcwf4SJ.js → mindmap-definition-FAOFIHXS-7q6fX0Sv.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-_7qToaZP.js → pegDiagram-VL7TDLO6-CkQPJN55.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-CXmrKmDm.js → pieDiagram-7S7Q4E2Y-CCal_tfX.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-hMmrJyaS.js → quadrantDiagram-CIZ2JOQS-B7bpxyAZ.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BXw8tCBe.js → railroadDiagram-AXF67PYL-CJmww7D-.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BmjgnLZl.js → requirementDiagram-LRYGKXZP-D6ldWJEv.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Bj77SPpN.js → sankeyDiagram-W5VNT64P-VGh33I9e.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-B2FDVysL.js → sequenceDiagram-SI44F4Z6-CCkDrIjU.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-UChNY9ZZ.js → sizeCapture-X5ZJPWSS-NC8f5Otb.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-wbAEkbd7.js → stateDiagram-OKZ733FA-_92ezZdF.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DyJg5gzW.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DCEkU8HR.js → swimlanes-SLNWSIFB-BdUCGtbP.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-B1cw_0Uy.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-BGp7H06U.js → timeline-definition-Z64GVDOM-CdD1H8ia.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DsCIQuWR.js → vennDiagram-T6HMQDX7-Dkdk3oFo.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-cRZLP4Xe.js → wardleyDiagram-T6FBY63Y-CKu2uPPY.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CP0xLR9Y.js → xychartDiagram-ELKLHX3M-ClqAnuG8.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/chat-view-types.js +1 -1
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +23 -0
- package/dist/worker/console/static-src/operator-chat/pending-user-message.js +32 -0
- package/dist/worker/console/static-src/operator-chat/tools-catalog.js +21 -2
- package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +2 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +2 -0
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +30 -14
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +133 -5
- package/dist/worker/console/static-src/shell/workspace-route.js +4 -0
- package/dist/worker/observe/static/constants.js +22 -22
- package/dist/worker/observe/static/dag-context-reason-labels.js +19 -0
- package/dist/worker/observe/static/dag-history-labels.js +1 -0
- package/dist/worker/observe/static/dag-inspector-humanize.d.ts +16 -0
- package/dist/worker/observe/static/dag-inspector-humanize.js +339 -0
- package/dist/worker/observe/static/dag-node-purpose.js +10 -10
- package/dist/worker/observe/static/index.html +4 -4
- package/dist/worker/observe/static/prompt-restart-candidates.js +4 -2
- package/dist/worker/observe/static/relations.js +5 -5
- package/dist/worker/observe/static/styles.css +171 -0
- package/dist/worker/observe/static/views/dag-graph.js +1 -1
- package/dist/worker/observe/static/views/dag-inspector.js +329 -174
- package/dist/worker/observe/static/views/dag.js +16 -12
- package/dist/worker/observe/static/views/session-timeline.js +5 -3
- package/dist/workflows/dag/backend-test-plan-protocol.js +104 -0
- 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 +101 -16
- package/dist/workflows/dag/frontend-implementation-contract.js +410 -41
- package/dist/workflows/dag/frontend-plan-render.js +13 -2
- package/dist/workflows/dag/frontend-prewrite-gate.js +1 -1
- package/dist/workflows/dag/frontend-recovery-controller.js +25 -1
- package/dist/workflows/dag/frontend-recovery-plan.js +4 -1
- package/dist/workflows/dag/frontend-recovery-run.js +142 -16
- 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 +38 -4
- package/dist/workflows/dag/frontend-writer-admission.js +2 -2
- package/dist/workflows/dag/init-hybrid.js +57 -28
- package/dist/workflows/dag/node-execution.js +201 -85
- package/dist/workflows/dag/recovery-lease.js +106 -16
- 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 +28 -6
- package/dist/workflows/dag/rerun-task.js +221 -18
- package/dist/workflows/dag/retry-policy.js +27 -10
- package/dist/workflows/dag/runner-exit-diagnostics.js +125 -0
- package/dist/workflows/dag/runner.js +220 -18
- package/dist/workflows/dag/structured-output-repair.js +4 -1
- package/dist/workflows/dag/types.js +4 -3
- 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/backend-test-dag.json +6 -4
- package/docs/templates/frontend-implementation-contract.schema.json +34 -2
- package/harness.json +1 -1
- package/package.json +4 -5
- package/skills/frontend-plan/SKILL.md +14 -1
- package/skills/frontend-plan/references/decision-contract.md +101 -5
- package/skills/frontend-plan/references/design-decisions.md +32 -0
- package/dist/worker/console/static/assets/channel-DsZxrgqe.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-CuToPeGV.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-CuToPeGV.js +0 -1
- package/dist/worker/console/static/assets/index-D9Sc0f0y.js +0 -449
- package/dist/worker/console/static/assets/index-DDQc5a50.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-yPoT19ft.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-VWdarchG.js +0 -8
|
@@ -62,9 +62,21 @@ function readSettingsPackages(settingsPath) {
|
|
|
62
62
|
return undefined;
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
-
/**
|
|
65
|
+
/**
|
|
66
|
+
* Strip an `npm:` prefix and a trailing `@<version>`/`@<dist-tag>` suffix
|
|
67
|
+
* from a settings package spec, e.g. `npm:pi-lens@3.8.72` → `pi-lens`,
|
|
68
|
+
* `npm:@scope/pkg@1.0.0` → `@scope/pkg`. The leading `@` of a scoped
|
|
69
|
+
* package name is preserved (the version separator is the LAST `@` at a
|
|
70
|
+
* positive index). Mirrors the console-side spec parsing semantics so a
|
|
71
|
+
* version-pinned install (e.g. via `pi install npm:pi-lens@3.8.72`) resolves
|
|
72
|
+
* identically in both surfaces.
|
|
73
|
+
*/
|
|
66
74
|
export function packageNameFromSpec(spec) {
|
|
67
|
-
|
|
75
|
+
const base = spec.startsWith("npm:") ? spec.slice(4) : spec;
|
|
76
|
+
const at = base.lastIndexOf("@");
|
|
77
|
+
if (at > 0)
|
|
78
|
+
return base.slice(0, at);
|
|
79
|
+
return base;
|
|
68
80
|
}
|
|
69
81
|
function loadPackageInventories(repoRoot) {
|
|
70
82
|
const agentDir = getPiAgentDir();
|
|
@@ -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";
|
|
@@ -15,7 +16,7 @@ import { buildRequirementCoverageGateShellCommand, expandShellPreset, buildVerdi
|
|
|
15
16
|
import { materializeBackendTestAnalysisContract } from "../workflows/dag/backend-test-analysis-contract.js";
|
|
16
17
|
import { extractBackendTestContractEnvelope } from "../workflows/dag/backend-test-contract-envelope.js";
|
|
17
18
|
import { materializeFrontendImplementationContract } from "../workflows/dag/frontend-implementation-contract.js";
|
|
18
|
-
import { assertFrontendSourceBindingFresh, buildContractFromCommittedFacts, deterministicSha256, frontendImplementationContractSchema, serializeCompactDeterministicJson, sha256Hex, writeFrontendImplementationContractArtifact, } from "../workflows/dag/frontend-implementation-contract.js";
|
|
19
|
+
import { assertFrontendSourceBindingFresh, buildContractFromCommittedFacts, deterministicSha256, frontendImplementationContractSchema, serializeCompactDeterministicJson, sha256Hex, writeFrontendImplementationContractArtifact, buildFrontendVerifyCommandDirectory, } from "../workflows/dag/frontend-implementation-contract.js";
|
|
19
20
|
import { checkFrontendSourceFidelityLedger } from "../workflows/dag/frontend-source-fidelity-ledger.js";
|
|
20
21
|
import { materializeFrontendTestResult, validateFrontendCaseEvidence, } from "../workflows/dag/frontend-test-result-contract.js";
|
|
21
22
|
import { copyFrontendTestStandardScenarios } from "../workflows/dag/frontend-test-standard-scenarios.js";
|
|
@@ -1907,11 +1908,12 @@ async function executePipelineCommands(input, meta, overrideCommands) {
|
|
|
1907
1908
|
return results;
|
|
1908
1909
|
}
|
|
1909
1910
|
/** A+B (AC-008): derive unresolved verification entrypoints from committed plan
|
|
1910
|
-
* facts. A verification target is unresolved when its
|
|
1911
|
-
* match any frozen command
|
|
1912
|
-
* Returns `{ ref, digest }` entries; a missing/unreadable
|
|
1913
|
-
* closed to [] so the router restarts the contract instead
|
|
1914
|
-
|
|
1911
|
+
* facts. A verification target is unresolved when its commandId does not
|
|
1912
|
+
* match any frozen command directory entry derived from the verify shell
|
|
1913
|
+
* evidence bundle. Returns `{ ref, digest }` entries; a missing/unreadable
|
|
1914
|
+
* plan ledger fails closed to [] so the router restarts the contract instead
|
|
1915
|
+
* of inventing refs. */
|
|
1916
|
+
async function deriveUnresolvedVerificationEntrypointRefs(runDir, frozenCommandIds) {
|
|
1915
1917
|
try {
|
|
1916
1918
|
const { restorePlanPatchFromCommittedFacts } = await import("../workflows/dag/frontend-shadow-dual-write.js");
|
|
1917
1919
|
const records = await readTypedEventStoreFromJsonl(path.join(runDir, "frontend-plan-pi", "plan-typed-facts.jsonl"));
|
|
@@ -1920,15 +1922,13 @@ async function deriveUnresolvedVerificationEntrypointRefs(runDir, frozenCommandL
|
|
|
1920
1922
|
?.verificationTargets;
|
|
1921
1923
|
if (!Array.isArray(targets))
|
|
1922
1924
|
return [];
|
|
1923
|
-
const frozen = new Set(
|
|
1924
|
-
.map((label) => label.trim().toLowerCase())
|
|
1925
|
-
.filter(Boolean));
|
|
1925
|
+
const frozen = new Set(frozenCommandIds.filter(Boolean));
|
|
1926
1926
|
const refs = [];
|
|
1927
1927
|
for (const item of targets) {
|
|
1928
1928
|
const target = item;
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1929
|
+
// v2 plan facts carry commandId as the stable directory key.
|
|
1930
|
+
const commandId = typeof target.commandId === "string" ? target.commandId : "";
|
|
1931
|
+
if (commandId && frozen.has(commandId)) {
|
|
1932
1932
|
continue;
|
|
1933
1933
|
}
|
|
1934
1934
|
const file = typeof target.file === "string" ? target.file : "";
|
|
@@ -1951,6 +1951,23 @@ async function executeFrontendVerificationBundle(input, meta) {
|
|
|
1951
1951
|
const bundle = shell.frontendVerificationBundle;
|
|
1952
1952
|
const cwd = resolveShellCwd(input.cwd, shell.cwd);
|
|
1953
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
|
+
};
|
|
1954
1971
|
let beforeStatus;
|
|
1955
1972
|
try {
|
|
1956
1973
|
beforeStatus = await readGitStatusPorcelain(input.cwd);
|
|
@@ -1959,22 +1976,32 @@ async function executeFrontendVerificationBundle(input, meta) {
|
|
|
1959
1976
|
beforeStatus = undefined;
|
|
1960
1977
|
}
|
|
1961
1978
|
const lintResults = [];
|
|
1979
|
+
const successfulLabels = new Map();
|
|
1980
|
+
const successfulCommandTexts = new Map();
|
|
1981
|
+
const executedTests = new Map();
|
|
1982
|
+
const commandExecutionEvidence = new Map();
|
|
1962
1983
|
for (const command of bundle.lintCommands ?? []) {
|
|
1963
|
-
const
|
|
1964
|
-
const
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
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
|
+
}
|
|
1977
2003
|
results.push(result);
|
|
2004
|
+
lintResults.push(result);
|
|
1978
2005
|
}
|
|
1979
2006
|
let lintAssessment;
|
|
1980
2007
|
if ((bundle.lintCommands?.length ?? 0) > 0 &&
|
|
@@ -2008,30 +2035,64 @@ async function executeFrontendVerificationBundle(input, meta) {
|
|
|
2008
2035
|
labels: bundle.behaviorEvidence.commandLabels,
|
|
2009
2036
|
},
|
|
2010
2037
|
];
|
|
2011
|
-
const
|
|
2038
|
+
const failedGroups = new Set();
|
|
2039
|
+
const groupFailures = [];
|
|
2012
2040
|
for (const group of lintBlocked ? [] : groups) {
|
|
2013
2041
|
for (const [index, command] of group.commands.entries()) {
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
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
|
+
}
|
|
2027
2079
|
results.push(result);
|
|
2028
2080
|
if (result.ok && group.labels[index]) {
|
|
2081
|
+
const observations = commandExecutionEvidence.get(key);
|
|
2082
|
+
if (observations !== undefined)
|
|
2083
|
+
executedTests.set(group.labels[index], observations);
|
|
2029
2084
|
const labels = successfulLabels.get(group.name) ?? [];
|
|
2030
2085
|
labels.push(group.labels[index]);
|
|
2031
2086
|
successfulLabels.set(group.name, labels);
|
|
2087
|
+
const texts = successfulCommandTexts.get(group.name) ?? [];
|
|
2088
|
+
texts.push(command);
|
|
2089
|
+
successfulCommandTexts.set(group.name, texts);
|
|
2032
2090
|
}
|
|
2033
|
-
if (!result.ok)
|
|
2091
|
+
if (!result.ok) {
|
|
2092
|
+
groupFailures.push(result);
|
|
2093
|
+
failedGroups.add(group.name);
|
|
2034
2094
|
break;
|
|
2095
|
+
}
|
|
2035
2096
|
}
|
|
2036
2097
|
}
|
|
2037
2098
|
if (beforeStatus !== undefined) {
|
|
@@ -2061,12 +2122,15 @@ async function executeFrontendVerificationBundle(input, meta) {
|
|
|
2061
2122
|
exitCode: result.exitCode,
|
|
2062
2123
|
failureCategory: result.failureCategory,
|
|
2063
2124
|
command: result.command,
|
|
2125
|
+
reused: result.reused === true,
|
|
2064
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.
|
|
2065
2131
|
const firstFailure = lintBlocked
|
|
2066
2132
|
? lintResults.find((result) => !result.ok)
|
|
2067
|
-
:
|
|
2068
|
-
.filter((result) => !lintResults.includes(result))
|
|
2069
|
-
.find((result) => !result.ok);
|
|
2133
|
+
: groupFailures[0];
|
|
2070
2134
|
const lintSyntheticFailure = lintBlocked && !firstFailure
|
|
2071
2135
|
? {
|
|
2072
2136
|
failureCategory: "invalid-output",
|
|
@@ -2083,7 +2147,7 @@ async function executeFrontendVerificationBundle(input, meta) {
|
|
|
2083
2147
|
mock: {
|
|
2084
2148
|
nodeId: input.task.id,
|
|
2085
2149
|
commandLabels: successfulLabels.get("mock") ?? [],
|
|
2086
|
-
commandTexts:
|
|
2150
|
+
commandTexts: successfulCommandTexts.get("mock") ?? [],
|
|
2087
2151
|
},
|
|
2088
2152
|
static: {
|
|
2089
2153
|
nodeId: input.task.id,
|
|
@@ -2092,6 +2156,7 @@ async function executeFrontendVerificationBundle(input, meta) {
|
|
|
2092
2156
|
behavior: {
|
|
2093
2157
|
nodeId: input.task.id,
|
|
2094
2158
|
commandLabels: successfulLabels.get("behavior") ?? [],
|
|
2159
|
+
executedTests,
|
|
2095
2160
|
},
|
|
2096
2161
|
},
|
|
2097
2162
|
});
|
|
@@ -2124,7 +2189,9 @@ async function executeFrontendVerificationBundle(input, meta) {
|
|
|
2124
2189
|
nodeId: failedNodeId,
|
|
2125
2190
|
rawFailureCategory: failureCategory,
|
|
2126
2191
|
});
|
|
2127
|
-
const failureOwner = classifiedOwner === "unknown"
|
|
2192
|
+
const failureOwner = classifiedOwner === "unknown" &&
|
|
2193
|
+
["typecheck", "build", "lint", "component-test", "unit-test", "trace"].includes(failureClass)
|
|
2194
|
+
? "implementation" : classifiedOwner;
|
|
2128
2195
|
const restartPhase = classifyRestartPhase(failureOwner);
|
|
2129
2196
|
// A+B (AC-008): verification-config routing. Produce structured
|
|
2130
2197
|
// unresolved entrypoint refs from committed plan facts and the frozen
|
|
@@ -2135,12 +2202,18 @@ async function executeFrontendVerificationBundle(input, meta) {
|
|
|
2135
2202
|
// never re-classifies the same signal twice.
|
|
2136
2203
|
let verificationConfigRoute;
|
|
2137
2204
|
if (failureOwner === "verification-config") {
|
|
2138
|
-
const
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2205
|
+
const verifyDirectory = buildFrontendVerifyCommandDirectory({
|
|
2206
|
+
staticLabels: bundle.staticEvidence.commandLabels ?? [],
|
|
2207
|
+
behaviorLabels: bundle.behaviorEvidence.commandLabels ?? [],
|
|
2208
|
+
mockLabels: bundle.mockEvidence?.commandLabels ?? [],
|
|
2209
|
+
lintLabels: bundle.lintEvidence?.commandLabels ?? [],
|
|
2210
|
+
staticCommandTexts: bundle.staticEvidence.commandTexts ?? [],
|
|
2211
|
+
behaviorCommandTexts: bundle.behaviorEvidence.commandTexts ?? [],
|
|
2212
|
+
mockCommandTexts: bundle.mockEvidence?.commandTexts ?? [],
|
|
2213
|
+
lintCommandTexts: bundle.lintEvidence?.commandTexts ?? [],
|
|
2214
|
+
});
|
|
2215
|
+
const frozenCommandLabels = verifyDirectory.map((entry) => entry.label);
|
|
2216
|
+
const unresolvedEntrypointRefs = await deriveUnresolvedVerificationEntrypointRefs(meta.runDir, verifyDirectory.map((entry) => entry.commandId));
|
|
2144
2217
|
verificationConfigRoute = routeVerificationConfig({
|
|
2145
2218
|
unresolvedEntrypointRefs: unresolvedEntrypointRefs.map((ref) => ref.ref),
|
|
2146
2219
|
});
|
|
@@ -2531,7 +2604,7 @@ async function executeFrontendDesignPolicy(input, meta) {
|
|
|
2531
2604
|
};
|
|
2532
2605
|
}
|
|
2533
2606
|
const uncoveredVTs = contract.verificationTargets
|
|
2534
|
-
.filter((vt) => vt.
|
|
2607
|
+
.filter((vt) => vt.mode !== "static")
|
|
2535
2608
|
.filter((vt) => ![...writeSet].some((pattern) => pathMatchesPattern(vt.file, pattern) || vt.file === pattern));
|
|
2536
2609
|
if (uncoveredVTs.length > 0) {
|
|
2537
2610
|
return {
|
|
@@ -2598,6 +2671,10 @@ async function executeFrontendDesignPolicy(input, meta) {
|
|
|
2598
2671
|
[],
|
|
2599
2672
|
allowedDependencies: config.allowedDependencies,
|
|
2600
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)),
|
|
2601
2678
|
});
|
|
2602
2679
|
// 8. Write canonical contract + policy result.
|
|
2603
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([
|
|
@@ -22,14 +22,16 @@ import path from "node:path";
|
|
|
22
22
|
import { ensureSecureDir, writeSecureJsonSync, } from "../../../infrastructure/console/app-data.js";
|
|
23
23
|
import { isTerminalOperationState, } from "../../../infrastructure/console/operation-store.js";
|
|
24
24
|
import { scrubSecrets } from "./explore-tools.js";
|
|
25
|
-
|
|
25
|
+
/** Bounded preview stored on the event ring / SSE (not the live tool payload). */
|
|
26
|
+
export const OPERATION_PREVIEW_MAX_CHARS = 4000;
|
|
27
|
+
const OPERATION_PREVIEW_TRUNCATION_SUFFIX = "…[truncated]";
|
|
26
28
|
function boundedRedactedText(value, maxChars = OPERATION_PREVIEW_MAX_CHARS) {
|
|
27
29
|
if (typeof value !== "string" || !value.trim())
|
|
28
30
|
return undefined;
|
|
29
31
|
const { scrubbed } = scrubSecrets(value);
|
|
30
32
|
return scrubbed.length <= maxChars
|
|
31
33
|
? scrubbed
|
|
32
|
-
: `${scrubbed.slice(0, maxChars)}
|
|
34
|
+
: `${scrubbed.slice(0, maxChars)}${OPERATION_PREVIEW_TRUNCATION_SUFFIX}`;
|
|
33
35
|
}
|
|
34
36
|
function safeIdentity(value) {
|
|
35
37
|
return boundedRedactedText(value, 240);
|
|
@@ -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
|
});
|
|
@@ -851,6 +859,7 @@ export async function handleGetChatSession(_req, res, deps, sessionId) {
|
|
|
851
859
|
}
|
|
852
860
|
}
|
|
853
861
|
await deps.operationLinker.recoverSession(sessionId);
|
|
862
|
+
const messagePage = await deps.store.listMessages(sessionId, { limit: 50 });
|
|
854
863
|
const composerDraft = await new ComposerDraftStore(deps.appData).get(sessionId);
|
|
855
864
|
const snapshot = deps.events.snapshot(sessionId);
|
|
856
865
|
const telemetry = deriveChatSessionTelemetry(snapshot);
|
|
@@ -862,7 +871,7 @@ export async function handleGetChatSession(_req, res, deps, sessionId) {
|
|
|
862
871
|
eventEpoch: deps.events.epoch(sessionId),
|
|
863
872
|
session: {
|
|
864
873
|
...projectBrowserChatSession(record),
|
|
865
|
-
messages: projectStoredMessagesWithEventOrder(
|
|
874
|
+
messages: projectStoredMessagesWithEventOrder(messagePage.messages, snapshot),
|
|
866
875
|
composerDraft,
|
|
867
876
|
activeTurnId: activeTurn?.turnId,
|
|
868
877
|
turnPhase: activeTurn
|
|
@@ -874,6 +883,10 @@ export async function handleGetChatSession(_req, res, deps, sessionId) {
|
|
|
874
883
|
todos: projectCurrentTodos(snapshot) ?? [],
|
|
875
884
|
goal: deps.goalRegistry?.get(sessionId) ?? null,
|
|
876
885
|
},
|
|
886
|
+
history: {
|
|
887
|
+
hasMore: messagePage.hasMore,
|
|
888
|
+
...(messagePage.nextCursor ? { nextCursor: messagePage.nextCursor } : {}),
|
|
889
|
+
},
|
|
877
890
|
hasBash: true,
|
|
878
891
|
sessionStats: telemetry.stats,
|
|
879
892
|
usage: telemetry.usage,
|
|
@@ -1134,19 +1147,18 @@ export async function handleSteerDeferredTurn(req, res, deps, sessionId, clientR
|
|
|
1134
1147
|
}
|
|
1135
1148
|
}
|
|
1136
1149
|
async function persistAcceptedSteering(deps, sessionId, turnId, userMessage, clientRequestId) {
|
|
1150
|
+
emitUserMessageEvent(deps, sessionId, turnId, userMessage, clientRequestId, true);
|
|
1151
|
+
await emitDeferredTurnUpdate(deps, sessionId, turnId, undefined, 0);
|
|
1152
|
+
}
|
|
1153
|
+
function emitUserMessageEvent(deps, sessionId, turnId, userMessage, clientRequestId, steering = false) {
|
|
1137
1154
|
deps.events.append(sessionId, turnId, {
|
|
1138
1155
|
kind: "user_message",
|
|
1139
1156
|
data: {
|
|
1140
|
-
clientRequestId,
|
|
1141
|
-
steering: true,
|
|
1142
|
-
message: {
|
|
1143
|
-
id: userMessage.id,
|
|
1144
|
-
text: userMessage.text,
|
|
1145
|
-
at: userMessage.at,
|
|
1146
|
-
},
|
|
1157
|
+
...(clientRequestId ? { clientRequestId } : {}),
|
|
1158
|
+
...(steering ? { steering: true } : {}),
|
|
1159
|
+
message: { id: userMessage.id, text: userMessage.text, at: userMessage.at },
|
|
1147
1160
|
},
|
|
1148
1161
|
});
|
|
1149
|
-
await emitDeferredTurnUpdate(deps, sessionId, turnId, undefined, 0);
|
|
1150
1162
|
}
|
|
1151
1163
|
/** Lightweight busy-truth endpoint for the client reconcile loop (pi-web
|
|
1152
1164
|
* get_state equivalent): exposes the active turn + the latest turn's durable
|
|
@@ -1690,7 +1702,8 @@ export async function handleChatPrompt(req, res, deps, sessionId) {
|
|
|
1690
1702
|
text,
|
|
1691
1703
|
images,
|
|
1692
1704
|
signal: ac.signal,
|
|
1693
|
-
onUserMessagePersisted: (record) => {
|
|
1705
|
+
onUserMessagePersisted: (record, userMessage) => {
|
|
1706
|
+
emitUserMessageEvent(deps, sessionId, turnId, userMessage);
|
|
1694
1707
|
const users = record.messages.filter((message) => message.role === "user");
|
|
1695
1708
|
if (users.length !== 1)
|
|
1696
1709
|
return;
|
|
@@ -2000,9 +2013,12 @@ export async function handleCreateChatTurn(req, res, deps, sessionId, trustedPay
|
|
|
2000
2013
|
sessionId,
|
|
2001
2014
|
turnId: turn.turnId,
|
|
2002
2015
|
text,
|
|
2016
|
+
clientRequestId,
|
|
2003
2017
|
images,
|
|
2004
2018
|
signal: controller.signal,
|
|
2005
2019
|
onUserMessagePersisted: async (record, userMessage) => {
|
|
2020
|
+
if (!trustedPayload)
|
|
2021
|
+
emitUserMessageEvent(deps, sessionId, turn.turnId, userMessage, clientRequestId);
|
|
2006
2022
|
await trustedPayload?.onUserMessagePersisted?.(turn.turnId, userMessage);
|
|
2007
2023
|
const users = record.messages.filter((message) => message.role === "user");
|
|
2008
2024
|
if (users.length !== 1)
|
|
@@ -2845,8 +2861,8 @@ async function handleSddBingo(req, res, deps, sessionId) {
|
|
|
2845
2861
|
if (Object.keys(body).some((key) => key !== "skillName")) {
|
|
2846
2862
|
throw new SddDataAlignmentError("SDD_INVALID_SKILL_NAME", "请求体只允许 skillName 字段", 400);
|
|
2847
2863
|
}
|
|
2848
|
-
await deps.sddDataAlignment.report(sessionId, body.skillName);
|
|
2849
|
-
sendJson(res, 200, { ok: true });
|
|
2864
|
+
const result = await deps.sddDataAlignment.report(sessionId, body.skillName);
|
|
2865
|
+
sendJson(res, 200, { ok: true, skillName: result.skillName });
|
|
2850
2866
|
}
|
|
2851
2867
|
catch (error) {
|
|
2852
2868
|
if (error instanceof SddDataAlignmentError) {
|