@tea-agent/loop-agent 0.41.0 → 0.41.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/CHANGELOG.md +267 -33
- package/dist/application/dag/generate-task-dag.js +75 -19
- package/dist/application/task-lifecycle/advance.js +24 -5
- package/dist/application/task-lifecycle/observe.js +171 -17
- package/dist/application/task-lifecycle/plan-transitions.js +42 -7
- package/dist/application/task-lifecycle/recommendations.js +13 -2
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +7 -0
- package/dist/cli/program.js +5 -0
- package/dist/commands/client-recovery.js +3 -0
- package/dist/commands/dag-follow-up.js +138 -0
- package/dist/commands/init.js +27 -1
- package/dist/commands/task-advance.js +19 -0
- package/dist/executors/dag-pi-executor.js +3784 -231
- package/dist/executors/pi-executor.js +15 -4
- package/dist/executors/pi-read-budget-policy.js +239 -0
- package/dist/executors/shell-executor.js +945 -217
- package/dist/executors/shell-write-guard.js +7 -0
- package/dist/shared/dag-failure-category.js +12 -0
- package/dist/shared/openspec-spec.js +70 -4
- package/dist/shared/operator/capabilities.js +7 -0
- package/dist/task/config-types.js +34 -7
- package/dist/task/contract/adopt.js +4 -0
- package/dist/task/contract/import-revision.js +4 -0
- package/dist/task/contract/project.js +3 -0
- package/dist/task/contract/schema.js +2 -1
- package/dist/task/frontend-project-capability.js +203 -20
- package/dist/task/runtime.js +5 -2
- package/dist/task/source-prepare/build-draft.js +3 -3
- package/dist/task/source-prepare/fragment-inventory.js +15 -9
- package/dist/task/source-prepare/prepare.js +89 -1
- package/dist/task/source-prepare/semantic-intake.js +6 -2
- package/dist/task/source-references.js +22 -1
- package/dist/worker/console/chat/pi-runtime.js +2 -3
- package/dist/worker/console/chat/routes.js +3 -12
- package/dist/worker/console/chat/semantic-activity.js +11 -12
- package/dist/worker/console/chat/session-stats.js +0 -96
- package/dist/worker/console/chat/turn-process.js +4 -28
- package/dist/worker/console/frontend-human-decision-adapter.js +19 -0
- package/dist/worker/console/frontend-split-operation-adapter.js +20 -0
- package/dist/worker/console/index.js +3 -0
- package/dist/worker/console/operation-store.js +226 -6
- package/dist/worker/console/operator-actions.js +169 -7
- package/dist/worker/console/pi-plugins.js +1 -3
- package/dist/worker/console/server.js +3 -0
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-DwVJxUxz.js → abnfDiagram-N423BO3Z-Bho4we4c.js} +1 -1
- package/dist/worker/console/static/assets/{arc-DqfCcXdu.js → arc-TBDkTeK0.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-qMVtggRv.js → architectureDiagram-T3A2C74G-BF_fZrn2.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-ECrV38dh.js → blockDiagram-VBNYF7ZC-CCnPgUe2.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DBaaLqQv.js → c4Diagram-5PPSVZJV-BCYZJLAT.js} +1 -1
- package/dist/worker/console/static/assets/channel-bnXW0U3C.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-BvfNHkAz.js → chunk-2GRJ4B5K-D8K6adhU.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-C5StiIbc.js → chunk-2Q5K7J3B-DkT1Q78b.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-HabGiJkS.js → chunk-5RXB4S5H-DuhvpIr7.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BiTg127i.js → chunk-5VM5RSS4-BnK6dC49.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-CZsXBPuR.js → chunk-6Q2QTUOP-DPz__z7r.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-D-r1MxLK.js → chunk-GF5L2VYU-0kkppM2j.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-DQxb9plr.js → chunk-JWPE2WC7-BB41iK5t.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DMndGvCT.js → chunk-KBJHAD2P-CiVrxKHU.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-ByL4BgOJ.js → chunk-RYQCIY6F-C2h9Xje1.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-BVe1b1gL.js → chunk-XXDRQBXY-3iOSHdbM.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-rZpq_twR.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-rZpq_twR.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BAyGzN8j.js → cose-bilkent-JH36ORCC-BN3A7tkk.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-C5yVYU7E.js → cynefin-VYW2F7L2-CTuBy0Or.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-x-MwsEm9.js → cynefinDiagram-MW4NZA55-wb6tUlMN.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-CT5Tle5F.js → dagre-VZM6K2ZE-GP1Ivx8B.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-BARERhlS.js → diagram-7IWD3JNH-ejrcK6L0.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BwfUz0VM.js → diagram-B4RE2ZJO-YFkCCvUU.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-Mk4r2WvJ.js → diagram-LBJQPF4R-B4rrvIaL.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-KUrxuBs0.js → diagram-Q27KOJAE-C-YIZcUf.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-CjZOceZn.js → diagram-UB23O5K3-BPF5wcL1.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-rnwUCiEI.js → ebnfDiagram-BXEA7PRR-CU5SzXjO.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DM4j23FM.js → erDiagram-JOGREHBK-CpL414DE.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-DD5HdDEs.js → flowDiagram-UKHOOZJN-MrhLzNau.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CIAGSF47.js → ganttDiagram-PKOTCBZU-Bg4qFuUo.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-DbwFbVEL.js → gitGraphDiagram-DS77QQ5N-ColxSXLR.js} +1 -1
- package/dist/worker/console/static/assets/index-D3CC4eUz.css +1 -0
- package/dist/worker/console/static/assets/index-DLt4ZFvD.js +437 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-B-Pb0T66.js → infoDiagram-6WML65LV-Czvtm6gc.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-B9Unk-YX.js → ishikawaDiagram-WSZJBQD7-B7Xp8HyJ.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-C2F3JkPt.js → journeyDiagram-NVQOT4AX-DWYZcvph.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-Bl8-Xcoz.js → kanban-definition-27J2QSJJ-KTLZRUjx.js} +1 -1
- package/dist/worker/console/static/assets/{linear-yK4uZmC5.js → linear-_Exn7bfl.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-Bpa6PmuE.js → mermaid.core-Cxg8kX8V.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-BfGRoCSe.js → mindmap-definition-FAOFIHXS-oKrlo2Bt.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-Bz6_HtST.js → pegDiagram-VL7TDLO6-XAeteiNd.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-i0Umbsby.js → pieDiagram-7S7Q4E2Y-BTf1K-lp.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-DaVQBobe.js → quadrantDiagram-CIZ2JOQS-C0IlY0Jf.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-DMTGnSq7.js → railroadDiagram-AXF67PYL-BVbKtZhW.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-2ao_DaSq.js → requirementDiagram-LRYGKXZP-C5e4JFcr.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-2SG1lTB9.js → sankeyDiagram-W5VNT64P-Cz30hc29.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BQYOLybR.js → sequenceDiagram-SI44F4Z6-DS33n6d0.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-C8g3yjuI.js → sizeCapture-X5ZJPWSS-46w7v-9T.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-q_F8nAQG.js → stateDiagram-OKZ733FA-pbcD5SsZ.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-zBMYiUyi.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-8LrWpH2s.js → swimlanes-SLNWSIFB-DuivYBSj.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-d_rms-5i.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-BQoY3qzl.js → timeline-definition-Z64GVDOM-Cb9VZVIl.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BDu1UjSp.js → vennDiagram-T6HMQDX7-D2-mfOgR.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-C48M7Y0a.js → wardleyDiagram-T6FBY63Y-DeS2aC76.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-YU6FTl_F.js → xychartDiagram-ELKLHX3M-WGsrFjxH.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/active-run-badge.js +17 -0
- package/dist/worker/console/static-src/app/useRunProgress.js +2 -9
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +0 -2
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +0 -6
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +3 -28
- package/dist/worker/console/static-src/pages/tasks/run-panel-progress.js +0 -106
- package/dist/worker/console/workspace-context.js +111 -1
- package/dist/worker/materialize/frontend-split-task-materializer.js +72 -0
- package/dist/worker/observability/read-model.js +0 -4
- package/dist/worker/observe/static/dag-node-purpose.js +5 -0
- package/dist/worker/observe/static/views/dag-inspector.js +7 -2
- package/dist/worker/observe/static/views/session-timeline.js +15 -46
- package/dist/workflows/dag/backend-test-result-contract.js +1 -1
- package/dist/workflows/dag/contract-validator-registrations.js +1 -2
- package/dist/workflows/dag/dag-retry-schema.js +138 -0
- package/dist/workflows/dag/frontend-closeout.js +221 -0
- package/dist/workflows/dag/frontend-design-policy.js +400 -0
- package/dist/workflows/dag/frontend-human-decision.js +182 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +932 -162
- package/dist/workflows/dag/frontend-plan-render.js +2 -1
- package/dist/workflows/dag/frontend-prewrite-gate.js +255 -349
- package/dist/workflows/dag/frontend-provider-capability-matrix.js +159 -0
- package/dist/workflows/dag/frontend-read-budgets.js +12 -0
- package/dist/workflows/dag/frontend-recovery-capsule.js +455 -0
- package/dist/workflows/dag/frontend-recovery-controller.js +202 -0
- package/dist/workflows/dag/frontend-recovery-lineage.js +178 -0
- package/dist/workflows/dag/frontend-recovery-plan.js +17 -10
- package/dist/workflows/dag/frontend-recovery-run.js +33 -20
- package/dist/workflows/dag/frontend-repair.js +1 -432
- package/dist/workflows/dag/frontend-review-context.js +261 -15
- package/dist/workflows/dag/frontend-review-findings.js +270 -0
- package/dist/workflows/dag/frontend-shadow-dual-write.js +914 -0
- package/dist/workflows/dag/frontend-shape-capsule-store.js +191 -0
- package/dist/workflows/dag/frontend-shape-facts.js +409 -0
- package/dist/workflows/dag/frontend-shape.js +427 -0
- package/dist/workflows/dag/frontend-source-fidelity-ledger.js +108 -0
- package/dist/workflows/dag/frontend-split-application-service.js +203 -0
- package/dist/workflows/dag/frontend-split-orchestrator.js +899 -0
- package/dist/workflows/dag/frontend-typed-event-store.js +452 -0
- package/dist/workflows/dag/frontend-typed-event-transaction.js +180 -0
- package/dist/workflows/dag/frontend-verification-trace.js +249 -24
- package/dist/workflows/dag/frontend-worktree-diff.js +250 -17
- package/dist/workflows/dag/frontend-writer-admission.js +285 -0
- package/dist/workflows/dag/frontend-writer-status.js +256 -0
- package/dist/workflows/dag/init-hybrid.js +685 -494
- package/dist/workflows/dag/interrupt-request.js +7 -0
- package/dist/workflows/dag/node-execution.js +732 -4
- package/dist/workflows/dag/prompt.js +34 -1
- package/dist/workflows/dag/recovery-lease.js +80 -0
- package/dist/workflows/dag/report.js +37 -1
- package/dist/workflows/dag/rerun-feedback.js +256 -1
- package/dist/workflows/dag/rerun-plan.js +10 -2
- package/dist/workflows/dag/rerun-run.js +23 -0
- package/dist/workflows/dag/rerun-task.js +29 -0
- package/dist/workflows/dag/retry-policy.js +214 -104
- package/dist/workflows/dag/runner.js +429 -122
- package/dist/workflows/dag/scheduler.js +114 -16
- package/dist/workflows/dag/types.js +239 -12
- package/dist/workflows/dag/validate.js +28 -12
- package/docs/init-surface.manifest.json +30 -12
- package/docs/skills/vetted-skill-registry.md +4 -2
- package/docs/templates/README.md +2 -0
- package/docs/templates/agent-dag-report.schema.json +8 -2
- package/docs/templates/agent-dag.schema.json +1 -1
- package/docs/templates/frontend-implementation-contract.schema.json +4 -1
- package/docs/templates/frontend-implementation-dag.json +89 -0
- package/docs/templates/spec-registry.schema.json +45 -0
- package/harness.json +3 -3
- package/package.json +1 -1
- package/skills/frontend-bounded-implement/SKILL.md +15 -14
- package/skills/frontend-bounded-implement/references/code-standards.md +19 -0
- package/skills/frontend-contract/SKILL.md +23 -0
- package/skills/frontend-contract/references/contract-protocol.md +34 -0
- package/skills/frontend-design-review/SKILL.md +22 -41
- package/skills/frontend-plan/SKILL.md +26 -0
- package/skills/frontend-plan/references/decision-contract.md +37 -0
- package/skills/frontend-plan/references/design-decisions.md +17 -0
- package/skills/frontend-review/SKILL.md +20 -15
- package/skills/frontend-review/references/review-findings.md +6 -7
- package/skills/frontend-scout/SKILL.md +25 -0
- package/skills/frontend-scout/references/design-evidence.md +16 -0
- package/skills/frontend-scout/references/scout-evidence.md +23 -0
- package/skills/frontend-verification/SKILL.md +1 -1
- package/skills/loop-agent/references/command-reference.md +1 -0
- package/skills/loop-agent/references/hybrid-dag.md +2 -2
- package/dist/worker/console/static/assets/channel-CL1NRAmS.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-PBGzzjZA.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-PBGzzjZA.js +0 -1
- package/dist/worker/console/static/assets/index-BfleX-qj.css +0 -1
- package/dist/worker/console/static/assets/index-Bih9w9h9.js +0 -451
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DrxGBDJu.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BhpDxzAf.js +0 -8
- package/dist/worker/console/static-src/operator-chat/dag-progress-link.js +0 -22
- package/skills/frontend-implementation/SKILL.md +0 -52
- package/skills/frontend-implementation/references/code-standards.md +0 -33
- package/skills/frontend-implementation/references/design-spec.md +0 -56
- package/skills/frontend-implementation/references/node-contracts.md +0 -31
|
@@ -35,25 +35,8 @@ function toProgressNode(node) {
|
|
|
35
35
|
startedAt: typeof node.startedAt === "string" ? node.startedAt : undefined,
|
|
36
36
|
durationMs: typeof node.durationMs === "number" ? node.durationMs : undefined,
|
|
37
37
|
errorPreview: typeof node.errorPreview === "string" ? node.errorPreview : undefined,
|
|
38
|
-
lastMeaningfulProgressAt: typeof node.lastMeaningfulProgressAt === "string"
|
|
39
|
-
? node.lastMeaningfulProgressAt
|
|
40
|
-
: undefined,
|
|
41
|
-
livenessStatus: typeof node.livenessStatus === "string" ? node.livenessStatus : undefined,
|
|
42
38
|
};
|
|
43
39
|
}
|
|
44
|
-
function latestIso(values) {
|
|
45
|
-
let latest = null;
|
|
46
|
-
for (const iso of values) {
|
|
47
|
-
if (!iso)
|
|
48
|
-
continue;
|
|
49
|
-
const time = Date.parse(iso);
|
|
50
|
-
if (!Number.isFinite(time))
|
|
51
|
-
continue;
|
|
52
|
-
if (!latest || time > latest.time)
|
|
53
|
-
latest = { iso, time };
|
|
54
|
-
}
|
|
55
|
-
return latest?.iso ?? null;
|
|
56
|
-
}
|
|
57
40
|
function elapsedSince(iso, now) {
|
|
58
41
|
if (!iso)
|
|
59
42
|
return null;
|
|
@@ -75,8 +58,6 @@ export function summarizeRunProgress(dag, now = Date.now()) {
|
|
|
75
58
|
stalled: false,
|
|
76
59
|
currentNodeElapsedMs: null,
|
|
77
60
|
runElapsedMs: null,
|
|
78
|
-
lastMeaningfulProgressAt: null,
|
|
79
|
-
observedAtMs: now,
|
|
80
61
|
};
|
|
81
62
|
}
|
|
82
63
|
const rawNodes = Array.isArray(dag.nodes) ? dag.nodes : [];
|
|
@@ -105,8 +86,6 @@ export function summarizeRunProgress(dag, now = Date.now()) {
|
|
|
105
86
|
runElapsedMs: terminal
|
|
106
87
|
? (runDurationMs ?? elapsedSince(runStartedAt, now))
|
|
107
88
|
: elapsedSince(runStartedAt, now),
|
|
108
|
-
lastMeaningfulProgressAt: latestIso(nodes.map((node) => node.lastMeaningfulProgressAt)),
|
|
109
|
-
observedAtMs: now,
|
|
110
89
|
};
|
|
111
90
|
}
|
|
112
91
|
const TERMINAL_RUN_PROGRESS_STATUSES = new Set([
|
|
@@ -129,88 +108,3 @@ export function formatDurationMs(ms) {
|
|
|
129
108
|
return `${minutes} 分 ${seconds} 秒`;
|
|
130
109
|
return `${seconds} 秒`;
|
|
131
110
|
}
|
|
132
|
-
function friendlyRunPhase(node) {
|
|
133
|
-
if (!node)
|
|
134
|
-
return "正在等待下一阶段";
|
|
135
|
-
const searchable = `${node.nodeId} ${node.label}`.toLowerCase();
|
|
136
|
-
if (/contract|boundary|intake/.test(searchable))
|
|
137
|
-
return "正在确认执行边界";
|
|
138
|
-
if (/scout|explore|research/.test(searchable))
|
|
139
|
-
return "正在梳理影响范围";
|
|
140
|
-
if (/plan|design/.test(searchable))
|
|
141
|
-
return "正在规划实施步骤";
|
|
142
|
-
if (/implement|repair|fix|write/.test(searchable))
|
|
143
|
-
return "正在实现本次修改";
|
|
144
|
-
if (/verify|test|lint|review|check/.test(searchable))
|
|
145
|
-
return "正在验证本次修改";
|
|
146
|
-
if (/closeout|handoff|report/.test(searchable))
|
|
147
|
-
return "正在整理执行结果";
|
|
148
|
-
if (node.label && node.label !== node.nodeId)
|
|
149
|
-
return `正在进行${node.label}`;
|
|
150
|
-
return "正在推进当前阶段";
|
|
151
|
-
}
|
|
152
|
-
function formatMeaningfulAge(iso, now) {
|
|
153
|
-
if (!iso)
|
|
154
|
-
return "等待首个执行进展";
|
|
155
|
-
const time = Date.parse(iso);
|
|
156
|
-
if (!Number.isFinite(time))
|
|
157
|
-
return "等待首个执行进展";
|
|
158
|
-
const seconds = Math.max(0, Math.floor((now - time) / 1000));
|
|
159
|
-
if (seconds < 60)
|
|
160
|
-
return `最近进展 ${seconds} 秒前`;
|
|
161
|
-
const minutes = Math.floor(seconds / 60);
|
|
162
|
-
if (minutes < 60)
|
|
163
|
-
return `最近进展 ${minutes} 分钟前`;
|
|
164
|
-
return `最近进展 ${Math.floor(minutes / 60)} 小时前`;
|
|
165
|
-
}
|
|
166
|
-
/** Human-facing copy shared by the task panel and Operator Chat progress row. */
|
|
167
|
-
export function presentRunProgress(summary, now = Date.now()) {
|
|
168
|
-
if (!summary || !summary.effectiveStatus) {
|
|
169
|
-
return {
|
|
170
|
-
kind: "pending",
|
|
171
|
-
title: "DAG 正在启动",
|
|
172
|
-
detail: "正在等待首个执行进展",
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
const status = summary.effectiveStatus;
|
|
176
|
-
const liveElapsed = summary.runElapsedMs == null
|
|
177
|
-
? null
|
|
178
|
-
: summary.runElapsedMs +
|
|
179
|
-
(summary.terminal ? 0 : Math.max(0, now - summary.observedAtMs));
|
|
180
|
-
const elapsed = formatDurationMs(liveElapsed);
|
|
181
|
-
const progressDetail = `${friendlyRunPhase(summary.running)} · 已完成 ${summary.finished}/${summary.total} 步 · ${formatMeaningfulAge(summary.lastMeaningfulProgressAt, now)}`;
|
|
182
|
-
if (status === "running") {
|
|
183
|
-
return { kind: "progress", title: `DAG 执行中 · 已运行 ${elapsed}`, detail: progressDetail };
|
|
184
|
-
}
|
|
185
|
-
if (status === "running-quiet") {
|
|
186
|
-
return { kind: "quiet", title: `DAG 仍在执行 · 已运行 ${elapsed}`, detail: progressDetail };
|
|
187
|
-
}
|
|
188
|
-
if (status === "running-suspected-stall") {
|
|
189
|
-
return { kind: "warning", title: `执行可能停滞 · 已运行 ${elapsed}`, detail: progressDetail };
|
|
190
|
-
}
|
|
191
|
-
if (["needs-attention", "paused", "interrupted"].includes(status)) {
|
|
192
|
-
return { kind: "warning", title: "DAG 需要处理", detail: progressDetail };
|
|
193
|
-
}
|
|
194
|
-
if (status === "finished") {
|
|
195
|
-
return {
|
|
196
|
-
kind: "success",
|
|
197
|
-
title: `DAG 已完成 · 总用时 ${elapsed}`,
|
|
198
|
-
detail: `已完成 ${summary.finished}/${summary.total} 步 · ${formatMeaningfulAge(summary.lastMeaningfulProgressAt, now)}`,
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
if (status === "partial_failed" || status === "failed") {
|
|
202
|
-
return {
|
|
203
|
-
kind: "error",
|
|
204
|
-
title: `DAG 未完成 · 总用时 ${elapsed}`,
|
|
205
|
-
detail: `已完成 ${summary.finished}/${summary.total} 步 · ${formatMeaningfulAge(summary.lastMeaningfulProgressAt, now)}`,
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
if (status === "pending") {
|
|
209
|
-
return { kind: "pending", title: "DAG 正在启动", detail: progressDetail };
|
|
210
|
-
}
|
|
211
|
-
return {
|
|
212
|
-
kind: "warning",
|
|
213
|
-
title: `DAG 已结束 · 总用时 ${elapsed}`,
|
|
214
|
-
detail: `已完成 ${summary.finished}/${summary.total} 步`,
|
|
215
|
-
};
|
|
216
|
-
}
|
|
@@ -19,9 +19,10 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import { randomBytes } from "node:crypto";
|
|
21
21
|
import { existsSync } from "node:fs";
|
|
22
|
+
import { rm } from "node:fs/promises";
|
|
22
23
|
import path from "node:path";
|
|
23
24
|
import { LoopAgentClient } from "../loop-agent/loop-agent-client.js";
|
|
24
|
-
import { openConsoleAppData } from "./app-data.js";
|
|
25
|
+
import { openConsoleAppData, sha256Json, writeSecureJson, } from "./app-data.js";
|
|
25
26
|
import { DagConfirmationStore } from "./dag-confirmation.js";
|
|
26
27
|
import { DagExecutionReceiptStore } from "./dag-execution-receipt.js";
|
|
27
28
|
import { DraftStore } from "./draft-store.js";
|
|
@@ -38,6 +39,9 @@ import { GoalRoundDriver } from "./chat/goal-round-driver.js";
|
|
|
38
39
|
import { driveGoalRound, recoverDeferredTurns } from "./chat/routes.js";
|
|
39
40
|
import { createOperationEventStore, } from "./operation-sse.js";
|
|
40
41
|
import { OperationStore } from "./operation-store.js";
|
|
42
|
+
import { createFrontendSplitOperationAdapter, } from "./frontend-split-operation-adapter.js";
|
|
43
|
+
import { createFrontendHumanDecisionAdapter } from "./frontend-human-decision-adapter.js";
|
|
44
|
+
import { FrontendSplitTaskMaterializer, } from "../materialize/frontend-split-task-materializer.js";
|
|
41
45
|
import { probePiReadiness, } from "./pi-readiness.js";
|
|
42
46
|
import { resolveSiblingLoopAgentBin } from "./sibling-controller.js";
|
|
43
47
|
import { NightAuxTicker } from "./night-aux-ticker.js";
|
|
@@ -73,6 +77,106 @@ export async function createWorkspaceContext(record, deps) {
|
|
|
73
77
|
const events = createOperationEventStore({
|
|
74
78
|
persistenceDir: path.join(appData.operations, "events"),
|
|
75
79
|
});
|
|
80
|
+
// Frontend split runtime is repo-scoped: materialization, durable schedule
|
|
81
|
+
// intents, and aggregate closeout must use this workspace's app-data and
|
|
82
|
+
// OperationStore rather than the process startup workspace.
|
|
83
|
+
const splitScheduleDir = path.join(appData.root, "frontend-split-schedule");
|
|
84
|
+
const splitAuditDir = path.join(appData.root, "frontend-split-audit");
|
|
85
|
+
const splitAggregateDir = path.join(appData.root, "frontend-split-aggregate");
|
|
86
|
+
const splitMaterializationBackend = {
|
|
87
|
+
async ensureTask({ taskId }) {
|
|
88
|
+
return { taskId, complete: true };
|
|
89
|
+
},
|
|
90
|
+
async ensureOperation({ taskId, operationId, payloadDigest, request }) {
|
|
91
|
+
const action = request.kind === "child"
|
|
92
|
+
? "frontendSplitChild"
|
|
93
|
+
: "frontendSplitIntegration";
|
|
94
|
+
const { operation } = await operations.ensureSplitMaterializedOperation({
|
|
95
|
+
operationId,
|
|
96
|
+
taskId,
|
|
97
|
+
action,
|
|
98
|
+
actionParams: request,
|
|
99
|
+
payloadHash: payloadDigest,
|
|
100
|
+
});
|
|
101
|
+
return {
|
|
102
|
+
taskId: operation.taskId ?? taskId,
|
|
103
|
+
operationId: operation.operationId,
|
|
104
|
+
complete: true,
|
|
105
|
+
};
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
const materializer = new FrontendSplitTaskMaterializer(splitMaterializationBackend);
|
|
109
|
+
const schedule = async (input) => {
|
|
110
|
+
const fact = {
|
|
111
|
+
schemaVersion: 1,
|
|
112
|
+
kind: "frontend-split-schedule-intent",
|
|
113
|
+
operationId: input.operationId,
|
|
114
|
+
generation: input.generation,
|
|
115
|
+
bindingDigest: input.bindingDigest,
|
|
116
|
+
recordedAt: new Date().toISOString(),
|
|
117
|
+
};
|
|
118
|
+
const name = `${sha256Json({
|
|
119
|
+
operationId: input.operationId,
|
|
120
|
+
generation: input.generation,
|
|
121
|
+
bindingDigest: input.bindingDigest,
|
|
122
|
+
})}.json`;
|
|
123
|
+
await writeSecureJson(path.join(splitScheduleDir, name), fact);
|
|
124
|
+
};
|
|
125
|
+
const auditStale = async (input) => {
|
|
126
|
+
const fact = {
|
|
127
|
+
schemaVersion: 1,
|
|
128
|
+
kind: "frontend-split-stale-audit",
|
|
129
|
+
operationId: input.operationId,
|
|
130
|
+
reason: input.reason,
|
|
131
|
+
fact: input.fact,
|
|
132
|
+
recordedAt: new Date().toISOString(),
|
|
133
|
+
};
|
|
134
|
+
await writeSecureJson(path.join(splitAuditDir, `${sha256Json(fact)}.json`), fact);
|
|
135
|
+
};
|
|
136
|
+
const prepareAggregate = async ({ operationId, aggregate, }) => {
|
|
137
|
+
const before = await operations.get(operationId);
|
|
138
|
+
if (!before) {
|
|
139
|
+
throw new Error(`cannot prepare aggregate: parent operation not found: ${operationId}`);
|
|
140
|
+
}
|
|
141
|
+
const aggregateFile = path.join(splitAggregateDir, `${aggregate.digest}.json`);
|
|
142
|
+
let parentRecordPath = "";
|
|
143
|
+
let wroteAggregate = false;
|
|
144
|
+
let wroteParent = false;
|
|
145
|
+
return {
|
|
146
|
+
committedRef: {
|
|
147
|
+
ref: aggregateFile,
|
|
148
|
+
digest: aggregate.digest,
|
|
149
|
+
commitState: "committed",
|
|
150
|
+
},
|
|
151
|
+
async commit(input) {
|
|
152
|
+
parentRecordPath = input.parentRecordPath;
|
|
153
|
+
await writeSecureJson(aggregateFile, {
|
|
154
|
+
schemaVersion: 1,
|
|
155
|
+
kind: "frontend-split-aggregate-closeout",
|
|
156
|
+
operationId,
|
|
157
|
+
aggregate,
|
|
158
|
+
});
|
|
159
|
+
wroteAggregate = true;
|
|
160
|
+
await writeSecureJson(input.parentRecordPath, input.parentRecord);
|
|
161
|
+
wroteParent = true;
|
|
162
|
+
},
|
|
163
|
+
async rollback() {
|
|
164
|
+
if (wroteAggregate)
|
|
165
|
+
await rm(aggregateFile, { force: true });
|
|
166
|
+
if (wroteParent && parentRecordPath) {
|
|
167
|
+
await writeSecureJson(parentRecordPath, before);
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
};
|
|
172
|
+
const splitOrchestrator = createFrontendSplitOperationAdapter({
|
|
173
|
+
store: operations,
|
|
174
|
+
materializer,
|
|
175
|
+
schedule,
|
|
176
|
+
auditStale,
|
|
177
|
+
prepareAggregate,
|
|
178
|
+
});
|
|
179
|
+
const humanDecisionAdapter = createFrontendHumanDecisionAdapter(operations);
|
|
76
180
|
const drafts = new DraftStore(appData);
|
|
77
181
|
const confirmations = new DagConfirmationStore(appData, deps.bootToken.confirmationToken);
|
|
78
182
|
const dagExecutionReceipts = new DagExecutionReceiptStore(appData);
|
|
@@ -97,6 +201,9 @@ export async function createWorkspaceContext(record, deps) {
|
|
|
97
201
|
client,
|
|
98
202
|
operations,
|
|
99
203
|
events,
|
|
204
|
+
splitOrchestrator,
|
|
205
|
+
humanDecisionAdapter,
|
|
206
|
+
materializer,
|
|
100
207
|
drafts,
|
|
101
208
|
confirmations,
|
|
102
209
|
dagExecutionReceipts,
|
|
@@ -192,6 +299,9 @@ export async function createWorkspaceContext(record, deps) {
|
|
|
192
299
|
client,
|
|
193
300
|
operations,
|
|
194
301
|
events,
|
|
302
|
+
splitOrchestrator,
|
|
303
|
+
humanDecisionAdapter,
|
|
304
|
+
materializer,
|
|
195
305
|
drafts,
|
|
196
306
|
confirmations,
|
|
197
307
|
dagExecutionReceipts,
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { computeFrontendSplitDigest } from "../../workflows/dag/frontend-split-orchestrator.js";
|
|
3
|
+
const idSchema = z.string().trim().min(1);
|
|
4
|
+
const digestSchema = z.string().regex(/^[a-f0-9]{64}$/);
|
|
5
|
+
const positiveIntegerSchema = z.number().int().positive();
|
|
6
|
+
const pathSchema = z.string().trim().min(1);
|
|
7
|
+
export const frontendSplitTaskMaterializationRequestSchema = z.object({
|
|
8
|
+
kind: z.enum(["child", "integration"]),
|
|
9
|
+
parentOperationId: idSchema,
|
|
10
|
+
manifestDigest: digestSchema,
|
|
11
|
+
generation: positiveIntegerSchema,
|
|
12
|
+
taskId: idSchema,
|
|
13
|
+
operationId: idSchema,
|
|
14
|
+
payloadDigest: digestSchema,
|
|
15
|
+
allowedPaths: z.array(pathSchema),
|
|
16
|
+
writeSet: z.array(pathSchema),
|
|
17
|
+
verifyRefs: z.array(idSchema),
|
|
18
|
+
}).strict();
|
|
19
|
+
export const frontendSplitTaskMaterializationResultSchema = z.object({
|
|
20
|
+
idempotencyKey: digestSchema,
|
|
21
|
+
requestDigest: digestSchema,
|
|
22
|
+
taskId: idSchema,
|
|
23
|
+
operationId: idSchema,
|
|
24
|
+
}).strict();
|
|
25
|
+
function identityPayload(request) {
|
|
26
|
+
return { kind: request.kind, parentOperationId: request.parentOperationId, manifestDigest: request.manifestDigest, generation: request.generation, taskId: request.taskId, operationId: request.operationId };
|
|
27
|
+
}
|
|
28
|
+
export class FrontendSplitTaskMaterializer {
|
|
29
|
+
backend;
|
|
30
|
+
completed = new Map();
|
|
31
|
+
inFlight = new Map();
|
|
32
|
+
constructor(backend) {
|
|
33
|
+
this.backend = backend;
|
|
34
|
+
}
|
|
35
|
+
async materialize(raw) {
|
|
36
|
+
const request = frontendSplitTaskMaterializationRequestSchema.parse(raw);
|
|
37
|
+
const idempotencyKey = computeFrontendSplitDigest(identityPayload(request));
|
|
38
|
+
const requestDigest = computeFrontendSplitDigest(request);
|
|
39
|
+
const existing = this.completed.get(idempotencyKey);
|
|
40
|
+
if (existing) {
|
|
41
|
+
if (existing.requestDigest !== requestDigest)
|
|
42
|
+
throw new Error("frontend split materialization payload drift");
|
|
43
|
+
return existing;
|
|
44
|
+
}
|
|
45
|
+
const pending = this.inFlight.get(idempotencyKey);
|
|
46
|
+
if (pending) {
|
|
47
|
+
const result = await pending;
|
|
48
|
+
if (result.requestDigest !== requestDigest)
|
|
49
|
+
throw new Error("frontend split materialization payload drift");
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
const promise = this.perform(request, idempotencyKey, requestDigest);
|
|
53
|
+
this.inFlight.set(idempotencyKey, promise);
|
|
54
|
+
try {
|
|
55
|
+
const result = await promise;
|
|
56
|
+
this.completed.set(idempotencyKey, result);
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
this.inFlight.delete(idempotencyKey);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async perform(request, idempotencyKey, requestDigest) {
|
|
64
|
+
const task = await this.backend.ensureTask({ idempotencyKey, taskId: request.taskId, payloadDigest: requestDigest, request });
|
|
65
|
+
if (!task.complete || task.taskId !== request.taskId)
|
|
66
|
+
throw new Error("frontend split task materialization incomplete or drifted");
|
|
67
|
+
const operation = await this.backend.ensureOperation({ idempotencyKey, taskId: request.taskId, operationId: request.operationId, payloadDigest: requestDigest, request });
|
|
68
|
+
if (!operation.complete || operation.taskId !== request.taskId || operation.operationId !== request.operationId)
|
|
69
|
+
throw new Error("frontend split operation materialization incomplete or drifted");
|
|
70
|
+
return frontendSplitTaskMaterializationResultSchema.parse({ idempotencyKey, requestDigest, taskId: request.taskId, operationId: request.operationId });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -2362,8 +2362,6 @@ function parseDagNodes(parsed, rankByNode, modelByNode) {
|
|
|
2362
2362
|
durationMs: readNumber(node, "durationMs"),
|
|
2363
2363
|
startedAt: readString(node, "startedAt"),
|
|
2364
2364
|
finishedAt: readString(node, "finishedAt"),
|
|
2365
|
-
lastMeaningfulProgressAt: readString(node, "lastMeaningfulProgressAt"),
|
|
2366
|
-
livenessStatus: readString(node, "livenessStatus"),
|
|
2367
2365
|
...(outputPreview ? { outputPreview } : {}),
|
|
2368
2366
|
...(errorPreview ? { errorPreview } : {}),
|
|
2369
2367
|
...(attempts && attempts.length > 0
|
|
@@ -2409,8 +2407,6 @@ function parseDagNodes(parsed, rankByNode, modelByNode) {
|
|
|
2409
2407
|
durationMs: readNumber(node, "durationMs"),
|
|
2410
2408
|
startedAt: readString(node, "startedAt"),
|
|
2411
2409
|
finishedAt: readString(node, "finishedAt"),
|
|
2412
|
-
lastMeaningfulProgressAt: readString(node, "lastMeaningfulProgressAt"),
|
|
2413
|
-
livenessStatus: readString(node, "livenessStatus"),
|
|
2414
2410
|
...(outputPreview ? { outputPreview } : {}),
|
|
2415
2411
|
...(errorPreview ? { errorPreview } : {}),
|
|
2416
2412
|
...(attempts && attempts.length > 0
|
|
@@ -39,20 +39,25 @@ const NODE_PURPOSE_BY_ID = {
|
|
|
39
39
|
"closeout-summary-pi": "把验证与交付事实写成简短交接摘要。",
|
|
40
40
|
|
|
41
41
|
"frontend-contract-pi": "把前端需求整理成可追踪契约:页面、状态、验收与边界。",
|
|
42
|
+
"frontend-contract-import-shell": "导入并固化前端实现契约,供后续设计、写入与验证节点使用。",
|
|
42
43
|
"frontend-scout-pi": "只读侦察前端结构、组件与接口用法,不改文件。",
|
|
43
44
|
"frontend-plan-pi": "选定实现与 Mock/API 策略,输出可执行的前端实现契约补丁。",
|
|
45
|
+
"frontend-design-policy-shell": "确定前端设计与 Mock/API 策略,冻结后续实现必须遵守的规则。",
|
|
44
46
|
"frontend-design-review-pi": "审查前端计划是否可写:通过则沿用原计划,否则要求修订。",
|
|
45
47
|
"frontend-plan-revision-pi": "按设计审查意见修订前端契约,不扩大范围。",
|
|
46
48
|
"frontend-final-design-review-pi": "复核修订后的前端计划,只有通过才能授权写入。",
|
|
47
49
|
"frontend-prewrite-gate-shell": "写入前门禁:核对契约、覆盖与 Mock 策略,通过才允许改前端。",
|
|
50
|
+
"frontend-writer-admission-shell": "核对契约、设计策略与 writeSet,满足准入条件后才允许 writer 写入。",
|
|
48
51
|
"frontend-lint-baseline-shell": "记录写入前的 lint 基线,供实现后对比,不阻断写入。",
|
|
49
52
|
"frontend-implement-pi": "按已批准契约改前端代码、状态与交互,留在 writeSet 内。",
|
|
50
53
|
"frontend-verify-assess-shell": "跑静态/行为/Mock 验证并评估能否有界修复。",
|
|
54
|
+
"frontend-verify-shell": "执行冻结的前端验证命令,产出可复查的通过或失败证据。",
|
|
51
55
|
"frontend-repair-pi": "只修评估为可修复的前端失败,不重开需求。",
|
|
52
56
|
"frontend-reverify-shell": "修复后用同一套冻结命令复验。",
|
|
53
57
|
"frontend-review-context-shell": "打包真实 diff 与有效验证证据,供审查节点使用。",
|
|
54
58
|
"frontend-review-pi": "对照契约审查前端实现与证据,给出通过或需修订。",
|
|
55
59
|
"frontend-review-gate-shell": "前端审查门禁:结论为通过才进入收口。",
|
|
60
|
+
"frontend-closeout-shell": "汇总实现、验证与遗留事项,形成确定性的前端收口事实。",
|
|
56
61
|
"frontend-closeout-pi": "汇总前端交付状态(含真实联调是否仍待完成)。",
|
|
57
62
|
"frontend-mock-assess-pi": "评估项目是否具备授权的 Mock 验证能力。",
|
|
58
63
|
"frontend-mock-contract-gate-shell": "Mock 契约门禁:策略与验证命令不一致则阻断。",
|
|
@@ -49,7 +49,6 @@ import {
|
|
|
49
49
|
formatSessionEventTime,
|
|
50
50
|
renderSessionTimeline,
|
|
51
51
|
buildPageStyledDoc,
|
|
52
|
-
fetchPageStylesheetText,
|
|
53
52
|
} from "./session-timeline.js";
|
|
54
53
|
import { createCustomSelect } from "../custom-select.js";
|
|
55
54
|
|
|
@@ -2367,7 +2366,13 @@ function appendProcessExportButton(content, body, meta) {
|
|
|
2367
2366
|
const btn = el("button", "btn btn-secondary", "导出 HTML");
|
|
2368
2367
|
btn.type = "button";
|
|
2369
2368
|
btn.addEventListener("click", async () => {
|
|
2370
|
-
|
|
2369
|
+
let cssText = "";
|
|
2370
|
+
try {
|
|
2371
|
+
const res = await fetch("/inspect/styles.css", { cache: "no-cache" });
|
|
2372
|
+
if (res.ok) cssText = await res.text();
|
|
2373
|
+
} catch {
|
|
2374
|
+
/* 忽略:导出仍会用内联兜底样式 */
|
|
2375
|
+
}
|
|
2371
2376
|
// 复用页面真实渲染 DOM,保证导出排版与页面一致。
|
|
2372
2377
|
const clone = content.cloneNode(true);
|
|
2373
2378
|
const bar = clone.querySelector(".process-timeline-export-bar");
|
|
@@ -51,27 +51,6 @@ function downloadTextFile(filename, text) {
|
|
|
51
51
|
URL.revokeObjectURL(url);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
/** 抓取页面真实加载的样式表原文(导出内联用)。
|
|
55
|
-
* 不猜路径:直接取当前文档 <link rel="stylesheet"> 的 href(相对当前页面解析),
|
|
56
|
-
* 反代/子路径部署下也和页面渲染用的是同一份 CSS;fetch 失败时返回 "",
|
|
57
|
-
* 调用方回退 EXPORT_FALLBACK_CSS。 */
|
|
58
|
-
export async function fetchPageStylesheetText() {
|
|
59
|
-
try {
|
|
60
|
-
const links = [...document.querySelectorAll('link[rel="stylesheet"]')];
|
|
61
|
-
const texts = [];
|
|
62
|
-
for (const link of links) {
|
|
63
|
-
const href = link.getAttribute("href");
|
|
64
|
-
if (!href) continue;
|
|
65
|
-
const url = new URL(href, document.baseURI).href;
|
|
66
|
-
const res = await fetch(url, { cache: "no-cache" });
|
|
67
|
-
if (res.ok) texts.push(await res.text());
|
|
68
|
-
}
|
|
69
|
-
return texts.join("\n");
|
|
70
|
-
} catch {
|
|
71
|
-
return "";
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
54
|
/** 兜底样式:仅在无法抓取页面 /inspect/styles.css 时使用(与页面 process-timeline 排版对齐)。 */
|
|
76
55
|
const EXPORT_FALLBACK_CSS = `
|
|
77
56
|
:root{--canvas:#f7f7f4;--surface:#ffffff;--canvas-soft:#fafaf7;--surface-muted:#f0efe9;--ink:#26251e;--body:#5a5852;--muted:#807d72;--muted-soft:#a09c92;--hairline:#e6e5e0;--hairline-strong:#cfcdc4;--orange:#f54e00;--orange-active:#d04200;--blue:#3f6ea4;--green:#1f8a65;--amber:#a66a00;--red:#c72d53;--lavender:#e4daf1;--radius-sm:6px;--space-2:8px;--space-3:12px;--space-5:20px}
|
|
@@ -124,31 +103,19 @@ body{margin:0;font-family:-apple-system,"Segoe UI",Roboto,"PingFang SC","Microso
|
|
|
124
103
|
.markdown-output p,.markdown-output blockquote,.markdown-output ul,.markdown-output ol{margin:0;overflow-wrap:anywhere}
|
|
125
104
|
.markdown-output ul,.markdown-output ol{padding-left:22px}
|
|
126
105
|
.markdown-output blockquote{padding-left:12px}
|
|
127
|
-
.btn{display:inline-flex;align-items:center;gap:6px;min-height:28px;padding:0 12px;border:1px solid var(--hairline-strong);border-radius:var(--radius-sm);background:var(--surface);color:var(--body);font-size:12px;font-weight:600;cursor:pointer;user-select:none}
|
|
128
|
-
.btn-secondary{background:var(--surface-muted)}
|
|
129
|
-
.btn:hover{border-color:var(--orange);color:var(--orange-active)}
|
|
130
106
|
`;
|
|
131
107
|
|
|
132
108
|
const EXPORT_SHELL_CSS = `
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
body{margin:0;font-family:-apple-system,"Segoe UI",Roboto,"PingFang SC","Microsoft YaHei",sans-serif;background:var(--canvas,#f7f7f4)}
|
|
138
|
-
.export-shell{max-width:1000px;margin:0 auto;padding:24px 28px;background:var(--surface,#ffffff);min-height:100vh;box-shadow:0 0 0 1px var(--hairline,#e6e5e0)}
|
|
139
|
-
.export-head h1{font-size:15px;color:var(--ink,#26251e);margin:0;font-weight:700}
|
|
140
|
-
.export-sub{color:var(--muted,#807d72);font-size:12px;margin:4px 0 16px}
|
|
109
|
+
body{margin:0;font-family:-apple-system,"Segoe UI",Roboto,"PingFang SC","Microsoft YaHei",sans-serif;background:var(--canvas)}
|
|
110
|
+
.export-shell{max-width:1000px;margin:0 auto;padding:24px 28px;background:var(--surface);min-height:100vh;box-shadow:0 0 0 1px var(--hairline)}
|
|
111
|
+
.export-head h1{font-size:15px;color:var(--ink);margin:0;font-weight:700}
|
|
112
|
+
.export-sub{color:var(--muted);font-size:12px;margin:4px 0 16px}
|
|
141
113
|
.export-controls{display:flex;justify-content:flex-start;margin:0 0 12px}
|
|
142
|
-
.export-toggle{min-height:28px;padding:0 12px;border:1px solid var(--hairline-strong
|
|
143
|
-
.export-toggle:hover,.export-toggle:focus-visible{border-color:var(--orange
|
|
114
|
+
.export-toggle{min-height:28px;padding:0 12px;border:1px solid var(--hairline-strong);border-radius:var(--radius-sm);background:var(--surface);color:var(--body);font-size:11px;font-weight:600;cursor:pointer;user-select:none}
|
|
115
|
+
.export-toggle:hover,.export-toggle:focus-visible{border-color:var(--orange);color:var(--orange-active);outline:none}
|
|
144
116
|
body.hide-protocol .process-timeline-item.process-timeline-protocol{display:none}
|
|
145
117
|
`;
|
|
146
118
|
|
|
147
|
-
/** 导出文档自带的变量基础:与页面 styles.css 的 light 主题保持一致。
|
|
148
|
-
* 即使 fetch 到了页面 CSS,其变量也挂在 body[data-shell=…]/.inspect-workspace-root
|
|
149
|
-
* 选择器下而不生效,必须在导出文档内重新定义。 */
|
|
150
|
-
const EXPORT_ROOT_VARS_CSS = `:root{--canvas:#f7f7f4;--surface:#ffffff;--canvas-soft:#fafaf7;--surface-muted:#f0efe9;--surface-ink:#26251e;--ink:#26251e;--body:#5a5852;--muted:#807d72;--muted-soft:#a09c92;--hairline:#e6e5e0;--hairline-strong:#cfcdc4;--orange:#f54e00;--orange-active:#d04200;--blue:#3f6ea4;--green:#1f8a65;--amber:#a66a00;--red:#c72d53;--lavender:#e4daf1;--mint:#d8ead5;--peach:#f1d8cb;--gold:#d8b166;--radius-sm:6px;--radius-md:8px;--radius-lg:12px;--space-1:4px;--space-2:8px;--space-3:12px;--space-4:16px;--space-5:20px;--space-6:24px;--space-8:32px;--space-10:40px;--space-12:48px}`;
|
|
151
|
-
|
|
152
119
|
/**
|
|
153
120
|
* 把页面渲染出的时间线 innerHTML 封装成自包含 HTML。
|
|
154
121
|
* @param {string} bodyHTML 页面渲染的 .process-timeline 等真实 DOM 序列化结果
|
|
@@ -159,18 +126,14 @@ export function buildPageStyledDoc(bodyHTML, meta = {}, cssText = "") {
|
|
|
159
126
|
const now = new Date().toLocaleString();
|
|
160
127
|
const title = meta.nodeId ? `节点执行过程 · ${meta.nodeId}` : "节点执行过程";
|
|
161
128
|
const style = cssText.trim()
|
|
162
|
-
? `${
|
|
129
|
+
? `${cssText}\n\n/* 导出外壳 */\n${EXPORT_SHELL_CSS}`
|
|
163
130
|
: `${EXPORT_FALLBACK_CSS}\n${EXPORT_SHELL_CSS}`;
|
|
164
131
|
// 有协议行时,在左上角提供一个「展开/隐藏原始协议事件」按钮(与页面一致),默认隐藏。
|
|
165
132
|
const hasProtocol = bodyHTML.includes("process-timeline-protocol");
|
|
166
133
|
const controls = hasProtocol
|
|
167
134
|
? `<div class="export-controls"><button type="button" id="exportToggleProtocol" class="export-toggle">展开原始协议事件</button></div><script>(function(){var b=document.body,t=document.getElementById('exportToggleProtocol'),on=false;function apply(){b.classList.toggle('hide-protocol',!on);t.textContent=on?'隐藏原始协议事件':'展开原始协议事件';}if(t){t.addEventListener('click',function(){on=!on;apply();});apply();}})();</script>`
|
|
168
135
|
: "";
|
|
169
|
-
|
|
170
|
-
// (--canvas/--surface/--hairline-strong …) 与部分规则 (body[data-shell] …) 定义在
|
|
171
|
-
// 该作用域下,导出文档带上属性后令牌与前缀规则原生命中,与页面渲染一致。
|
|
172
|
-
// EXPORT_ROOT_VARS_CSS 仍保留:fetch 页面 CSS 失败的兜底路径令牌不缺。
|
|
173
|
-
return `<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${escapeHtml(title)}</title><style>${style}</style></head><body data-shell="observe-console"><div class="export-shell"><div class="export-head"><h1>${escapeHtml(title)}</h1></div><p class="export-sub">DAG ${escapeHtml(meta.dagRunId ?? "")} · 导出于 ${escapeHtml(now)}</p>${controls}<div class="export-timeline">${bodyHTML}</div></div></body></html>`;
|
|
136
|
+
return `<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${escapeHtml(title)}</title><style>${style}</style></head><body><div class="export-shell"><div class="export-head"><h1>${escapeHtml(title)}</h1></div><p class="export-sub">DAG ${escapeHtml(meta.dagRunId ?? "")} · 导出于 ${escapeHtml(now)}</p>${controls}<div class="export-timeline">${bodyHTML}</div></div></body></html>`;
|
|
174
137
|
}
|
|
175
138
|
|
|
176
139
|
export function sessionEventResult(event) {
|
|
@@ -888,7 +851,13 @@ export function renderSessionTimeline(content, nodeId, options = {}) {
|
|
|
888
851
|
const exportBtn = el("button", "btn btn-secondary", "导出 HTML");
|
|
889
852
|
exportBtn.type = "button";
|
|
890
853
|
exportBtn.addEventListener("click", async () => {
|
|
891
|
-
|
|
854
|
+
let cssText = "";
|
|
855
|
+
try {
|
|
856
|
+
const res = await fetch("/inspect/styles.css", { cache: "no-cache" });
|
|
857
|
+
if (res.ok) cssText = await res.text();
|
|
858
|
+
} catch {
|
|
859
|
+
/* 忽略:导出仍会用内联兜底样式 */
|
|
860
|
+
}
|
|
892
861
|
// 复用页面真实渲染(含原始协议事件),保证导出的排版与页面一致。
|
|
893
862
|
const prevShow = uiState.sessionTimelineShowProtocol;
|
|
894
863
|
uiState.sessionTimelineShowProtocol = true;
|
|
@@ -934,7 +934,7 @@ export async function materializeBackendTestResultFromPytestHtml(input) {
|
|
|
934
934
|
outcome = "command-error";
|
|
935
935
|
}
|
|
936
936
|
const commandSummary = input.commandSummary ??
|
|
937
|
-
`PYTHONUTF8=1 PYTHONIOENCODING=utf-8 PYTHONDONTWRITEBYTECODE=1 python
|
|
937
|
+
`PYTHONUTF8=1 PYTHONIOENCODING=utf-8 PYTHONDONTWRITEBYTECODE=1 python -m pytest -v -p no:cacheprovider --html=${htmlRelativePath} --self-contained-html`;
|
|
938
938
|
assertNoSecrets("commandSummary", commandSummary);
|
|
939
939
|
const result = backendTestResultContractSchema.parse({
|
|
940
940
|
schemaVersion: 1,
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
* its schemaId in the contract output registry. Imported once by the node
|
|
4
4
|
* executor; keeps per-contract imports out of the executor.
|
|
5
5
|
*/
|
|
6
|
-
import { validateFrontendContractNodeOutput, validateFrontendPlanPatchNodeOutput,
|
|
6
|
+
import { validateFrontendContractNodeOutput, validateFrontendPlanPatchNodeOutput, } from "./frontend-implementation-contract.js";
|
|
7
7
|
import { registerStructuredContractValidator } from "./contract-output-registry.js";
|
|
8
8
|
registerStructuredContractValidator("frontend-implementation-contract-v1", validateFrontendContractNodeOutput);
|
|
9
9
|
registerStructuredContractValidator("frontend-implementation-contract-plan-patch-v1", validateFrontendPlanPatchNodeOutput);
|
|
10
|
-
registerStructuredContractValidator("frontend-implementation-contract-revision-patch-v1", validateFrontendRevisionPatchNodeOutput);
|