@tea-agent/loop-agent 0.37.0 → 0.37.1-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -6
- package/CHANGELOG.md +71 -144
- package/README.md +1 -1
- package/bin/loop-agent.js +1 -37
- package/dist/application/task-lifecycle/advance.js +0 -1
- package/dist/application/task-lifecycle/observe.js +0 -15
- package/dist/application/task-lifecycle/plan-transitions.js +0 -15
- package/dist/cli/command-definitions.js +0 -7
- package/dist/cli/program.js +3 -8
- package/dist/commands/init-upgrade.js +19 -351
- package/dist/commands/init.js +67 -14
- package/dist/commands/run-dag-progress.js +0 -14
- package/dist/commands/task-advance.js +3 -33
- package/dist/executors/dag-pi-executor.js +30 -47
- package/dist/executors/pi-sdk-executor.js +0 -34
- package/dist/executors/shell-executor.js +95 -6
- package/dist/shared/operator/capabilities.js +13 -200
- package/dist/shared/package-metadata.js +0 -42
- package/dist/task/contract/project.js +0 -4
- package/dist/task/source-prepare/completeness.js +0 -53
- package/dist/task/source-prepare/parse-intent.js +12 -82
- package/dist/task/source-prepare/prepare.js +5 -102
- package/dist/task/source-prepare/semantic-intake.js +23 -144
- package/dist/worker/console/chat/artifact-card.js +1 -8
- package/dist/worker/console/chat/chat-event-store.js +0 -152
- package/dist/worker/console/chat/human-gate-card.js +1 -9
- package/dist/worker/console/chat/operation-card.js +2 -71
- package/dist/worker/console/chat/pi-runtime.js +62 -111
- package/dist/worker/console/chat/routes.js +10 -43
- package/dist/worker/console/chat/session-store.js +1 -9
- package/dist/worker/console/chat/shortcuts.js +7 -9
- package/dist/worker/console/chat/turn-process.js +23 -204
- package/dist/worker/console/chat/workspace-landing.js +1 -2
- package/dist/worker/console/operation-runner.js +6 -155
- package/dist/worker/console/operator-actions.js +14 -391
- package/dist/worker/console/operator-user-error.js +0 -4
- package/dist/worker/console/recovery-cta.js +3 -50
- package/dist/worker/console/static/assets/index-HX1pbOyl.css +1 -0
- package/dist/worker/console/static/assets/index-M0BLEBfh.js +56 -0
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +9 -13
- package/dist/worker/console/static-src/app/useOperatorActions.js +2 -4
- package/dist/worker/console/static-src/app/useRecoveryActions.js +56 -65
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +1 -73
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +30 -114
- package/dist/worker/console/static-src/operator-chat/refs.js +0 -3
- package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +1 -2
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +13 -67
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +6 -12
- package/dist/worker/console/static-src/operator-chat/useComposer.js +8 -45
- package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +3 -7
- package/dist/worker/loop-agent/loop-agent-client.js +3 -17
- package/dist/worker/observability/read-model.js +0 -20
- package/dist/worker/observe/spec-evidence.js +8 -3
- package/dist/worker/observe/static/operator-chrome.d.ts +0 -1
- package/dist/worker/observe/static/operator-chrome.js +3 -10
- package/dist/worker/observe/static/views/dag-inspector.js +71 -6
- package/dist/worker/observe/static/views/dag.js +0 -12
- package/dist/worker/preflight.js +1 -2
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +707 -37
- package/dist/workflows/dag/backend-test-case-manifest.js +4 -0
- package/dist/workflows/dag/backend-test-markdown-workflow.js +109 -7
- package/dist/workflows/dag/backend-test-module-stem.js +5 -0
- package/dist/workflows/dag/backend-test-pytest-collection.js +345 -24
- package/dist/workflows/dag/backend-test-scenario-param.js +890 -164
- package/dist/workflows/dag/backend-test-writer-completeness.js +47 -16
- package/dist/workflows/dag/dynamic-runtime/map.js +24 -8
- package/dist/workflows/dag/dynamic-runtime/shared.js +1 -9
- package/dist/workflows/dag/frontend-implementation-contract.js +39 -233
- package/dist/workflows/dag/frontend-prewrite-gate.js +61 -364
- package/dist/workflows/dag/frontend-repair.js +18 -219
- package/dist/workflows/dag/frontend-verification-trace.js +32 -47
- package/dist/workflows/dag/init-hybrid.js +82 -165
- package/dist/workflows/dag/node-execution.js +0 -89
- package/dist/workflows/dag/recovery-recommendation.js +0 -58
- package/dist/workflows/dag/runner.js +15 -333
- package/dist/workflows/dag/scheduler.js +3 -257
- package/dist/workflows/dag/types.js +9 -130
- package/docs/README.md +3 -3
- package/docs/architecture/evolution.md +67 -102
- package/docs/templates/backend-test-dag.json +50 -32
- package/docs/templates/init-managed-agents.md +2 -5
- package/harness.json +2 -2
- package/package.json +3 -9
- package/skills/loop-agent/SKILL.md +0 -1
- package/skills/loop-agent/references/command-reference.md +0 -4
- package/dist/build-stamp.json +0 -6
- package/dist/commands/dag-request-interrupt.js +0 -20
- package/dist/worker/console/chat/assistant-content.js +0 -121
- package/dist/worker/console/chat/semantic-activity.js +0 -477
- package/dist/worker/console/operation-run-facts.js +0 -190
- package/dist/worker/console/operation-wait.js +0 -355
- package/dist/worker/console/recovery-error-copy.js +0 -198
- package/dist/worker/console/static/assets/abnfDiagram-N423BO3Z-DSvF6RXQ.js +0 -1
- package/dist/worker/console/static/assets/arc-DuI4ogtJ.js +0 -1
- package/dist/worker/console/static/assets/architectureDiagram-T3A2C74G-DdBbgD4y.js +0 -36
- package/dist/worker/console/static/assets/blockDiagram-VBNYF7ZC-xQC2HELm.js +0 -132
- package/dist/worker/console/static/assets/c4Diagram-5PPSVZJV-DPMSgwIZ.js +0 -10
- package/dist/worker/console/static/assets/channel-DLVz9TYW.js +0 -1
- package/dist/worker/console/static/assets/chunk-2GRJ4B5K-WTEKTxin.js +0 -1
- package/dist/worker/console/static/assets/chunk-2Q5K7J3B-B6vZ0Pv_.js +0 -1
- package/dist/worker/console/static/assets/chunk-5RXB4S5H-BrerNAix.js +0 -231
- package/dist/worker/console/static/assets/chunk-5VM5RSS4-D_ch2aaI.js +0 -15
- package/dist/worker/console/static/assets/chunk-6Q2QTUOP-VvfacNrq.js +0 -88
- package/dist/worker/console/static/assets/chunk-GF5L2VYU-B-uGFIg2.js +0 -206
- package/dist/worker/console/static/assets/chunk-JWPE2WC7-BlysCtod.js +0 -1
- package/dist/worker/console/static/assets/chunk-KBJHAD2P-BjAUAfcT.js +0 -1
- package/dist/worker/console/static/assets/chunk-RYQCIY6F-BeZnBmb-.js +0 -1
- package/dist/worker/console/static/assets/chunk-XXDRQBXY-Bbfe3UJG.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BeAuGcO9.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BeAuGcO9.js +0 -1
- package/dist/worker/console/static/assets/cose-bilkent-JH36ORCC-BKJQnYVM.js +0 -1
- package/dist/worker/console/static/assets/cynefin-VYW2F7L2-B1wtI3hU.js +0 -166
- package/dist/worker/console/static/assets/cynefinDiagram-MW4NZA55-DCF6DBSu.js +0 -62
- package/dist/worker/console/static/assets/cytoscape.esm-yzknjiTM.js +0 -321
- package/dist/worker/console/static/assets/dagre-VZM6K2ZE-BJyNGRlP.js +0 -4
- package/dist/worker/console/static/assets/defaultLocale-DX6XiGOO.js +0 -1
- package/dist/worker/console/static/assets/diagram-7IWD3JNH-B7j1-VsZ.js +0 -30
- package/dist/worker/console/static/assets/diagram-B4RE2ZJO-BREJ140a.js +0 -3
- package/dist/worker/console/static/assets/diagram-LBJQPF4R-CDPHmHGH.js +0 -24
- package/dist/worker/console/static/assets/diagram-Q27KOJAE-BtZLrez1.js +0 -24
- package/dist/worker/console/static/assets/diagram-UB23O5K3-mzybu8oU.js +0 -41
- package/dist/worker/console/static/assets/ebnfDiagram-BXEA7PRR-Dk3I2uO6.js +0 -1
- package/dist/worker/console/static/assets/erDiagram-JOGREHBK-BT97u_tQ.js +0 -85
- package/dist/worker/console/static/assets/flowDiagram-UKHOOZJN-CMOtYzui.js +0 -156
- package/dist/worker/console/static/assets/ganttDiagram-PKOTCBZU-DiNydkuY.js +0 -292
- package/dist/worker/console/static/assets/gitGraphDiagram-DS77QQ5N-Cwt6fLkl.js +0 -106
- package/dist/worker/console/static/assets/graph-DOmOIIwC.js +0 -1
- package/dist/worker/console/static/assets/index-DQxiso1w.js +0 -325
- package/dist/worker/console/static/assets/index-_j0Hzo69.css +0 -1
- package/dist/worker/console/static/assets/infoDiagram-6WML65LV-AsGIGKKB.js +0 -2
- package/dist/worker/console/static/assets/init-Gi6I4Gst.js +0 -1
- package/dist/worker/console/static/assets/ishikawaDiagram-WSZJBQD7-BL8ITZh3.js +0 -70
- package/dist/worker/console/static/assets/journeyDiagram-NVQOT4AX-VPKAUX8M.js +0 -139
- package/dist/worker/console/static/assets/kanban-definition-27J2QSJJ-BL56hfGp.js +0 -89
- package/dist/worker/console/static/assets/layout-D-LzfAck.js +0 -1
- package/dist/worker/console/static/assets/linear-BmSG3nIZ.js +0 -1
- package/dist/worker/console/static/assets/map-DxJ2ADlA.js +0 -1
- package/dist/worker/console/static/assets/mermaid.core-DWGK9kwP.js +0 -308
- package/dist/worker/console/static/assets/mindmap-definition-FAOFIHXS--vvKLjo0.js +0 -96
- package/dist/worker/console/static/assets/ordinal-Cboi1Yqb.js +0 -1
- package/dist/worker/console/static/assets/pegDiagram-VL7TDLO6-B41mxb2-.js +0 -1
- package/dist/worker/console/static/assets/pieDiagram-7S7Q4E2Y-DTlu77sw.js +0 -39
- package/dist/worker/console/static/assets/quadrantDiagram-CIZ2JOQS-Bk5A7alv.js +0 -7
- package/dist/worker/console/static/assets/railroadDiagram-AXF67PYL-Csf9jzQy.js +0 -1
- package/dist/worker/console/static/assets/requirementDiagram-LRYGKXZP-CeptoSNx.js +0 -84
- package/dist/worker/console/static/assets/sankeyDiagram-W5VNT64P-A-DLCLer.js +0 -40
- package/dist/worker/console/static/assets/sequenceDiagram-SI44F4Z6-22VbPdOI.js +0 -162
- package/dist/worker/console/static/assets/sizeCapture-X5ZJPWSS-DWvAyBC9.js +0 -1
- package/dist/worker/console/static/assets/stateDiagram-OKZ733FA-qICZ75yT.js +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DzKvfHTX.js +0 -1
- package/dist/worker/console/static/assets/swimlanes-SLNWSIFB-xi7lcMyp.js +0 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Cxqr_qM1.js +0 -8
- package/dist/worker/console/static/assets/timeline-definition-Z64GVDOM-Bu6Nl6O2.js +0 -120
- package/dist/worker/console/static/assets/vennDiagram-T6HMQDX7-BzMz4Bn9.js +0 -34
- package/dist/worker/console/static/assets/wardleyDiagram-T6FBY63Y-CxaJnzc5.js +0 -78
- package/dist/worker/console/static/assets/xychartDiagram-ELKLHX3M-5JK-omQK.js +0 -7
- package/dist/worker/console/static/fonts/katex/KaTeX_AMS-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-Italic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Math-Italic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Script-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size1-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size2-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size3-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size4-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/dist/worker/console/static-src/operator-chat/activity-journey.js +0 -125
- package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +0 -73
- package/dist/worker/console/static-src/operator-chat/activity-references.js +0 -20
- package/dist/worker/console/static-src/operator-chat/slash-palette-layout.js +0 -24
- package/dist/worker/console/static-src/operator-chat/slash-palette-nav.js +0 -141
- package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +0 -59
- package/dist/worker/observability/interrupt-eligibility.js +0 -264
- package/dist/workflows/dag/contract-output-registry.js +0 -14
- package/dist/workflows/dag/contract-validator-registrations.js +0 -8
- package/dist/workflows/dag/frontend-recovery-plan.js +0 -73
- package/dist/workflows/dag/frontend-recovery-root-manifest.js +0 -123
- package/dist/workflows/dag/frontend-recovery-run.js +0 -539
- package/dist/workflows/dag/frontend-writer-recovery.js +0 -106
- package/dist/workflows/dag/frontend-writer-rollback.js +0 -821
- package/dist/workflows/dag/interrupt-request.js +0 -559
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { lstat, readFile } from "node:fs/promises";
|
|
3
|
-
import path from "node:path";
|
|
4
3
|
import { ContractMutationError, applyTaskContract, logicalRevisionForState, observeTaskContractState, projectConstraintsMarkdown, projectRequirementMarkdown, sha256OfCanonicalJson, validateDraftForTask, } from "../contract/index.js";
|
|
5
4
|
import { TASK_CONTRACT_DRAFT_SCHEMA_VERSION } from "../contract/constants.js";
|
|
6
5
|
import { getTaskContractPaths } from "../contract/paths.js";
|
|
@@ -29,114 +28,26 @@ async function loadExistingTaskConfig(repoRoot, taskId) {
|
|
|
29
28
|
return null;
|
|
30
29
|
}
|
|
31
30
|
}
|
|
32
|
-
/**
|
|
33
|
-
* Check whether a "## References" section in a managed 需求.md matches the
|
|
34
|
-
* projector's canonical form: every entry is `- [role] <manifest-path>` and
|
|
35
|
-
* every path exists in source-manifest.json. The projector itself writes
|
|
36
|
-
* this section, so matching entries are the writer's own round-trip output
|
|
37
|
-
* and must be accepted (2026-08-16 L1). Unknown/free-form entries stay
|
|
38
|
-
* blocking — they indicate hand-edited provenance.
|
|
39
|
-
*/
|
|
40
|
-
function isProjectedReferencesSection(sectionBody, manifestPaths) {
|
|
41
|
-
const entryRe = /^- \[(?<role>[^\]]+)\] (?<ref>\S.*)$/;
|
|
42
|
-
const lines = sectionBody
|
|
43
|
-
.split(/\r?\n/)
|
|
44
|
-
.map((line) => line.trim())
|
|
45
|
-
.filter(Boolean);
|
|
46
|
-
if (lines.length === 0)
|
|
47
|
-
return true; // empty section: nothing to reject
|
|
48
|
-
return lines.every((line) => {
|
|
49
|
-
const match = entryRe.exec(line);
|
|
50
|
-
if (!match?.groups)
|
|
51
|
-
return false;
|
|
52
|
-
const ref = match.groups.ref?.replace(/\\/g, "/").trim() ?? "";
|
|
53
|
-
return manifestPaths.has(ref);
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Source Resolutions canonical projector shape:
|
|
58
|
-
* `- <sourceRef>: <conflict> → <decision> (<reason>; decidedBy=<who>)`.
|
|
59
|
-
*/
|
|
60
|
-
function isProjectedSourceResolutionsSection(sectionBody) {
|
|
61
|
-
const entryRe = /^- \S[^:]*: .+ → .+ \(.+; decidedBy=.+\)$/u;
|
|
62
|
-
const lines = sectionBody
|
|
63
|
-
.split(/\r?\n/)
|
|
64
|
-
.map((line) => line.trim())
|
|
65
|
-
.filter(Boolean);
|
|
66
|
-
if (lines.length === 0)
|
|
67
|
-
return true;
|
|
68
|
-
return lines.every((line) => entryRe.test(line));
|
|
69
|
-
}
|
|
70
|
-
/** Section titles the read-back validator may inspect (static whitelist). */
|
|
71
|
-
const MANAGED_PROJECTION_SECTION_TITLES = [
|
|
72
|
-
"References",
|
|
73
|
-
"Source Resolutions",
|
|
74
|
-
];
|
|
75
|
-
/** Extract the body of the first `## <Title>` section, if present. */
|
|
76
|
-
function extractSectionBody(markdown, title) {
|
|
77
|
-
const re = new RegExp(`^##\\s+${title}\\s*$`, "im");
|
|
78
|
-
const match = re.exec(markdown);
|
|
79
|
-
if (!match)
|
|
80
|
-
return null;
|
|
81
|
-
const rest = markdown.slice(match.index + match[0].length);
|
|
82
|
-
const nextHeading = /^##\s+/m.exec(rest);
|
|
83
|
-
return nextHeading ? rest.slice(0, nextHeading.index) : rest;
|
|
84
|
-
}
|
|
85
|
-
/** Materialized paths registered in the task's source-manifest.json. */
|
|
86
|
-
async function loadSourceManifestPaths(requirementPath) {
|
|
87
|
-
const manifestPath = path.join(path.dirname(requirementPath), "source-manifest.json");
|
|
88
|
-
try {
|
|
89
|
-
const raw = JSON.parse(await readFile(manifestPath, "utf-8"));
|
|
90
|
-
return new Set((raw.documents ?? [])
|
|
91
|
-
.map((doc) => doc.materializedPath?.replace(/\\/g, "/"))
|
|
92
|
-
.filter((p) => Boolean(p)));
|
|
93
|
-
}
|
|
94
|
-
catch {
|
|
95
|
-
return new Set();
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
31
|
async function loadManagedProjectionDraft(input) {
|
|
99
32
|
const config = input.existingTaskConfig;
|
|
100
33
|
if (!config) {
|
|
101
34
|
return { ok: false, message: "managed task config could not be loaded" };
|
|
102
35
|
}
|
|
103
36
|
try {
|
|
104
|
-
const [
|
|
37
|
+
const [requirement, constraints] = await Promise.all([
|
|
105
38
|
readFile(input.requirementPath, "utf-8"),
|
|
106
39
|
readFile(input.constraintsPath, "utf-8"),
|
|
107
40
|
]);
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
// writer/reader contract drift that deadlocked every PRD import whose
|
|
112
|
-
// draft carried references. Now accept the projector's own canonical
|
|
113
|
-
// forms: References entries must all be `- [role] <path>` with paths
|
|
114
|
-
// present in source-manifest.json; Source Resolutions entries must
|
|
115
|
-
// match the projector's `- ref: conflict → decision (...)` shape.
|
|
116
|
-
// Free-form/hand-edited entries remain blocking.
|
|
117
|
-
const manifest = await loadSourceManifestPaths(input.requirementPath);
|
|
118
|
-
const referencesBody = extractSectionBody(requirementRaw, "References");
|
|
119
|
-
if (referencesBody !== null &&
|
|
120
|
-
!isProjectedReferencesSection(referencesBody, manifest)) {
|
|
121
|
-
return {
|
|
122
|
-
ok: false,
|
|
123
|
-
message: "managed requirement projection contains hand-edited or unknown References entries (not backed by source-manifest); use loop-agent task advance " +
|
|
124
|
-
input.taskId +
|
|
125
|
-
" --from-draft <reviewed-draft.json> --json",
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
const sourceResolutionsBody = extractSectionBody(requirementRaw, "Source Resolutions");
|
|
129
|
-
if (sourceResolutionsBody !== null &&
|
|
130
|
-
!isProjectedSourceResolutionsSection(sourceResolutionsBody)) {
|
|
41
|
+
const requirementFacts = extractRequirementFactsFromMarkdown(requirement);
|
|
42
|
+
const constraintFacts = extractRequirementFactsFromMarkdown(constraints);
|
|
43
|
+
if (/^##\s+(?:References|Source Resolutions)\s*$/im.test(requirement)) {
|
|
131
44
|
return {
|
|
132
45
|
ok: false,
|
|
133
|
-
message: "managed requirement projection contains
|
|
46
|
+
message: "managed requirement projection contains unsupported References or Source Resolutions; use loop-agent task advance " +
|
|
134
47
|
input.taskId +
|
|
135
48
|
" --from-draft <reviewed-draft.json> --json",
|
|
136
49
|
};
|
|
137
50
|
}
|
|
138
|
-
const requirementFacts = extractRequirementFactsFromMarkdown(requirementRaw);
|
|
139
|
-
const constraintFacts = extractRequirementFactsFromMarkdown(constraints);
|
|
140
51
|
if (!requirementFacts.objective?.trim()) {
|
|
141
52
|
return {
|
|
142
53
|
ok: false,
|
|
@@ -606,14 +517,6 @@ export async function prepareTaskSource(input) {
|
|
|
606
517
|
code: "MANAGED_PROJECTION_INCOMPLETE",
|
|
607
518
|
level: "blocking",
|
|
608
519
|
message: managedProjectionError,
|
|
609
|
-
selfHealing: {
|
|
610
|
-
action: "adopt-draft",
|
|
611
|
-
steps: [
|
|
612
|
-
"task status --json 读取 contract.observedCanonicalHash / observedSourceHashes,确认现有源文件状态",
|
|
613
|
-
"从 Console drafts(<app-data>/repos/<repo>/drafts/<taskId>.json)或 semantic intake 产物(artifacts/intake/semantic-draft.json 的 draft 字段)取回结构化 draft",
|
|
614
|
-
"补充缺失字段(scope/invariants 等)后:task advance --from-draft <reviewed-draft.json> --json",
|
|
615
|
-
],
|
|
616
|
-
},
|
|
617
520
|
});
|
|
618
521
|
}
|
|
619
522
|
// If --use-imported-prd and no parseable requirement and no AC from flags/text → blocking
|
|
@@ -13,7 +13,6 @@ import { z } from "zod";
|
|
|
13
13
|
import { executePiStep } from "../../executors/pi-executor.js";
|
|
14
14
|
import { resolveDagPiModelConfig } from "../../executors/dag-pi-executor.js";
|
|
15
15
|
import { resolveExecutorModelMatrices } from "../../executors/model-routing.js";
|
|
16
|
-
import { listPiSdkAvailableModels } from "../../executors/pi-sdk-executor.js";
|
|
17
16
|
import { loadHarnessManifest } from "../../governance/harness.js";
|
|
18
17
|
import { TASK_CONTRACT_DRAFT_SCHEMA_VERSION } from "../contract/constants.js";
|
|
19
18
|
import { getTaskPaths } from "../runtime.js";
|
|
@@ -21,14 +20,6 @@ import { detectProductArtifactMeta } from "./artifact-meta.js";
|
|
|
21
20
|
import { filterPlaceholderPaths } from "./placeholder-paths.js";
|
|
22
21
|
export const SEMANTIC_INTAKE_ARTIFACT_REL = "artifacts/intake/semantic-draft.json";
|
|
23
22
|
export const SEMANTIC_INTAKE_ATTEMPT_MARKER = "artifacts/intake/semantic-intake-attempted.json";
|
|
24
|
-
/** Per-model wall clock for one semantic-intake Pi attempt. */
|
|
25
|
-
export const SEMANTIC_INTAKE_ATTEMPT_TIMEOUT_MS = 300_000;
|
|
26
|
-
/** Extra Pi-catalog models after MED → HIGH → LOW. */
|
|
27
|
-
export const SEMANTIC_INTAKE_MAX_EXTRA_MODELS = 5;
|
|
28
|
-
/** Parent CLI budget covering harness tiers plus extra catalog models. */
|
|
29
|
-
export const SEMANTIC_INTAKE_CLI_TIMEOUT_MS = SEMANTIC_INTAKE_ATTEMPT_TIMEOUT_MS *
|
|
30
|
-
(3 + SEMANTIC_INTAKE_MAX_EXTRA_MODELS);
|
|
31
|
-
const NON_CHAT_MODEL = /embed|whisper|tts|dall-e|dalle|image|rerank|moderation/i;
|
|
32
23
|
const productArtifactBlockers = new Set([
|
|
33
24
|
"PRODUCT_ANALYSIS_NOT_EXECUTABLE",
|
|
34
25
|
"PRODUCT_CLARIFICATION_NOT_EXECUTABLE",
|
|
@@ -135,58 +126,6 @@ export function evaluateSemanticIntakeEligibility(input) {
|
|
|
135
126
|
reason: "structural or engineering-boundary gaps with imported sources",
|
|
136
127
|
};
|
|
137
128
|
}
|
|
138
|
-
function intakeModelKey(ref) {
|
|
139
|
-
return ref.trim().replace(/\\/g, "/").toLowerCase();
|
|
140
|
-
}
|
|
141
|
-
function intakeModelRef(provider, id) {
|
|
142
|
-
const trimmedId = id.trim();
|
|
143
|
-
if (trimmedId.includes("/"))
|
|
144
|
-
return trimmedId.replace(/\\/g, "/");
|
|
145
|
-
return `${provider.trim()}/${trimmedId}`;
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* MED → HIGH → LOW from harness, then Pi-supported catalog models
|
|
149
|
-
* (credentialed first). Duplicates and non-chat ids are skipped.
|
|
150
|
-
*/
|
|
151
|
-
export function buildSemanticIntakeModelQueue(input) {
|
|
152
|
-
const seen = new Set();
|
|
153
|
-
const queue = [];
|
|
154
|
-
const push = (raw) => {
|
|
155
|
-
const ref = raw?.trim().replace(/\\/g, "/");
|
|
156
|
-
if (!ref)
|
|
157
|
-
return;
|
|
158
|
-
const key = intakeModelKey(ref);
|
|
159
|
-
if (seen.has(key))
|
|
160
|
-
return;
|
|
161
|
-
seen.add(key);
|
|
162
|
-
queue.push(ref);
|
|
163
|
-
};
|
|
164
|
-
push(input.harness.MED);
|
|
165
|
-
push(input.harness.HIGH);
|
|
166
|
-
push(input.harness.LOW);
|
|
167
|
-
const extras = [...(input.available ?? [])]
|
|
168
|
-
.filter((entry) => {
|
|
169
|
-
const hay = `${entry.id} ${entry.name ?? ""} ${entry.provider}`;
|
|
170
|
-
return !NON_CHAT_MODEL.test(hay);
|
|
171
|
-
})
|
|
172
|
-
.sort((left, right) => {
|
|
173
|
-
const leftCred = left.hasCredentials === true ? 0 : 1;
|
|
174
|
-
const rightCred = right.hasCredentials === true ? 0 : 1;
|
|
175
|
-
if (leftCred !== rightCred)
|
|
176
|
-
return leftCred - rightCred;
|
|
177
|
-
return intakeModelRef(left.provider, left.id).localeCompare(intakeModelRef(right.provider, right.id));
|
|
178
|
-
});
|
|
179
|
-
let extraCount = 0;
|
|
180
|
-
for (const entry of extras) {
|
|
181
|
-
if (extraCount >= SEMANTIC_INTAKE_MAX_EXTRA_MODELS)
|
|
182
|
-
break;
|
|
183
|
-
const before = queue.length;
|
|
184
|
-
push(intakeModelRef(entry.provider, entry.id));
|
|
185
|
-
if (queue.length > before)
|
|
186
|
-
extraCount += 1;
|
|
187
|
-
}
|
|
188
|
-
return queue;
|
|
189
|
-
}
|
|
190
129
|
export function extractJsonObject(text) {
|
|
191
130
|
const fenced = /```(?:json)?\s*([\s\S]*?)```/iu.exec(text);
|
|
192
131
|
const candidate = fenced?.[1]?.trim() || text.trim();
|
|
@@ -554,7 +493,6 @@ export async function runSemanticIntake(input) {
|
|
|
554
493
|
};
|
|
555
494
|
}
|
|
556
495
|
let assistantText;
|
|
557
|
-
let attemptedModels = [];
|
|
558
496
|
if (input.fixtureAssistantText !== undefined) {
|
|
559
497
|
assistantText = input.fixtureAssistantText;
|
|
560
498
|
}
|
|
@@ -562,33 +500,7 @@ export async function runSemanticIntake(input) {
|
|
|
562
500
|
const execute = input.executePi ?? executePiStep;
|
|
563
501
|
const manifest = await loadHarnessManifest(input.repoRoot);
|
|
564
502
|
const models = resolveExecutorModelMatrices(manifest);
|
|
565
|
-
|
|
566
|
-
try {
|
|
567
|
-
available = input.listAvailableModels
|
|
568
|
-
? await input.listAvailableModels()
|
|
569
|
-
: await listPiSdkAvailableModels();
|
|
570
|
-
}
|
|
571
|
-
catch {
|
|
572
|
-
available = [];
|
|
573
|
-
}
|
|
574
|
-
const queue = buildSemanticIntakeModelQueue({
|
|
575
|
-
harness: models.pi,
|
|
576
|
-
available,
|
|
577
|
-
});
|
|
578
|
-
if (queue.length === 0) {
|
|
579
|
-
await writeAttemptMarker(input.repoRoot, input.taskId, {
|
|
580
|
-
ok: false,
|
|
581
|
-
code: "SEMANTIC_INTAKE_PI_FAILED",
|
|
582
|
-
failureCategory: "unavailable",
|
|
583
|
-
at: new Date().toISOString(),
|
|
584
|
-
attemptedModels: [],
|
|
585
|
-
});
|
|
586
|
-
return {
|
|
587
|
-
ok: false,
|
|
588
|
-
code: "SEMANTIC_INTAKE_PI_FAILED",
|
|
589
|
-
message: "semantic intake has no MED/HIGH/LOW or Pi-supported models to try",
|
|
590
|
-
};
|
|
591
|
-
}
|
|
503
|
+
const model = models.pi.MED;
|
|
592
504
|
const attached = usableDocs
|
|
593
505
|
.map((d) => d.absolutePath)
|
|
594
506
|
.filter((p) => Boolean(p));
|
|
@@ -598,71 +510,39 @@ export async function runSemanticIntake(input) {
|
|
|
598
510
|
documents: usableDocs,
|
|
599
511
|
repoRoot: input.repoRoot,
|
|
600
512
|
});
|
|
601
|
-
const
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
stallTimeoutMs: SEMANTIC_INTAKE_ATTEMPT_TIMEOUT_MS,
|
|
615
|
-
userMessage: "Structure the imported requirement documents into the required JSON only.",
|
|
616
|
-
validateOutput: (text) => {
|
|
617
|
-
try {
|
|
618
|
-
const parsed = parseSemanticRequirementDraft(text);
|
|
619
|
-
assertSemanticDraftAcceptable(parsed);
|
|
620
|
-
return [];
|
|
621
|
-
}
|
|
622
|
-
catch (error) {
|
|
623
|
-
return [error instanceof Error ? error.message : String(error)];
|
|
624
|
-
}
|
|
625
|
-
},
|
|
626
|
-
});
|
|
627
|
-
attempted.push({
|
|
628
|
-
model: result.modelDisplay || modelRef,
|
|
629
|
-
ok: result.ok,
|
|
630
|
-
failureCategory: result.failureCategory,
|
|
631
|
-
timedOut: result.timedOut,
|
|
632
|
-
});
|
|
633
|
-
if (result.ok) {
|
|
634
|
-
chosenText = result.assistantText;
|
|
635
|
-
break;
|
|
513
|
+
const result = await execute({
|
|
514
|
+
attachedFiles: attached,
|
|
515
|
+
modelConfig: resolveDagPiModelConfig(model),
|
|
516
|
+
prompt,
|
|
517
|
+
repoRoot: input.repoRoot,
|
|
518
|
+
step: "analyze",
|
|
519
|
+
toolNames: ["read", "grep", "find", "ls"],
|
|
520
|
+
userMessage: "Structure the imported requirement documents into the required JSON only.",
|
|
521
|
+
validateOutput: (text) => {
|
|
522
|
+
try {
|
|
523
|
+
const parsed = parseSemanticRequirementDraft(text);
|
|
524
|
+
assertSemanticDraftAcceptable(parsed);
|
|
525
|
+
return [];
|
|
636
526
|
}
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
model: modelRef,
|
|
644
|
-
ok: false,
|
|
645
|
-
failureCategory: "unknown",
|
|
646
|
-
});
|
|
647
|
-
lastFailure = message.slice(0, 2000);
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
attemptedModels = attempted;
|
|
651
|
-
if (chosenText === undefined) {
|
|
527
|
+
catch (error) {
|
|
528
|
+
return [error instanceof Error ? error.message : String(error)];
|
|
529
|
+
}
|
|
530
|
+
},
|
|
531
|
+
});
|
|
532
|
+
if (!result.ok) {
|
|
652
533
|
await writeAttemptMarker(input.repoRoot, input.taskId, {
|
|
653
534
|
ok: false,
|
|
654
535
|
code: "SEMANTIC_INTAKE_PI_FAILED",
|
|
655
|
-
failureCategory:
|
|
536
|
+
failureCategory: result.failureCategory,
|
|
656
537
|
at: new Date().toISOString(),
|
|
657
|
-
attemptedModels: attempted,
|
|
658
538
|
});
|
|
659
539
|
return {
|
|
660
540
|
ok: false,
|
|
661
541
|
code: "SEMANTIC_INTAKE_PI_FAILED",
|
|
662
|
-
message: `semantic intake pi failed
|
|
542
|
+
message: `semantic intake pi failed: ${result.failureCategory}: ${result.stderr || result.assistantText}`.slice(0, 2000),
|
|
663
543
|
};
|
|
664
544
|
}
|
|
665
|
-
assistantText =
|
|
545
|
+
assistantText = result.assistantText;
|
|
666
546
|
}
|
|
667
547
|
try {
|
|
668
548
|
const semantic = parseSemanticRequirementDraft(assistantText);
|
|
@@ -696,7 +576,6 @@ export async function runSemanticIntake(input) {
|
|
|
696
576
|
code: "SEMANTIC_INTAKE_OK",
|
|
697
577
|
artifactPath: SEMANTIC_INTAKE_ARTIFACT_REL,
|
|
698
578
|
at: new Date().toISOString(),
|
|
699
|
-
...(attemptedModels.length > 0 ? { attemptedModels } : {}),
|
|
700
579
|
});
|
|
701
580
|
return { ok: true, draft, artifactPath, semantic };
|
|
702
581
|
}
|
|
@@ -19,12 +19,5 @@ export function mergeArtifactCardState(current, incoming) {
|
|
|
19
19
|
const index = current.findIndex((item) => item.artifactId === incoming.artifactId);
|
|
20
20
|
if (index < 0)
|
|
21
21
|
return [...current, incoming];
|
|
22
|
-
return current.map((item, candidate) => candidate === index
|
|
23
|
-
? {
|
|
24
|
-
...item,
|
|
25
|
-
...incoming,
|
|
26
|
-
eventSeq: item.eventSeq ?? incoming.eventSeq,
|
|
27
|
-
activityRef: item.activityRef ?? incoming.activityRef,
|
|
28
|
-
}
|
|
29
|
-
: item);
|
|
22
|
+
return current.map((item, candidate) => candidate === index ? { ...item, ...incoming } : item);
|
|
30
23
|
}
|
|
@@ -50,62 +50,6 @@ function controllerSummary(operation) {
|
|
|
50
50
|
.join("@");
|
|
51
51
|
return safeIdentity(label);
|
|
52
52
|
}
|
|
53
|
-
/** Re-project an already-safe stored run summary through a bounded re-check. */
|
|
54
|
-
function safeRunSummaryProjection(value) {
|
|
55
|
-
if (!value || typeof value !== "object")
|
|
56
|
-
return undefined;
|
|
57
|
-
const record = value;
|
|
58
|
-
const runId = safeIdentity(record.runId);
|
|
59
|
-
const status = safeIdentity(record.status);
|
|
60
|
-
if (!runId || !status)
|
|
61
|
-
return undefined;
|
|
62
|
-
const nodes = Array.isArray(record.nodes)
|
|
63
|
-
? record.nodes
|
|
64
|
-
.filter((node) => Boolean(node && typeof node === "object"))
|
|
65
|
-
.map((node) => ({
|
|
66
|
-
id: safeIdentity(node.id) ?? "",
|
|
67
|
-
status: safeIdentity(node.status) ?? "",
|
|
68
|
-
}))
|
|
69
|
-
.filter((node) => node.id && node.status)
|
|
70
|
-
.slice(0, 20)
|
|
71
|
-
: [];
|
|
72
|
-
const verdict = typeof record.verdict === "string"
|
|
73
|
-
? boundedRedactedText(record.verdict)
|
|
74
|
-
: undefined;
|
|
75
|
-
const failureCategory = typeof record.failureCategory === "string"
|
|
76
|
-
? boundedRedactedText(record.failureCategory)
|
|
77
|
-
: undefined;
|
|
78
|
-
const outOfBounds = Array.isArray(record.outOfBounds)
|
|
79
|
-
? record.outOfBounds
|
|
80
|
-
.filter((entry) => Boolean(entry && typeof entry === "object"))
|
|
81
|
-
.map((entry) => ({
|
|
82
|
-
nodeId: safeIdentity(entry.nodeId) ?? "",
|
|
83
|
-
failureCategory: safeIdentity(entry.failureCategory) ?? "",
|
|
84
|
-
}))
|
|
85
|
-
.filter((entry) => entry.nodeId && entry.failureCategory)
|
|
86
|
-
.slice(0, 20)
|
|
87
|
-
: undefined;
|
|
88
|
-
const nextRec = record.next && typeof record.next === "object"
|
|
89
|
-
? record.next
|
|
90
|
-
: undefined;
|
|
91
|
-
const next = nextRec
|
|
92
|
-
? {
|
|
93
|
-
kind: safeIdentity(nextRec.kind) ?? "",
|
|
94
|
-
description: typeof nextRec.description === "string"
|
|
95
|
-
? boundedRedactedText(nextRec.description)
|
|
96
|
-
: undefined,
|
|
97
|
-
}
|
|
98
|
-
: undefined;
|
|
99
|
-
return {
|
|
100
|
-
runId,
|
|
101
|
-
status,
|
|
102
|
-
nodes,
|
|
103
|
-
...(verdict ? { verdict } : {}),
|
|
104
|
-
...(failureCategory ? { failureCategory } : {}),
|
|
105
|
-
...(outOfBounds && outOfBounds.length > 0 ? { outOfBounds } : {}),
|
|
106
|
-
...(next && next.kind ? { next } : {}),
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
53
|
/** Chat/browser-safe projection. Never includes actionParams, CLI args, paths or raw results. */
|
|
110
54
|
export function projectOperationForChat(operation) {
|
|
111
55
|
const params = operation.actionParams ?? {};
|
|
@@ -117,16 +61,12 @@ export function projectOperationForChat(operation) {
|
|
|
117
61
|
const featureId = safeIdentity(params.featureId);
|
|
118
62
|
const workerRunId = safeIdentity(params.workerRunId);
|
|
119
63
|
const controller = controllerSummary(operation);
|
|
120
|
-
const runSummary = safeRunSummaryProjection(operation.runSummary);
|
|
121
64
|
const previewHash = createHash("sha256")
|
|
122
65
|
.update(JSON.stringify({
|
|
123
66
|
stdoutPreview,
|
|
124
67
|
stderrPreview,
|
|
125
68
|
summaryPreview,
|
|
126
69
|
state: operation.state,
|
|
127
|
-
// runSummary participates in the dedupe hash: terminal summary updates
|
|
128
|
-
// must not be swallowed by state+previewHash dedup (2026-08-14).
|
|
129
|
-
runSummary: runSummary ?? null,
|
|
130
70
|
}))
|
|
131
71
|
.digest("hex");
|
|
132
72
|
return {
|
|
@@ -148,76 +88,6 @@ export function projectOperationForChat(operation) {
|
|
|
148
88
|
...(operation.errorCode
|
|
149
89
|
? { errorCode: safeIdentity(operation.errorCode) }
|
|
150
90
|
: {}),
|
|
151
|
-
...(runSummary ? { runSummary } : {}),
|
|
152
|
-
previewHash,
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
/** Action families that leave a user-inspectable durable outcome. */
|
|
156
|
-
const ARTIFACT_EMITTING_ACTIONS = new Set([
|
|
157
|
-
"taskAdvance",
|
|
158
|
-
"taskCreate",
|
|
159
|
-
"contractApply",
|
|
160
|
-
"dagReport",
|
|
161
|
-
"taskImportPrd",
|
|
162
|
-
"importPrd",
|
|
163
|
-
]);
|
|
164
|
-
/**
|
|
165
|
-
* Project a terminal operation into an「产物」rail card (2026-08-16).
|
|
166
|
-
* Mirrors `ArtifactCardState` (schemaVersion 1) so the existing frontend
|
|
167
|
-
* parser renders it without any change. Best-effort: operations without a
|
|
168
|
-
* durable outcome (pure status reads) return undefined.
|
|
169
|
-
*/
|
|
170
|
-
function artifactFromTerminalOperation(operation, projection) {
|
|
171
|
-
if (!ARTIFACT_EMITTING_ACTIONS.has(operation.action))
|
|
172
|
-
return undefined;
|
|
173
|
-
// Failed/interrupted actions leave nothing inspectable as a deliverable.
|
|
174
|
-
if (operation.state !== "succeeded")
|
|
175
|
-
return undefined;
|
|
176
|
-
const taskId = projection.taskId;
|
|
177
|
-
const kind = taskId
|
|
178
|
-
? "contract"
|
|
179
|
-
: "operation";
|
|
180
|
-
const title = taskId ? `任务 ${taskId}` : `执行 ${operation.action}`;
|
|
181
|
-
const summary = projection.runSummary?.status
|
|
182
|
-
? `运行 ${projection.runSummary.runId} · ${projection.runSummary.status}`
|
|
183
|
-
: projection.summaryPreview || undefined;
|
|
184
|
-
const reportLines = [];
|
|
185
|
-
if (projection.runSummary) {
|
|
186
|
-
const rs = projection.runSummary;
|
|
187
|
-
reportLines.push(`运行: ${rs.runId}`);
|
|
188
|
-
reportLines.push(`状态: ${rs.status}`);
|
|
189
|
-
if (rs.verdict)
|
|
190
|
-
reportLines.push(`结论: ${rs.verdict}`);
|
|
191
|
-
if (rs.failureCategory)
|
|
192
|
-
reportLines.push(`失败归类: ${rs.failureCategory}`);
|
|
193
|
-
if (Array.isArray(rs.nodes)) {
|
|
194
|
-
for (const node of rs.nodes.slice(0, 30)) {
|
|
195
|
-
reportLines.push(`- ${node.id}: ${node.status}`);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
if (projection.stdoutPreview) {
|
|
200
|
-
reportLines.push("", "--- stdout 摘要 ---", projection.stdoutPreview);
|
|
201
|
-
}
|
|
202
|
-
const previewHash = createHash("sha256")
|
|
203
|
-
.update(JSON.stringify({
|
|
204
|
-
operationId: operation.operationId,
|
|
205
|
-
state: operation.state,
|
|
206
|
-
taskId: taskId ?? null,
|
|
207
|
-
runSummary: projection.runSummary ?? null,
|
|
208
|
-
stdoutPreview: projection.stdoutPreview ?? null,
|
|
209
|
-
}))
|
|
210
|
-
.digest("hex");
|
|
211
|
-
return {
|
|
212
|
-
schemaVersion: 1,
|
|
213
|
-
kind,
|
|
214
|
-
artifactId: `operation-${operation.operationId}`,
|
|
215
|
-
title,
|
|
216
|
-
...(summary ? { summary } : {}),
|
|
217
|
-
...(reportLines.length > 0
|
|
218
|
-
? { reportText: reportLines.join("\n").slice(0, 8000) }
|
|
219
|
-
: {}),
|
|
220
|
-
inspectHref: projection.inspectHref,
|
|
221
91
|
previewHash,
|
|
222
92
|
};
|
|
223
93
|
}
|
|
@@ -323,7 +193,6 @@ export function projectChatToolEventPayload(input) {
|
|
|
323
193
|
resultPreview: preview,
|
|
324
194
|
previewHash,
|
|
325
195
|
isError: Boolean(input.isError),
|
|
326
|
-
...(input.operationId ? { operationId: input.operationId } : {}),
|
|
327
196
|
};
|
|
328
197
|
}
|
|
329
198
|
export function projectOperationEventSummary(event) {
|
|
@@ -481,27 +350,6 @@ export function createChatOperationLinker(options) {
|
|
|
481
350
|
},
|
|
482
351
|
});
|
|
483
352
|
}
|
|
484
|
-
// Artifact projection (2026-08-16): the sidebar「产物」tab was permanently
|
|
485
|
-
// empty because nothing in the server ever emitted `artifact-ref`. On a
|
|
486
|
-
// terminal operation with a durable outcome (task created / advanced /
|
|
487
|
-
// contract applied / report produced) project one artifact card so the
|
|
488
|
-
// user can inspect the deliverable straight from the rail.
|
|
489
|
-
if (isTerminalOperationState(operation.state)) {
|
|
490
|
-
const artifact = artifactFromTerminalOperation(operation, projection);
|
|
491
|
-
if (artifact) {
|
|
492
|
-
const artifactDuplicate = options.chatEvents
|
|
493
|
-
.snapshot(input.sessionId)
|
|
494
|
-
.some((event) => event.kind === "artifact-ref" &&
|
|
495
|
-
event.data
|
|
496
|
-
.artifact?.previewHash === artifact.previewHash);
|
|
497
|
-
if (!artifactDuplicate) {
|
|
498
|
-
options.chatEvents.append(input.sessionId, input.turnId, {
|
|
499
|
-
kind: "artifact-ref",
|
|
500
|
-
data: { artifact },
|
|
501
|
-
});
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
353
|
if (isTerminalOperationState(operation.state)) {
|
|
506
354
|
const key = keyFor(input.sessionId, input.operationId);
|
|
507
355
|
followers.get(key)?.();
|
|
@@ -32,14 +32,6 @@ export function mergeHumanGateCardState(cards, next) {
|
|
|
32
32
|
if (index < 0)
|
|
33
33
|
return [...cards, next];
|
|
34
34
|
const copy = [...cards];
|
|
35
|
-
|
|
36
|
-
if (!current)
|
|
37
|
-
return [...cards, next];
|
|
38
|
-
copy[index] = {
|
|
39
|
-
...current,
|
|
40
|
-
...next,
|
|
41
|
-
eventSeq: current.eventSeq ?? next.eventSeq,
|
|
42
|
-
activityRef: current.activityRef ?? next.activityRef,
|
|
43
|
-
};
|
|
35
|
+
copy[index] = next;
|
|
44
36
|
return copy;
|
|
45
37
|
}
|
|
@@ -2,24 +2,7 @@ export function mergeOperationCardState(current, incoming) {
|
|
|
2
2
|
const index = current.findIndex((operation) => operation.operationId === incoming.operationId);
|
|
3
3
|
if (index < 0)
|
|
4
4
|
return [...current, incoming];
|
|
5
|
-
return current.map((operation, candidateIndex) => {
|
|
6
|
-
if (candidateIndex !== index)
|
|
7
|
-
return operation;
|
|
8
|
-
let eventSeq = operation.eventSeq;
|
|
9
|
-
if (eventSeq === undefined)
|
|
10
|
-
eventSeq = incoming.eventSeq;
|
|
11
|
-
else if (incoming.eventSeq !== undefined) {
|
|
12
|
-
eventSeq = Math.min(eventSeq, incoming.eventSeq);
|
|
13
|
-
}
|
|
14
|
-
return {
|
|
15
|
-
...operation,
|
|
16
|
-
...incoming,
|
|
17
|
-
...(eventSeq === undefined ? {} : { eventSeq }),
|
|
18
|
-
updatedSeq: Math.max(operation.updatedSeq ?? operation.eventSeq ?? 0, incoming.updatedSeq ?? incoming.eventSeq ?? 0),
|
|
19
|
-
toolCallId: operation.toolCallId ?? incoming.toolCallId,
|
|
20
|
-
activityRef: operation.activityRef ?? incoming.activityRef,
|
|
21
|
-
};
|
|
22
|
-
});
|
|
5
|
+
return current.map((operation, candidateIndex) => candidateIndex === index ? { ...operation, ...incoming } : operation);
|
|
23
6
|
}
|
|
24
7
|
export function operationFromEventPayload(payload) {
|
|
25
8
|
const operation = payload.operation;
|
|
@@ -36,57 +19,5 @@ export function operationFromEventPayload(payload) {
|
|
|
36
19
|
typeof candidate.previewHash !== "string") {
|
|
37
20
|
return undefined;
|
|
38
21
|
}
|
|
39
|
-
|
|
40
|
-
// field and drop it entirely when malformed (legacy events simply lack it).
|
|
41
|
-
const safe = safeRunSummary(candidate.runSummary);
|
|
42
|
-
const { runSummary: _unsafeRunSummary, ...base } = candidate;
|
|
43
|
-
return {
|
|
44
|
-
...base,
|
|
45
|
-
...(safe ? { runSummary: safe } : {}),
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
/** Field-by-field validation for the optional browser-side runSummary. */
|
|
49
|
-
function safeRunSummary(value) {
|
|
50
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
51
|
-
return undefined;
|
|
52
|
-
const rec = value;
|
|
53
|
-
if (typeof rec.runId !== "string" || !rec.runId)
|
|
54
|
-
return undefined;
|
|
55
|
-
if (typeof rec.status !== "string" || !rec.status)
|
|
56
|
-
return undefined;
|
|
57
|
-
let nodes = [];
|
|
58
|
-
if (Array.isArray(rec.nodes)) {
|
|
59
|
-
nodes = rec.nodes
|
|
60
|
-
.flatMap((node) => {
|
|
61
|
-
if (!node || typeof node !== "object")
|
|
62
|
-
return [];
|
|
63
|
-
const id = String(node.id ?? "");
|
|
64
|
-
const status = String(node.status ?? "");
|
|
65
|
-
if (!id || !status)
|
|
66
|
-
return [];
|
|
67
|
-
return [{ id, status }];
|
|
68
|
-
})
|
|
69
|
-
.slice(0, 20);
|
|
70
|
-
}
|
|
71
|
-
let next;
|
|
72
|
-
if (rec.next && typeof rec.next === "object") {
|
|
73
|
-
const nextRecord = rec.next;
|
|
74
|
-
next = {
|
|
75
|
-
kind: String(nextRecord.kind ?? ""),
|
|
76
|
-
...(typeof nextRecord.description === "string"
|
|
77
|
-
? { description: nextRecord.description }
|
|
78
|
-
: {}),
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
const out = {
|
|
82
|
-
runId: rec.runId,
|
|
83
|
-
status: rec.status,
|
|
84
|
-
nodes,
|
|
85
|
-
...(typeof rec.verdict === "string" ? { verdict: rec.verdict } : {}),
|
|
86
|
-
...(typeof rec.failureCategory === "string"
|
|
87
|
-
? { failureCategory: rec.failureCategory }
|
|
88
|
-
: {}),
|
|
89
|
-
...(next && next.kind ? { next } : {}),
|
|
90
|
-
};
|
|
91
|
-
return out;
|
|
22
|
+
return candidate;
|
|
92
23
|
}
|