@tea-agent/loop-agent 0.43.0-next.1 → 0.43.0-next.11
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 +144 -9
- package/dist/application/dag/run-dag.js +5 -0
- package/dist/application/evaluation/budget.js +19 -1
- package/dist/application/task-lifecycle/advance.js +17 -4
- package/dist/application/task-lifecycle/observe.js +43 -29
- package/dist/application/task-lifecycle/plan-transitions.js +5 -4
- package/dist/build-stamp.json +3 -3
- package/dist/executors/dag-pi-executor.js +1237 -586
- package/dist/executors/pi-executor.js +22 -1
- package/dist/executors/pi-sdk-executor.js +140 -39
- package/dist/executors/shell-executor.js +8 -4
- package/dist/shared/frontend-execution-policy.js +23 -0
- package/dist/task/config-types.js +4 -0
- package/dist/task/source-prepare/ledger-reconciliation.js +2 -2
- package/dist/task/source-prepare/ledger-review.js +6 -9
- package/dist/task/source-prepare/semantic-intake.js +16 -26
- package/dist/task/source-prepare/source-fidelity-pi.js +28 -9
- package/dist/task/source-references.js +48 -23
- package/dist/worker/console/chat/assistant-content.js +23 -2
- package/dist/worker/console/chat/browser-policy.js +113 -0
- package/dist/worker/console/chat/browser-routes.js +140 -0
- package/dist/worker/console/chat/explore-tools.js +13 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/ask-user-question.js +44 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/explore-tools.js +202 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/goal-tools.js +79 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/operator-tools.js +86 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/todo-write.js +38 -0
- package/dist/worker/console/chat/pi-runtime/inspection-types.js +28 -0
- package/dist/worker/console/chat/pi-runtime/inspection.js +439 -0
- package/dist/worker/console/chat/pi-runtime/sdk-bindings.js +153 -0
- package/dist/worker/console/chat/pi-runtime/sdk-session.js +5 -0
- package/dist/worker/console/chat/pi-runtime.js +207 -1178
- package/dist/worker/console/chat/routes.js +208 -71
- package/dist/worker/console/chat/scm-routes.js +217 -0
- package/dist/worker/console/chat/scm-service.js +283 -0
- package/dist/worker/console/chat/scm-tools.js +111 -0
- package/dist/worker/console/chat/sdd-data-alignment.js +65 -9
- package/dist/worker/console/chat/session-catalog.js +32 -0
- package/dist/worker/console/chat/session-mode-view.js +49 -0
- package/dist/worker/console/chat/session-mode.js +219 -0
- package/dist/worker/console/chat/session-store.js +22 -6
- package/dist/worker/console/chat/shortcuts.js +6 -0
- package/dist/worker/console/chat/terminal-routes.js +216 -0
- package/dist/worker/console/chat/terminal-sessions.js +348 -0
- package/dist/worker/console/chat/terminal-tools.js +211 -0
- package/dist/worker/console/chat/tool-preview.js +11 -0
- package/dist/worker/console/chat/tools.js +2 -0
- package/dist/worker/console/server.js +2 -28
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-B0Q0nClf.js → abnfDiagram-N423BO3Z-CTSK0xbU.js} +1 -1
- package/dist/worker/console/static/assets/{arc-DCPjC19G.js → arc-_2MfctFP.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CP5n9jmG.js → architectureDiagram-T3A2C74G-DsDe2wTg.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-CmaWi_Wg.js → blockDiagram-VBNYF7ZC-Dtz_779U.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-Ct5tcMfZ.js → c4Diagram-5PPSVZJV-LaULi-gw.js} +1 -1
- package/dist/worker/console/static/assets/channel-CqS4yBWT.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-C0osm1Zf.js → chunk-2GRJ4B5K-DuHBqqX8.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-CDWviED7.js → chunk-2Q5K7J3B-BZgZs1dn.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-mtSk1-j7.js → chunk-5RXB4S5H-tzwmlIO_.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-D5J9PHC7.js → chunk-5VM5RSS4-CeYrLkSn.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-Begg4WAa.js → chunk-6Q2QTUOP-C92cgilm.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-sF1AEy7T.js → chunk-GF5L2VYU-XOXpGC0F.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BqbsWXZb.js → chunk-JWPE2WC7-CGHbJyCH.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BKJrPcJ6.js → chunk-KBJHAD2P-Bs2-LTzf.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CcwNMRho.js → chunk-RYQCIY6F-rN7up7E4.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-BEJi3iIs.js → chunk-XXDRQBXY-B_fWjKpR.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DVOxNOq0.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DVOxNOq0.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-DRLgTvVu.js → cose-bilkent-JH36ORCC-DE3SdJvB.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BoAVG3cs.js → cynefin-VYW2F7L2-B-4yuRAX.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Bzol06hR.js → cynefinDiagram-MW4NZA55-TiCPFuO8.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-UmliJM77.js → dagre-VZM6K2ZE-7crzLMzA.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-BWLcJkFo.js → diagram-7IWD3JNH-Cvr-x6Fd.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-CgApOm-O.js → diagram-B4RE2ZJO-BzcRHUjH.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-Bh3RgTLs.js → diagram-LBJQPF4R-BjkAXilh.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-Dsh-D5nE.js → diagram-Q27KOJAE-BkzyATK7.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-Dkbbpcpb.js → diagram-UB23O5K3-rZuTarYP.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-3EBaA3t3.js → ebnfDiagram-BXEA7PRR-CAYENaN1.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-Bq2Dc5ok.js → erDiagram-JOGREHBK-DCEam0Uq.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-De7y55ha.js → flowDiagram-UKHOOZJN-e_dH4wVO.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-fLiDbQRh.js → ganttDiagram-PKOTCBZU-CvZa2zOK.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Du617mp1.js → gitGraphDiagram-DS77QQ5N-CbT5TuKp.js} +1 -1
- package/dist/worker/console/static/assets/index-CUPeoE8W.js +468 -0
- package/dist/worker/console/static/assets/index-SAANIK8g.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-BwmlF-XP.js → infoDiagram-6WML65LV-BWzHQa_Z.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-zsGtS59u.js → ishikawaDiagram-WSZJBQD7-BTjlovtU.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DTTPTe3f.js → journeyDiagram-NVQOT4AX-DkvQoCc1.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BkXg9aP-.js → kanban-definition-27J2QSJJ-CHSzGi3W.js} +1 -1
- package/dist/worker/console/static/assets/{linear-7U2ue5IE.js → linear-DZMkTCWH.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-BUuGHmWO.js → mermaid.core-BIMhty7A.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-7q6fX0Sv.js → mindmap-definition-FAOFIHXS-Dx-tyMl3.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-CkQPJN55.js → pegDiagram-VL7TDLO6-C6G34rbY.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-CCal_tfX.js → pieDiagram-7S7Q4E2Y-CHK9XOfm.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-B7bpxyAZ.js → quadrantDiagram-CIZ2JOQS-CDARkI0w.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-CJmww7D-.js → railroadDiagram-AXF67PYL-DiuUe0ZQ.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-D6ldWJEv.js → requirementDiagram-LRYGKXZP-RE61UR56.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-VGh33I9e.js → sankeyDiagram-W5VNT64P-KDNLHMEG.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-CCkDrIjU.js → sequenceDiagram-SI44F4Z6-CeVgXnD9.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-NC8f5Otb.js → sizeCapture-X5ZJPWSS-CXwGyjjG.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-_92ezZdF.js → stateDiagram-OKZ733FA-BjTfozab.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BXoYsyr2.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-BdUCGtbP.js → swimlanes-SLNWSIFB-BaXOB8GT.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DfKliS4e.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CdD1H8ia.js → timeline-definition-Z64GVDOM-C3r2hqUU.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-Dkdk3oFo.js → vennDiagram-T6HMQDX7-MWKgl1S9.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CKu2uPPY.js → wardleyDiagram-T6FBY63Y-C9iFcZPk.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-ClqAnuG8.js → xychartDiagram-ELKLHX3M-DqnM24Jp.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/active-turn-tail.js +10 -0
- package/dist/worker/console/static-src/operator-chat/chat-link.js +94 -0
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +122 -10
- package/dist/worker/console/static-src/operator-chat/compaction-message.js +7 -0
- package/dist/worker/console/static-src/operator-chat/pending-user-message.js +27 -15
- package/dist/worker/console/static-src/operator-chat/process-label.js +41 -0
- package/dist/worker/console/static-src/operator-chat/turn-group-equality.js +13 -0
- package/dist/worker/console/static-src/operator-chat/turn-process-disclosure.js +10 -0
- package/dist/worker/console/static-src/operator-chat/use-searchable-hidden.js +51 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +44 -24
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +18 -2
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +41 -8
- package/dist/worker/console/static-src/operator-chat/useComposer.js +26 -11
- package/dist/worker/console/static-src/operator-chat/user-turn-anchor-equality.js +21 -0
- package/dist/worker/console/workspace-context.js +15 -1
- package/dist/worker/observe/node-transparency.js +81 -72
- package/dist/worker/observe/routes.js +20 -1
- package/dist/worker/observe/static/dag-inspector-humanize.js +3 -0
- package/dist/worker/observe/static/dom.js +20 -1
- package/dist/worker/observe/static/format-pool.d.ts +2 -0
- package/dist/worker/observe/static/format-pool.js +6 -0
- package/dist/worker/observe/static/format.js +7 -0
- package/dist/worker/observe/static/inspect-workspace.js +34 -7
- package/dist/worker/observe/static/inspector-submission.js +32 -0
- package/dist/worker/observe/static/kpi.js +1 -0
- package/dist/worker/observe/static/relations.js +2 -0
- package/dist/worker/observe/static/router.js +13 -0
- package/dist/worker/observe/static/run-processing.js +2 -0
- package/dist/worker/observe/static/shell-chrome.js +36 -3
- package/dist/worker/observe/static/state.js +35 -2
- package/dist/worker/observe/static/styles.css +263 -39
- package/dist/worker/observe/static/task-failure-labels.d.ts +4 -0
- package/dist/worker/observe/static/task-failure-labels.js +67 -0
- package/dist/worker/observe/static/task-history.js +12 -0
- package/dist/worker/observe/static/views/batch.js +6 -13
- package/dist/worker/observe/static/views/dag-graph.js +50 -3
- package/dist/worker/observe/static/views/dag-inspector.js +742 -268
- package/dist/worker/observe/static/views/dag-trajectory.js +3 -0
- package/dist/worker/observe/static/views/dag.d.ts +6 -0
- package/dist/worker/observe/static/views/dag.js +49 -11
- package/dist/worker/observe/static/views/dags.js +2 -0
- package/dist/worker/observe/static/views/dashboard.js +21 -12
- package/dist/worker/observe/static/views/failures.js +21 -11
- package/dist/worker/observe/static/views/feature.js +11 -29
- package/dist/worker/observe/static/views/pool.js +37 -28
- package/dist/worker/observe/static/views/run.js +48 -5
- package/dist/worker/observe/static/views/session-timeline.js +189 -240
- package/dist/worker/observe/static/views/task.js +81 -62
- package/dist/workflows/dag/budget-enforcement.js +53 -3
- package/dist/workflows/dag/frontend-capacity.js +9 -0
- package/dist/workflows/dag/frontend-closeout.js +1 -1
- package/dist/workflows/dag/frontend-design-policy.js +2 -1
- package/dist/workflows/dag/frontend-durable-tools.js +193 -0
- package/dist/workflows/dag/frontend-execution-groups.js +24 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +13 -0
- package/dist/workflows/dag/frontend-input-projection.js +76 -0
- package/dist/workflows/dag/frontend-plan-completeness.js +186 -0
- package/dist/workflows/dag/frontend-plan-recovery-policy.js +18 -0
- package/dist/workflows/dag/frontend-plan-render.js +10 -3
- package/dist/workflows/dag/frontend-prewrite-gate.js +3 -3
- package/dist/workflows/dag/frontend-recovery-controller.js +7 -7
- package/dist/workflows/dag/frontend-recovery-lineage.js +13 -0
- package/dist/workflows/dag/frontend-recovery-run.js +4 -0
- package/dist/workflows/dag/frontend-review-context.js +2 -1
- package/dist/workflows/dag/frontend-review-scopes.js +139 -0
- package/dist/workflows/dag/frontend-session-budget.js +249 -0
- package/dist/workflows/dag/frontend-shadow-dual-write.js +20 -2
- package/dist/workflows/dag/frontend-shape-facts.js +12 -2
- package/dist/workflows/dag/frontend-shape.js +30 -1
- package/dist/workflows/dag/frontend-test-execution-evidence.js +3 -2
- package/dist/workflows/dag/frontend-typed-event-store.js +11 -0
- package/dist/workflows/dag/frontend-verification-trace.js +1 -1
- package/dist/workflows/dag/init-hybrid.js +65 -167
- package/dist/workflows/dag/node-execution.js +38 -155
- package/dist/workflows/dag/prompt.js +4 -0
- package/dist/workflows/dag/rerun-plan.js +7 -1
- package/dist/workflows/dag/runner.js +26 -1
- package/dist/workflows/dag/types.js +6 -0
- package/docs/operations/README.md +1 -0
- package/docs/templates/frontend-design-contract.md +4 -4
- package/docs/templates/frontend-implementation-contract.schema.json +34 -2
- package/docs/templates/frontend-implementation-dag.json +14 -13
- package/package.json +6 -3
- package/skills/frontend-bounded-implement/SKILL.md +3 -3
- package/skills/frontend-bounded-implement/references/code-standards.md +3 -3
- package/skills/frontend-contract/SKILL.md +2 -1
- package/skills/frontend-contract/references/contract-protocol.md +22 -4
- package/skills/frontend-design-review/SKILL.md +22 -16
- package/skills/frontend-design-review/references/review-checklist.md +7 -7
- package/skills/frontend-plan/SKILL.md +21 -2
- package/skills/frontend-plan/references/decision-contract.md +42 -23
- package/skills/frontend-review/SKILL.md +22 -29
- package/skills/frontend-review/references/review-findings.md +24 -43
- package/skills/frontend-scout/references/scout-evidence.md +4 -0
- package/dist/worker/console/static/assets/channel-DAS07MdS.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Cwk-5jiW.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Cwk-5jiW.js +0 -1
- package/dist/worker/console/static/assets/index-C0O48S_P.js +0 -449
- package/dist/worker/console/static/assets/index-CzKf4U8P.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DyJg5gzW.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-B1cw_0Uy.js +0 -8
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { mkdir, rename, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { readFrontendToolProgress } from "./frontend-durable-tools.js";
|
|
5
|
+
import { frontendModelCapabilities } from "../../shared/frontend-execution-policy.js";
|
|
6
|
+
/** Count returned text only after a successful native read. No filesystem reads or raw content retention. */
|
|
7
|
+
export function createFrontendReadObservation() {
|
|
8
|
+
const pending = new Map();
|
|
9
|
+
const finished = new Set();
|
|
10
|
+
const seen = new Set();
|
|
11
|
+
let bytes = 0, duplicates = 0, complete = 0, rejected = 0, unknown = false;
|
|
12
|
+
return {
|
|
13
|
+
observe(raw) {
|
|
14
|
+
const event = record(raw);
|
|
15
|
+
const id = event.toolCallId;
|
|
16
|
+
if (typeof id !== "string")
|
|
17
|
+
return;
|
|
18
|
+
if (event.type === "tool_execution_start" && event.toolName === "read")
|
|
19
|
+
pending.set(id, record(event.args ?? event.input));
|
|
20
|
+
if (event.type !== "tool_execution_end" || !pending.has(id) || finished.has(id))
|
|
21
|
+
return;
|
|
22
|
+
const args = pending.get(id);
|
|
23
|
+
pending.delete(id);
|
|
24
|
+
finished.add(id);
|
|
25
|
+
const result = record(event.result);
|
|
26
|
+
if (event.isError === true || result.isError === true || result.ok === false || result.error || record(result.details).ok === false) {
|
|
27
|
+
rejected++;
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const content = result.content;
|
|
31
|
+
if (!Array.isArray(content) || content.some(item => record(item).type !== "text" || typeof record(item).text !== "string") || typeof args.path !== "string") {
|
|
32
|
+
unknown = true;
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const text = content.map(item => record(item).text).join("\n");
|
|
36
|
+
const size = Buffer.byteLength(text);
|
|
37
|
+
const key = createHash("sha256").update(JSON.stringify([args.path.replaceAll("\\", "/").replace(/^\.\//, ""), args.offset ?? null, args.limit ?? null, text])).digest("hex");
|
|
38
|
+
bytes += size;
|
|
39
|
+
complete++;
|
|
40
|
+
if (seen.has(key))
|
|
41
|
+
duplicates += size;
|
|
42
|
+
seen.add(key);
|
|
43
|
+
},
|
|
44
|
+
snapshot() { return { successfulReadBytes: unknown ? null : bytes, duplicateReadBytes: unknown ? null : duplicates, completedReads: complete, rejectedReads: rejected, incompleteReads: pending.size }; },
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export function classifyFrontendSessionFailure(result) {
|
|
48
|
+
const errorCode = /\b(?:FRONTEND_[A-Z_]+|TOOL_SCHEMA_INVALID|FACT_IDENTITY_CONFLICT|REQUEST_ID_REUSE_CONFLICT|TERMINAL_CONFLICT|[A-Z_]+_INCOMPLETE|[A-Z_]+_EXHAUSTED)\b/.exec(result.stderr)?.[0] ?? null;
|
|
49
|
+
const category = result.failureCategory;
|
|
50
|
+
const failureLayer = result.stopReason === "length" || /context|budget-exhausted/.test(category) ? "capacity"
|
|
51
|
+
: result.ok ? "none"
|
|
52
|
+
: errorCode?.includes("LEDGER") || errorCode === "FRONTEND_COMMIT_FAILED" ? "persistence"
|
|
53
|
+
: errorCode === "TOOL_SCHEMA_INVALID" ? "schema"
|
|
54
|
+
: /binding|source|permission|write-guard|tool-policy/.test(category) ? "governance"
|
|
55
|
+
: /timeout|rate-limit|provider|transport|network|auth/.test(category) ? "provider"
|
|
56
|
+
: /invalid-output|structured-output/.test(category) ? "protocol-or-coverage"
|
|
57
|
+
: "unknown";
|
|
58
|
+
return { failureLayer, errorCode };
|
|
59
|
+
}
|
|
60
|
+
const fields = ["inputTokens", "outputTokens", "reasoningTokens", "cacheReadTokens", "cacheWriteTokens", "totalTokens", "peakRequestContextTokens"];
|
|
61
|
+
const record = (v) => v !== null && typeof v === "object" ? v : {};
|
|
62
|
+
const number = (r, ...keys) => {
|
|
63
|
+
for (const key of keys)
|
|
64
|
+
if (typeof r[key] === "number" && Number.isFinite(r[key]) && r[key] >= 0)
|
|
65
|
+
return r[key];
|
|
66
|
+
return null;
|
|
67
|
+
};
|
|
68
|
+
const sumKnown = (values) => values.length && values.every(v => v !== null) ? values.reduce((s, v) => s + v, 0) : null;
|
|
69
|
+
/** Observation only. Never feed this projection into the SDK execution guard. */
|
|
70
|
+
export function collectFrontendUsage(events) {
|
|
71
|
+
const samples = new Map();
|
|
72
|
+
const aliases = new Map();
|
|
73
|
+
let anonymous = false;
|
|
74
|
+
let observed = false;
|
|
75
|
+
for (const raw of events) {
|
|
76
|
+
const event = record(raw);
|
|
77
|
+
const message = record(event.message);
|
|
78
|
+
if (typeof message.role === "string" && message.role !== "assistant")
|
|
79
|
+
continue;
|
|
80
|
+
const usage = record(event.usage ?? message.usage ?? event.tokenUsage);
|
|
81
|
+
if (!Object.keys(usage).length)
|
|
82
|
+
continue;
|
|
83
|
+
observed = true;
|
|
84
|
+
const ids = [event.responseId, event.messageId, message.responseId, message.id].filter((v) => typeof v === "string" && v.length > 0);
|
|
85
|
+
const roots = [...new Set(ids.map(id => aliases.get(id) ?? id))];
|
|
86
|
+
const key = roots[0];
|
|
87
|
+
if (!key) {
|
|
88
|
+
anonymous = true;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const input = number(usage, "input", "input_tokens", "inputTokens", "prompt_tokens", "promptTokens");
|
|
92
|
+
const output = number(usage, "output", "output_tokens", "outputTokens", "completion_tokens", "completionTokens");
|
|
93
|
+
const cacheRead = number(usage, "cacheRead", "cache_read_input_tokens");
|
|
94
|
+
const cacheWrite = number(usage, "cacheWrite", "cache_creation_input_tokens");
|
|
95
|
+
// Pi's native input excludes cache tokens; OpenAI-compatible input includes them.
|
|
96
|
+
const context = input === null ? null : "input" in usage ? sumKnown([input, cacheRead, cacheWrite]) : input;
|
|
97
|
+
const sample = {
|
|
98
|
+
inputTokens: input, outputTokens: output,
|
|
99
|
+
reasoningTokens: number(usage, "reasoning", "reasoning_tokens", "reasoningTokens") ?? number(record(usage.completion_tokens_details ?? usage.output_tokens_details), "reasoning_tokens"),
|
|
100
|
+
cacheReadTokens: cacheRead, cacheWriteTokens: cacheWrite,
|
|
101
|
+
totalTokens: number(usage, "total_tokens", "totalTokens") ?? sumKnown([context, output]),
|
|
102
|
+
peakRequestContextTokens: context,
|
|
103
|
+
};
|
|
104
|
+
for (const root of roots) {
|
|
105
|
+
const previous = samples.get(root);
|
|
106
|
+
if (previous)
|
|
107
|
+
for (const field of fields)
|
|
108
|
+
sample[field] = previous[field] === null ? sample[field] : sample[field] === null ? previous[field] : Math.max(previous[field], sample[field]);
|
|
109
|
+
samples.delete(root);
|
|
110
|
+
}
|
|
111
|
+
for (const [alias, root] of aliases)
|
|
112
|
+
if (roots.includes(root))
|
|
113
|
+
aliases.set(alias, key);
|
|
114
|
+
for (const id of ids)
|
|
115
|
+
aliases.set(id, key);
|
|
116
|
+
samples.set(key, sample);
|
|
117
|
+
}
|
|
118
|
+
const result = Object.fromEntries(fields.map(field => [field, anonymous ? null : field === "peakRequestContextTokens" ? (samples.size && [...samples.values()].every(s => s[field] !== null) ? Math.max(...[...samples.values()].map(s => s[field])) : null) : sumKnown([...samples.values()].map(s => s[field]))]));
|
|
119
|
+
return { ...result, requestCount: anonymous || !observed ? null : samples.size,
|
|
120
|
+
completeness: !observed ? "unknown" : !anonymous && result.inputTokens !== null && result.outputTokens !== null && result.totalTokens !== null ? "complete" : "partial" };
|
|
121
|
+
}
|
|
122
|
+
function aggregateAttempts(attempts) {
|
|
123
|
+
const usages = attempts.map(a => a.actual ?? collectFrontendUsage([]));
|
|
124
|
+
const totals = Object.fromEntries(fields.map(field => [field, field === "peakRequestContextTokens"
|
|
125
|
+
? usages.every(u => u[field] !== null) ? Math.max(...usages.map(u => u[field])) : null
|
|
126
|
+
: sumKnown(usages.map(u => u[field]))]));
|
|
127
|
+
return { ...totals, requestCount: sumKnown(usages.map(u => u.requestCount)), completeness: usages.every(u => u.completeness === "complete") ? "complete" : usages.every(u => u.completeness === "unknown") ? "unknown" : "partial" };
|
|
128
|
+
}
|
|
129
|
+
/** Captures every transport attempt, including an SDK failure before CLI fallback. */
|
|
130
|
+
export async function observeFrontendAttempt(input, execute) {
|
|
131
|
+
const event = { id: randomUUID(), backend: input.backend, model: input.model, startedAt: new Date().toISOString(), completion: "incomplete" };
|
|
132
|
+
const notify = () => { try {
|
|
133
|
+
input.observer?.(structuredClone(event));
|
|
134
|
+
}
|
|
135
|
+
catch { /* observation cannot change execution */ } };
|
|
136
|
+
notify();
|
|
137
|
+
const result = await execute();
|
|
138
|
+
event.completion = "complete";
|
|
139
|
+
event.actual = result.usageObservation ?? collectFrontendUsage([]);
|
|
140
|
+
event.reads = result.readObservation;
|
|
141
|
+
event.capabilities = result.capabilitiesObservation;
|
|
142
|
+
event.requestEnvelope = result.requestEnvelopeObservation;
|
|
143
|
+
event.terminal = { ok: result.ok, failureCategory: result.failureCategory, stopReason: result.stopReason ?? null, durationMs: result.durationMs };
|
|
144
|
+
notify();
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
const measure = (text) => ({ sha256: createHash("sha256").update(text).digest("hex"), chars: text.length, bytes: Buffer.byteLength(text), estimatedTokens: Math.ceil(Buffer.byteLength(text) / 3) });
|
|
148
|
+
function safeObservation(read, fallback) { try {
|
|
149
|
+
return read();
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return fallback;
|
|
153
|
+
} }
|
|
154
|
+
/** One logical dispatch and its real transport attempts; no raw prompt or tool output is stored. */
|
|
155
|
+
export async function observeFrontendSession(input, execute) {
|
|
156
|
+
const durableBefore = safeObservation(() => input.durableCommittedCount?.(), undefined);
|
|
157
|
+
const before = safeObservation(() => input.committedCount?.(), undefined);
|
|
158
|
+
const toolsBefore = readFrontendToolProgress(input.customTools);
|
|
159
|
+
const receipt = {
|
|
160
|
+
schemaVersion: 1, policyVersion: "frontend-session-observation-v1",
|
|
161
|
+
identity: { sessionId: randomUUID(), phase: input.phase, scopeIds: input.scopeIds ?? [], taskId: input.taskId ?? null, runId: input.runId ?? null, nodeId: input.nodeId ?? null, attempt: input.attempt ?? null, sourceDigest: input.sourceDigest ?? null, contractDigest: input.contractDigest ?? null },
|
|
162
|
+
startedAt: new Date().toISOString(), finishedAt: null, completion: "incomplete",
|
|
163
|
+
planned: { prompt: measure(input.prompt), userMessage: measure(input.userMessage ?? ""), toolSchemas: input.customTools ? safeObservation(() => measure(JSON.stringify(input.customTools.map(t => { const tool = record(t); return { name: tool.name, description: tool.description, parameters: tool.parameters }; }))), null) : null,
|
|
164
|
+
estimatorVersion: "utf8-bytes-div3-v1-estimate", capabilities: { ...frontendModelCapabilities(undefined), model: input.model ?? null }, unmeasuredComponents: ["sdk-system-overhead", "built-in-tools", "history", "tool-results", "attachments"] },
|
|
165
|
+
actual: collectFrontendUsage([]), attempts: [],
|
|
166
|
+
progress: { memoryCommittedDelta: null, durableCommittedDelta: null, failedRecords: null, duplicateRecords: null, durableSubmissions: null, durableReplacements: null, successfulReadBytes: null, duplicateReadBytes: null }, terminal: null,
|
|
167
|
+
};
|
|
168
|
+
let writes = Promise.resolve();
|
|
169
|
+
let abandoned = false;
|
|
170
|
+
const writeObservation = async (file, snapshot) => {
|
|
171
|
+
// Never recreate a run that the runner has already archived.
|
|
172
|
+
await mkdir(path.dirname(file)).catch(error => { if (error.code !== "EEXIST")
|
|
173
|
+
throw error; });
|
|
174
|
+
const temporary = `${file}.${randomUUID()}.tmp`;
|
|
175
|
+
try {
|
|
176
|
+
await writeFile(temporary, JSON.stringify(snapshot), { flag: "wx" });
|
|
177
|
+
await rename(temporary, file);
|
|
178
|
+
}
|
|
179
|
+
finally {
|
|
180
|
+
await rm(temporary, { force: true }).catch(() => { });
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
const save = () => {
|
|
184
|
+
const snapshot = structuredClone(receipt);
|
|
185
|
+
writes = writes.then(async () => {
|
|
186
|
+
if (abandoned)
|
|
187
|
+
return;
|
|
188
|
+
if (input.sink)
|
|
189
|
+
await input.sink(snapshot);
|
|
190
|
+
else if (input.artifactPath)
|
|
191
|
+
await writeObservation(input.artifactPath, snapshot);
|
|
192
|
+
}).catch(() => { });
|
|
193
|
+
return writes;
|
|
194
|
+
};
|
|
195
|
+
const waitForWrite = async (pending) => {
|
|
196
|
+
let timer;
|
|
197
|
+
try {
|
|
198
|
+
await Promise.race([pending, new Promise(resolve => { timer = setTimeout(() => { abandoned = true; resolve(); }, 100); })]);
|
|
199
|
+
}
|
|
200
|
+
finally {
|
|
201
|
+
if (timer)
|
|
202
|
+
clearTimeout(timer);
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
await waitForWrite(save());
|
|
206
|
+
const result = await execute(event => {
|
|
207
|
+
const index = receipt.attempts.findIndex(a => a.id === event.id);
|
|
208
|
+
if (index < 0)
|
|
209
|
+
receipt.attempts.push(event);
|
|
210
|
+
else
|
|
211
|
+
receipt.attempts[index] = event;
|
|
212
|
+
void save();
|
|
213
|
+
});
|
|
214
|
+
receipt.finishedAt = new Date().toISOString();
|
|
215
|
+
receipt.completion = "complete";
|
|
216
|
+
receipt.actual = receipt.attempts.length ? aggregateAttempts(receipt.attempts) : result.usageObservation ?? collectFrontendUsage([]);
|
|
217
|
+
receipt.planned.capabilities = receipt.attempts[0]?.capabilities ?? result.capabilitiesObservation ?? receipt.planned.capabilities;
|
|
218
|
+
receipt.progress.memoryCommittedDelta = before !== undefined && input.committedCount ? safeObservation(() => Math.max(0, input.committedCount() - before), null) : null;
|
|
219
|
+
const durableAfter = safeObservation(() => input.durableCommittedCount?.(), undefined);
|
|
220
|
+
receipt.progress.durableCommittedDelta = durableBefore !== undefined && durableAfter !== undefined ? Math.max(0, durableAfter - durableBefore) : null;
|
|
221
|
+
const toolsAfter = readFrontendToolProgress(input.customTools);
|
|
222
|
+
if (toolsBefore && toolsAfter)
|
|
223
|
+
for (const key of ["failedRecords", "duplicateRecords", "durableSubmissions", "durableReplacements"])
|
|
224
|
+
receipt.progress[key] = Math.max(0, toolsAfter[key] - toolsBefore[key]);
|
|
225
|
+
const reads = receipt.attempts.length ? receipt.attempts.map(a => a.reads) : [result.readObservation];
|
|
226
|
+
for (const key of ["successfulReadBytes", "duplicateReadBytes"])
|
|
227
|
+
receipt.progress[key] = sumKnown(reads.map(r => r?.[key] ?? null));
|
|
228
|
+
receipt.terminal = { ok: result.ok, failureCategory: result.failureCategory, stopReason: result.stopReason ?? null, durationMs: Date.parse(receipt.finishedAt) - Date.parse(receipt.startedAt), ...classifyFrontendSessionFailure(result) };
|
|
229
|
+
await waitForWrite(save());
|
|
230
|
+
return result;
|
|
231
|
+
}
|
|
232
|
+
export function summarizeFrontendSessions(receipts) {
|
|
233
|
+
const byId = new Map();
|
|
234
|
+
for (const receipt of receipts) {
|
|
235
|
+
const previous = byId.get(receipt.identity.sessionId);
|
|
236
|
+
const rank = (r) => [r.completion === "complete" ? 1 : 0, Date.parse(r.finishedAt ?? r.startedAt), r.attempts.filter(a => a.completion === "complete").length, r.attempts.length];
|
|
237
|
+
const candidateRank = rank(receipt);
|
|
238
|
+
const previousRank = previous ? rank(previous) : [];
|
|
239
|
+
const difference = candidateRank.map((v, i) => v - (previousRank[i] ?? 0)).find(v => v !== 0) ?? 0;
|
|
240
|
+
if (!previous || difference > 0)
|
|
241
|
+
byId.set(receipt.identity.sessionId, receipt);
|
|
242
|
+
}
|
|
243
|
+
const unique = [...byId.values()];
|
|
244
|
+
return { sessions: unique.length, failedSessions: unique.filter(r => r.terminal?.ok === false).length, incompleteSessions: unique.filter(r => r.completion === "incomplete").length,
|
|
245
|
+
failureLayers: Object.fromEntries([...new Set(unique.flatMap(r => r.terminal && r.terminal.failureLayer !== "none" ? [r.terminal.failureLayer] : []))].sort().map(layer => [layer, unique.filter(r => r.terminal?.failureLayer === layer).length])),
|
|
246
|
+
progress: Object.fromEntries(["durableCommittedDelta", "durableSubmissions", "durableReplacements", "failedRecords", "duplicateRecords", "successfulReadBytes", "duplicateReadBytes"].map(key => [key, sumKnown(unique.map(r => r.progress[key]))])),
|
|
247
|
+
totalTokens: sumKnown(unique.flatMap(r => r.attempts.length ? r.attempts.map(a => a.actual?.totalTokens ?? null) : [r.actual.totalTokens])),
|
|
248
|
+
sessionDurationSumMs: sumKnown(unique.map(r => r.terminal?.durationMs ?? null)) };
|
|
249
|
+
}
|
|
@@ -236,6 +236,7 @@ export const PLAN_LEDGER_FACT_KINDS = [
|
|
|
236
236
|
"plan-requirement",
|
|
237
237
|
"plan-verification-target",
|
|
238
238
|
"plan-evidence-gap",
|
|
239
|
+
"mock-endpoint",
|
|
239
240
|
];
|
|
240
241
|
function skeletonTargetFiles(skeleton) {
|
|
241
242
|
if (!isRecord(skeleton))
|
|
@@ -386,8 +387,25 @@ export function assemblePlanPatchFromCommittedFacts(records, contractRequirement
|
|
|
386
387
|
patch.interactions = [...interactionByName.values(), ...unnamedInteractions];
|
|
387
388
|
}
|
|
388
389
|
const mockApi = lastByKind("mock-api");
|
|
390
|
+
if (!mockApi && facts.some(f => f.kind === "mock-endpoint" && f.removed !== true))
|
|
391
|
+
throw Error("MOCK_POLICY_MISSING: endpoint has no policy");
|
|
389
392
|
if (mockApi && isRecord(mockApi.mockApi)) {
|
|
390
|
-
|
|
393
|
+
const endpoints = new Map();
|
|
394
|
+
for (const endpoint of Array.isArray(mockApi.mockApi.endpoints) ? mockApi.mockApi.endpoints : []) {
|
|
395
|
+
if (isRecord(endpoint))
|
|
396
|
+
endpoints.set(`${endpoint.method}:${endpoint.path}`, endpoint);
|
|
397
|
+
}
|
|
398
|
+
for (const fact of facts)
|
|
399
|
+
if (fact.kind === "mock-endpoint" && isRecord(fact.endpoint)) {
|
|
400
|
+
const key = `${fact.endpoint.method}:${fact.endpoint.path}`;
|
|
401
|
+
if (fact.removed === true)
|
|
402
|
+
endpoints.delete(key);
|
|
403
|
+
else
|
|
404
|
+
endpoints.set(key, fact.endpoint);
|
|
405
|
+
}
|
|
406
|
+
if (mockApi.mockApi.strategy === "not-needed" && endpoints.size)
|
|
407
|
+
throw Error("MOCK_POLICY_CONFLICT: withdraw endpoints before declaring Mock/API not needed");
|
|
408
|
+
patch.mockApi = { ...mockApi.mockApi, endpoints: [...endpoints.values()] };
|
|
391
409
|
}
|
|
392
410
|
const deviation = lastByKind("design-deviation");
|
|
393
411
|
if (deviation) {
|
|
@@ -433,7 +451,7 @@ export function assemblePlanPatchFromCommittedFacts(records, contractRequirement
|
|
|
433
451
|
const inherited = contractRequirements.get(id);
|
|
434
452
|
if (!inherited)
|
|
435
453
|
return entry;
|
|
436
|
-
const merged = { ...entry };
|
|
454
|
+
const merged = { ...entry, ...(inherited.text ? { expectedOutcome: inherited.text } : {}), ...(inherited.execution ? { execution: inherited.execution } : {}) };
|
|
437
455
|
// Inherit the contract-declared provenance only when the plan did
|
|
438
456
|
// not already declare it (model override wins; missing inherits).
|
|
439
457
|
if (!Array.isArray(entry.sourceFragmentIds)) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { sha256OfCanonicalJson } from "../../task/contract/hash.js";
|
|
2
|
+
import { FRONTEND_MAX_IMPLEMENTATION_FILES, frontendConcreteDeliveryFiles } from "./frontend-capacity.js";
|
|
2
3
|
import { buildFrontendShapeTransitionCapsule, deriveFrontendRuntimeShapeFacts, evaluateFrontendShapeEscalation, parseFrontendShapeTransitionCapsule, } from "./frontend-shape.js";
|
|
3
4
|
import { createTypedEventStore, readCommittedEvents, } from "./frontend-typed-event-store.js";
|
|
4
5
|
import { adoptTypedEventFact, stageTypedEventFact, } from "./frontend-typed-event-transaction.js";
|
|
@@ -222,7 +223,13 @@ function extractRuntimeShapeEvidence(input) {
|
|
|
222
223
|
!sourceText.includes(path) &&
|
|
223
224
|
/(?:\.[A-Za-z0-9]+|\*\*)$/.test(path));
|
|
224
225
|
const routes = canonicalStrings(text.match(/\/(?:[A-Za-z0-9_:[\]-]+\/?)+/g) ?? []);
|
|
225
|
-
|
|
226
|
+
// The frozen delivery inventory is available before Plan. Do not spend
|
|
227
|
+
// coverage/UX sessions building a contract the same capacity gate will reject.
|
|
228
|
+
// allowedPaths and text mentions are permissions/evidence, never this count.
|
|
229
|
+
const skeleton = input.tasksById.get("frontend-plan-pi")?.structuredContractOutput?.skeleton;
|
|
230
|
+
const deliveryFiles = frontendConcreteDeliveryFiles(skeleton?.targets?.files);
|
|
231
|
+
const capacitySplit = deliveryFiles.length > FRONTEND_MAX_IMPLEMENTATION_FILES;
|
|
232
|
+
const splitRequired = capacitySplit || /["']splitRequired["']\s*:\s*true|\bsplit-required\s+shape\b/i.test(text);
|
|
226
233
|
return {
|
|
227
234
|
sourceNodeId: input.sourceNodeId,
|
|
228
235
|
sourceNodeDigest: computeFrontendShapeNodeDigest(node),
|
|
@@ -232,7 +239,10 @@ function extractRuntimeShapeEvidence(input) {
|
|
|
232
239
|
targetPaths,
|
|
233
240
|
routes,
|
|
234
241
|
splitRequired,
|
|
235
|
-
evidenceRefs: [
|
|
242
|
+
evidenceRefs: [
|
|
243
|
+
...(capacitySplit ? ["dag:frontend-plan-pi:structuredContractOutput.skeleton.targets.files"] : []),
|
|
244
|
+
`node:${input.sourceNodeId}:stdout`,
|
|
245
|
+
],
|
|
236
246
|
};
|
|
237
247
|
}
|
|
238
248
|
/** Commit a typed, structured runtime fact immediately after a canonical source
|
|
@@ -61,7 +61,7 @@ function pathSpreadScore(paths) {
|
|
|
61
61
|
function collectEscalationSignals(blob, supervised) {
|
|
62
62
|
const signals = [];
|
|
63
63
|
for (const pattern of SHAPE_ESCALATION_PATTERNS) {
|
|
64
|
-
if (
|
|
64
|
+
if (hasAffirmativeShapeSignal(blob, pattern))
|
|
65
65
|
signals.push(pattern.id);
|
|
66
66
|
}
|
|
67
67
|
if (supervised)
|
|
@@ -72,6 +72,35 @@ function collectEscalationSignals(blob, supervised) {
|
|
|
72
72
|
}
|
|
73
73
|
return [...new Set(signals)];
|
|
74
74
|
}
|
|
75
|
+
function isExplicitShapeExclusion(clause, pattern) {
|
|
76
|
+
const match = new RegExp(pattern.re.source, "i").exec(clause);
|
|
77
|
+
if (!match)
|
|
78
|
+
return false;
|
|
79
|
+
const prefix = clause.slice(0, match.index).trim();
|
|
80
|
+
const suffix = clause.slice(match.index + match[0].length).trim();
|
|
81
|
+
const actionInSignal = ["new-route", "new-dependency"].includes(pattern.id);
|
|
82
|
+
if (suffix && !(actionInSignal && /^no$/i.test(prefix) && /^is\s+(?:required|needed)$/i.test(suffix)))
|
|
83
|
+
return false;
|
|
84
|
+
if (/^(?:不涉及|不(?:修改|变更|调整|改变|新增|添加)|无需(?:修改|变更|调整|改变|新增|添加)|no\s+changes?\s+to|(?:do\s+not|don't)\s+(?:change|modify))$/i.test(prefix))
|
|
85
|
+
return true;
|
|
86
|
+
return actionInSignal && /^(?:不|禁止|无需|无须|不需要|no|without|do\s+not|don't)$/i.test(prefix);
|
|
87
|
+
}
|
|
88
|
+
/** Only a complete, independent scope exclusion can suppress a risk signal.
|
|
89
|
+
* Nested negation, conditions and trailing consequences remain risk evidence;
|
|
90
|
+
* an excluded occurrence never cancels another occurrence elsewhere. */
|
|
91
|
+
function hasAffirmativeShapeSignal(blob, pattern) {
|
|
92
|
+
for (const sentence of blob.split(/[\n。.;;!?!?]/)) {
|
|
93
|
+
if (!pattern.re.test(sentence))
|
|
94
|
+
continue;
|
|
95
|
+
// Commas can attach a condition to an exclusion. A comma-separated list
|
|
96
|
+
// is independent only when every item is itself an explicit exclusion.
|
|
97
|
+
const clauses = sentence.split(/[,,、]/).map(part => part.trim().replace(/^(?:[-*+]|\d+\))\s+/, ""));
|
|
98
|
+
if (clauses.every(clause => SHAPE_ESCALATION_PATTERNS.some(candidate => isExplicitShapeExclusion(clause, candidate))))
|
|
99
|
+
continue;
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
75
104
|
/** Negation applies to the API mention itself, never to unrelated Mock policy. */
|
|
76
105
|
function hasFrontendRemoteSignal(blob) {
|
|
77
106
|
const remaining = blob
|
|
@@ -39,9 +39,10 @@ const resultSchema = z.object({
|
|
|
39
39
|
})),
|
|
40
40
|
})),
|
|
41
41
|
});
|
|
42
|
-
|
|
42
|
+
/** Resolve runner paths against its cwd, then use the contract workspace coordinates. */
|
|
43
|
+
export function parseFrontendTestExecutionReport(raw, cwd, workspaceRoot = cwd) {
|
|
43
44
|
return resultSchema.parse(raw).testResults.flatMap((suite) => suite.assertionResults.map((test) => ({
|
|
44
|
-
file: path.relative(
|
|
45
|
+
file: path.relative(workspaceRoot, path.resolve(cwd, suite.name)).replace(/\\/g, "/"),
|
|
45
46
|
title: test.fullName ?? [...(test.ancestorTitles ?? []), test.title].join(" "),
|
|
46
47
|
status: test.status,
|
|
47
48
|
})));
|
|
@@ -128,6 +128,7 @@ export const PLAN_RECORD_FACT_KINDS = [
|
|
|
128
128
|
"plan-evidence-gap",
|
|
129
129
|
"state-registry",
|
|
130
130
|
];
|
|
131
|
+
export const INCREMENTAL_DETAIL_FACT_KINDS = ["mock-endpoint", "review-finding", "design-finding", "contract-scope-completed", "plan-group-coverage", "scout-scope", "review-scope-completed", "design-scope-completed"];
|
|
131
132
|
/**
|
|
132
133
|
* A+B (AC-009): typed issue category shared by review and design change
|
|
133
134
|
* requests. The five-value enum replaces free-form issueCategory strings.
|
|
@@ -159,6 +160,7 @@ const ALL_TYPED_EVENT_FACT_KIND_VALUES = [
|
|
|
159
160
|
...CONTRACT_FACT_KINDS,
|
|
160
161
|
...PLAN_TERMINAL_FACT_KINDS,
|
|
161
162
|
...PLAN_RECORD_FACT_KINDS,
|
|
163
|
+
...INCREMENTAL_DETAIL_FACT_KINDS,
|
|
162
164
|
]),
|
|
163
165
|
];
|
|
164
166
|
export const typedEventFactKindSchema = z.enum(ALL_TYPED_EVENT_FACT_KIND_VALUES);
|
|
@@ -261,6 +263,15 @@ export const typedEventRecordSchema = z
|
|
|
261
263
|
revision: z.number().int().nonnegative(),
|
|
262
264
|
recordedAt: z.string().min(1),
|
|
263
265
|
quarantineReason: z.string().min(1).optional(),
|
|
266
|
+
submission: z.object({
|
|
267
|
+
version: z.literal(1),
|
|
268
|
+
bindingSha256: z.string().regex(/^[a-f0-9]{64}$/),
|
|
269
|
+
tool: z.string(),
|
|
270
|
+
callId: z.string(),
|
|
271
|
+
identity: z.string(),
|
|
272
|
+
paramsSha256: z.string().regex(/^[a-f0-9]{64}$/),
|
|
273
|
+
receipt: z.record(z.unknown()).optional(),
|
|
274
|
+
}).strict().optional(),
|
|
264
275
|
})
|
|
265
276
|
.strict()
|
|
266
277
|
.superRefine((value, ctx) => {
|
|
@@ -462,7 +462,7 @@ export async function runFrontendVerificationTraceGate(input) {
|
|
|
462
462
|
const mockCommandLabels = input.evidence?.mock?.commandLabels ?? [];
|
|
463
463
|
const mockCommandNodeId = input.evidence?.mock?.nodeId;
|
|
464
464
|
const mockCommandTexts = input.evidence?.mock?.commandTexts ?? [];
|
|
465
|
-
if (input.evidence && contract.mockApi.strategy !== "not-needed" && mockCommandLabels.length === 0) {
|
|
465
|
+
if (input.evidence && contract.verificationTargets.some((target) => target.mode === "behavior") && contract.mockApi.strategy !== "not-needed" && mockCommandLabels.length === 0) {
|
|
466
466
|
throw new Error("trace: selected Mock strategy requires successful Mock verification commands");
|
|
467
467
|
}
|
|
468
468
|
if (input.evidence) {
|