@tea-agent/loop-agent 0.41.1-beta.0 → 0.42.0-next.2
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 +78 -206
- package/dist/adapters/context-transfer/optional-pi-handoff.js +31 -0
- package/dist/adapters/context-transfer/pi-session.js +61 -0
- package/dist/application/dag/generate-task-dag.js +19 -75
- package/dist/application/task-lifecycle/advance.js +5 -24
- package/dist/application/task-lifecycle/observe.js +17 -171
- package/dist/application/task-lifecycle/plan-transitions.js +7 -42
- package/dist/application/task-lifecycle/recommendations.js +2 -13
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +12 -7
- package/dist/cli/program.js +23 -6
- package/dist/commands/client-recovery.js +0 -3
- package/dist/commands/dag-artifact.js +284 -0
- package/dist/commands/dag-context.js +184 -0
- package/dist/commands/dag-rerun.js +206 -1
- package/dist/commands/init.js +1 -27
- package/dist/commands/task-advance.js +0 -19
- package/dist/executors/dag-pi-executor.js +266 -3792
- package/dist/executors/pi-executor.js +4 -15
- package/dist/executors/pi-sdk-executor.js +3 -0
- package/dist/executors/shell-executor.js +217 -945
- package/dist/executors/shell-write-guard.js +0 -7
- package/dist/{worker → infrastructure}/console/app-data.js +4 -0
- package/dist/infrastructure/console/artifact-revision-store.js +430 -0
- package/dist/infrastructure/console/context-export-store.js +160 -0
- package/dist/infrastructure/console/dir-lock.js +132 -0
- package/dist/infrastructure/console/operation-store.js +197 -0
- package/dist/infrastructure/harness/artifact-store.js +10 -1
- package/dist/infrastructure/harness/atomic-write.js +12 -2
- package/dist/shared/context-transfer/artifact-revision.js +172 -0
- package/dist/shared/context-transfer.js +418 -0
- package/dist/shared/dag-failure-category.js +0 -12
- package/dist/shared/openspec-spec.js +4 -70
- package/dist/shared/operator/capabilities.js +14 -7
- package/dist/shared/operator/safe-run-summary.js +1 -0
- package/dist/shared/path-safety.js +93 -0
- package/dist/shared/preview.js +28 -4
- package/dist/task/config-types.js +80 -35
- package/dist/task/contract/adopt.js +0 -4
- package/dist/task/contract/import-revision.js +0 -4
- package/dist/task/contract/project.js +0 -3
- package/dist/task/contract/schema.js +1 -2
- package/dist/task/frontend-project-capability.js +20 -203
- package/dist/task/runtime.js +2 -5
- package/dist/task/source-prepare/build-draft.js +3 -3
- package/dist/task/source-prepare/fragment-inventory.js +9 -15
- package/dist/task/source-prepare/prepare.js +1 -89
- package/dist/task/source-prepare/semantic-intake.js +2 -6
- package/dist/task/source-references.js +1 -22
- package/dist/task/task-demand-routing.js +3 -0
- package/dist/worker/console/chat/chat-event-store.js +2 -2
- package/dist/worker/console/chat/pi-runtime.js +3 -2
- package/dist/worker/console/chat/resource-preferences-store.js +1 -1
- package/dist/worker/console/chat/routes.js +13 -4
- package/dist/worker/console/chat/semantic-activity.js +12 -11
- package/dist/worker/console/chat/session-stats.js +96 -0
- package/dist/worker/console/chat/session-store.js +1 -1
- package/dist/worker/console/chat/turn-process.js +28 -4
- package/dist/worker/console/chat/user-questions.js +1 -1
- package/dist/worker/console/console-update-runtime.js +1 -1
- package/dist/worker/console/context-transfer-diagnostics.js +198 -0
- package/dist/worker/console/dag-confirmation.js +1 -1
- package/dist/worker/console/dag-execution-receipt.js +1 -1
- package/dist/worker/console/doctor.js +1 -1
- package/dist/worker/console/draft-store.js +1 -1
- package/dist/worker/console/human-gate-token.js +1 -1
- package/dist/worker/console/index.js +3 -6
- package/dist/worker/console/interview/assessment.js +1 -1
- package/dist/worker/console/interview/session.js +1 -1
- package/dist/worker/console/operation-runner.js +45 -4
- package/dist/worker/console/operation-sse.js +1 -1
- package/dist/worker/console/operation-wait.js +1 -1
- package/dist/worker/console/operator-actions.js +107 -168
- package/dist/worker/console/operator-surface-health.js +1 -1
- package/dist/worker/console/operator-user-error.js +169 -0
- package/dist/worker/console/pi-plugins.js +60 -1
- package/dist/worker/console/routes.js +637 -8
- package/dist/worker/console/security.js +35 -0
- package/dist/worker/console/server.js +6 -6
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-Bho4we4c.js → abnfDiagram-N423BO3Z-DZZ8m3PO.js} +1 -1
- package/dist/worker/console/static/assets/{arc-TBDkTeK0.js → arc-D6PvaVd-.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-BF_fZrn2.js → architectureDiagram-T3A2C74G-B_OTOiI8.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-CCnPgUe2.js → blockDiagram-VBNYF7ZC-Bv6rqHBg.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BCYZJLAT.js → c4Diagram-5PPSVZJV-B8eHr0oz.js} +1 -1
- package/dist/worker/console/static/assets/channel-BU5gOilw.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-D8K6adhU.js → chunk-2GRJ4B5K-DYwR0im2.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DkT1Q78b.js → chunk-2Q5K7J3B-D2WPGqXt.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DuhvpIr7.js → chunk-5RXB4S5H-CQISJ_I7.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BnK6dC49.js → chunk-5VM5RSS4-C0o2Du1e.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DPz__z7r.js → chunk-6Q2QTUOP-4f8kr-U7.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-0kkppM2j.js → chunk-GF5L2VYU-D_OWvXzX.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BB41iK5t.js → chunk-JWPE2WC7-CasdPz5X.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-CiVrxKHU.js → chunk-KBJHAD2P-Cj8lRrla.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-C2h9Xje1.js → chunk-RYQCIY6F-CUvD1FSd.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-3iOSHdbM.js → chunk-XXDRQBXY-CqLw_eqB.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DIzKJGHr.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DIzKJGHr.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BN3A7tkk.js → cose-bilkent-JH36ORCC-ku-WqJPx.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-CTuBy0Or.js → cynefin-VYW2F7L2-Bq_PYDhl.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-wb6tUlMN.js → cynefinDiagram-MW4NZA55-BiGZV641.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-GP1Ivx8B.js → dagre-VZM6K2ZE-C4JRlglF.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-ejrcK6L0.js → diagram-7IWD3JNH-B0dNeWYG.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-YFkCCvUU.js → diagram-B4RE2ZJO-Cj35YvYM.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-B4rrvIaL.js → diagram-LBJQPF4R-uzQoJ2-8.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-C-YIZcUf.js → diagram-Q27KOJAE-D1a-Buoz.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BPF5wcL1.js → diagram-UB23O5K3-XjRrLRSs.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-CU5SzXjO.js → ebnfDiagram-BXEA7PRR-Da_O24cW.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-CpL414DE.js → erDiagram-JOGREHBK-BLJ8jrYU.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-MrhLzNau.js → flowDiagram-UKHOOZJN-B9GrLjM0.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-Bg4qFuUo.js → ganttDiagram-PKOTCBZU-CeJ0TqiK.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-ColxSXLR.js → gitGraphDiagram-DS77QQ5N-Bm2eNNFX.js} +1 -1
- package/dist/worker/console/static/assets/index-H9rFJiGL.css +1 -0
- package/dist/worker/console/static/assets/index-xwu9GxEc.js +451 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-Czvtm6gc.js → infoDiagram-6WML65LV-DB26i3d8.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-B7Xp8HyJ.js → ishikawaDiagram-WSZJBQD7-BOGRyeZT.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DWYZcvph.js → journeyDiagram-NVQOT4AX-CZRoBO_6.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-KTLZRUjx.js → kanban-definition-27J2QSJJ-qlWhJyoB.js} +1 -1
- package/dist/worker/console/static/assets/{linear-_Exn7bfl.js → linear-CfUiDDB3.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-Cxg8kX8V.js → mermaid.core-BQe6fpqj.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-oKrlo2Bt.js → mindmap-definition-FAOFIHXS-zFzWHw64.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-XAeteiNd.js → pegDiagram-VL7TDLO6-DFGUqjZO.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-BTf1K-lp.js → pieDiagram-7S7Q4E2Y-BB5i0l8Z.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-C0IlY0Jf.js → quadrantDiagram-CIZ2JOQS-obbVZ_X5.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BVbKtZhW.js → railroadDiagram-AXF67PYL-BwdaNzc1.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-C5e4JFcr.js → requirementDiagram-LRYGKXZP-CkTJaugh.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Cz30hc29.js → sankeyDiagram-W5VNT64P-BwJ-hIgq.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DS33n6d0.js → sequenceDiagram-SI44F4Z6-BzSgmm7w.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-46w7v-9T.js → sizeCapture-X5ZJPWSS-Bn3oj5Q1.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-pbcD5SsZ.js → stateDiagram-OKZ733FA-dxhGtL8K.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Cld2qK9v.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DuivYBSj.js → swimlanes-SLNWSIFB-BLpEInya.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-TIHpiT7w.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Cb9VZVIl.js → timeline-definition-Z64GVDOM-ChJGYcXy.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-D2-mfOgR.js → vennDiagram-T6HMQDX7-DK-qjRer.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DeS2aC76.js → wardleyDiagram-T6FBY63Y-3qeaWAg-.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-WGsrFjxH.js → xychartDiagram-ELKLHX3M-CootlyP9.js} +1 -1
- package/dist/worker/console/static/index.html +7 -2
- package/dist/worker/console/static-src/app/useOperatorActions.js +3 -2
- package/dist/worker/console/static-src/app/useRunProgress.js +9 -2
- package/dist/worker/console/static-src/operator-chat/dag-progress-link.js +22 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +2 -0
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +6 -0
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +28 -3
- package/dist/worker/console/static-src/pages/tasks/run-panel-progress.js +106 -0
- package/dist/worker/console/static-src/shell/console-update-reload.js +25 -0
- package/dist/worker/console/workspace-context.js +28 -112
- package/dist/worker/console/workspace-registry.js +2 -2
- package/dist/worker/continuation/worker-continuation.js +278 -0
- package/dist/worker/observability/read-model.js +4 -0
- package/dist/worker/observe/health.js +1 -1
- package/dist/worker/observe/node-transparency.js +572 -0
- package/dist/worker/observe/routes.js +51 -1
- package/dist/worker/observe/static/api.js +69 -5
- package/dist/worker/observe/static/dag-context-reason-labels.d.ts +9 -0
- package/dist/worker/observe/static/dag-context-reason-labels.js +120 -0
- package/dist/worker/observe/static/dag-node-purpose.js +0 -5
- package/dist/worker/observe/static/state.js +3 -1
- package/dist/worker/observe/static/styles.css +1037 -126
- package/dist/worker/observe/static/views/dag-inspector.js +1217 -65
- package/dist/worker/observe/static/views/session-timeline.js +121 -25
- package/dist/worker/outcomes/adapters.js +19 -0
- package/dist/worker/outcomes/types.js +1 -0
- package/dist/worker/pool/attempt-lease.js +97 -66
- package/dist/worker/pool/begin-attempt-with-lease.js +1 -0
- package/dist/worker/pool/reconcile.js +46 -1
- package/dist/worker/pool/run-store.js +2 -0
- package/dist/worker/pool/state-projection.js +2 -0
- package/dist/worker/task-spec/workflow-routing.js +8 -3
- package/dist/workflows/dag/artifact-bindings.js +149 -0
- package/dist/workflows/dag/artifact-revision-schema-registry.js +31 -0
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +99 -4
- package/dist/workflows/dag/backend-test-markdown-workflow.js +13 -1
- package/dist/workflows/dag/backend-test-result-contract.js +1 -1
- package/dist/workflows/dag/backend-test-writer-completeness.js +45 -3
- package/dist/workflows/dag/budget-enforcement.js +10 -0
- package/dist/workflows/dag/context-receipt.js +305 -0
- package/dist/workflows/dag/context-transfer/context-bundle.js +423 -0
- package/dist/workflows/dag/context-transfer/operator-actions.js +61 -0
- package/dist/workflows/dag/context-transfer/renderers.js +93 -0
- package/dist/workflows/dag/contract-validator-registrations.js +2 -1
- package/dist/workflows/dag/frontend-implementation-contract.js +162 -932
- package/dist/workflows/dag/frontend-plan-render.js +1 -2
- package/dist/workflows/dag/frontend-prewrite-gate.js +349 -255
- package/dist/workflows/dag/frontend-recovery-plan.js +10 -17
- package/dist/workflows/dag/frontend-recovery-run.js +20 -33
- package/dist/workflows/dag/frontend-repair.js +432 -1
- package/dist/workflows/dag/frontend-review-context.js +15 -261
- package/dist/workflows/dag/frontend-verification-trace.js +24 -249
- package/dist/workflows/dag/frontend-worktree-diff.js +17 -250
- package/dist/workflows/dag/frontend-writer-rollback.js +32 -0
- package/dist/workflows/dag/init-hybrid.js +740 -718
- package/dist/workflows/dag/interrupt-request.js +0 -7
- package/dist/workflows/dag/node-execution.js +136 -759
- package/dist/workflows/dag/path-safety.js +1 -0
- package/dist/workflows/dag/prompt.js +30 -38
- package/dist/workflows/dag/report.js +1 -37
- package/dist/workflows/dag/rerun-feedback.js +1 -256
- package/dist/workflows/dag/rerun-plan.js +557 -11
- package/dist/workflows/dag/rerun-run.js +601 -38
- package/dist/workflows/dag/rerun-task.js +0 -29
- package/dist/workflows/dag/retry-policy.js +109 -214
- package/dist/workflows/dag/runner.js +142 -430
- package/dist/workflows/dag/scheduler.js +16 -114
- package/dist/workflows/dag/skill-snapshot.js +17 -0
- package/dist/workflows/dag/types.js +195 -246
- package/dist/workflows/dag/validate.js +12 -28
- package/docs/architecture/runtime-boundaries.md +6 -6
- package/docs/architecture/worker-and-feature.md +1 -1
- package/docs/init-surface.manifest.json +12 -30
- package/docs/skills/vetted-skill-registry.md +2 -4
- package/docs/templates/README.md +0 -2
- package/docs/templates/agent-dag-report.schema.json +2 -8
- package/docs/templates/agent-dag.schema.json +33 -1
- package/docs/templates/backend-test-dag.json +18 -18
- package/docs/templates/frontend-implementation-contract.schema.json +1 -4
- package/docs/templates/product-line/task.yaml +1 -1
- package/harness.json +3 -3
- package/package.json +3 -1
- package/skills/frontend-bounded-implement/SKILL.md +14 -15
- package/skills/frontend-design-review/SKILL.md +41 -22
- package/skills/frontend-implementation/SKILL.md +52 -0
- package/skills/frontend-implementation/references/code-standards.md +33 -0
- package/skills/frontend-implementation/references/design-spec.md +56 -0
- package/skills/frontend-implementation/references/node-contracts.md +31 -0
- package/skills/frontend-review/SKILL.md +15 -20
- package/skills/frontend-review/references/review-findings.md +7 -6
- package/skills/frontend-verification/SKILL.md +1 -1
- package/skills/loop-agent/references/command-reference.md +6 -1
- package/skills/loop-agent/references/hybrid-dag.md +2 -2
- package/dist/commands/dag-follow-up.js +0 -138
- package/dist/executors/pi-read-budget-policy.js +0 -239
- package/dist/worker/console/frontend-human-decision-adapter.js +0 -19
- package/dist/worker/console/frontend-split-operation-adapter.js +0 -20
- package/dist/worker/console/operation-store.js +0 -389
- package/dist/worker/console/static/assets/channel-bnXW0U3C.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-rZpq_twR.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-rZpq_twR.js +0 -1
- package/dist/worker/console/static/assets/index-D3CC4eUz.css +0 -1
- package/dist/worker/console/static/assets/index-DLt4ZFvD.js +0 -437
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-zBMYiUyi.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-d_rms-5i.js +0 -8
- package/dist/worker/console/static-src/active-run-badge.js +0 -17
- package/dist/worker/materialize/frontend-split-task-materializer.js +0 -72
- package/dist/workflows/dag/dag-retry-schema.js +0 -138
- package/dist/workflows/dag/frontend-closeout.js +0 -221
- package/dist/workflows/dag/frontend-design-policy.js +0 -400
- package/dist/workflows/dag/frontend-human-decision.js +0 -182
- package/dist/workflows/dag/frontend-provider-capability-matrix.js +0 -159
- package/dist/workflows/dag/frontend-read-budgets.js +0 -12
- package/dist/workflows/dag/frontend-recovery-capsule.js +0 -455
- package/dist/workflows/dag/frontend-recovery-controller.js +0 -202
- package/dist/workflows/dag/frontend-recovery-lineage.js +0 -178
- package/dist/workflows/dag/frontend-review-findings.js +0 -270
- package/dist/workflows/dag/frontend-shadow-dual-write.js +0 -914
- package/dist/workflows/dag/frontend-shape-capsule-store.js +0 -191
- package/dist/workflows/dag/frontend-shape-facts.js +0 -409
- package/dist/workflows/dag/frontend-shape.js +0 -427
- package/dist/workflows/dag/frontend-source-fidelity-ledger.js +0 -108
- package/dist/workflows/dag/frontend-split-application-service.js +0 -203
- package/dist/workflows/dag/frontend-split-orchestrator.js +0 -899
- package/dist/workflows/dag/frontend-typed-event-store.js +0 -452
- package/dist/workflows/dag/frontend-typed-event-transaction.js +0 -180
- package/dist/workflows/dag/frontend-writer-admission.js +0 -285
- package/dist/workflows/dag/frontend-writer-status.js +0 -256
- package/dist/workflows/dag/recovery-lease.js +0 -80
- package/docs/templates/frontend-implementation-dag.json +0 -89
- package/docs/templates/spec-registry.schema.json +0 -45
- package/skills/frontend-bounded-implement/references/code-standards.md +0 -19
- package/skills/frontend-contract/SKILL.md +0 -23
- package/skills/frontend-contract/references/contract-protocol.md +0 -34
- package/skills/frontend-plan/SKILL.md +0 -26
- package/skills/frontend-plan/references/decision-contract.md +0 -37
- package/skills/frontend-plan/references/design-decisions.md +0 -17
- package/skills/frontend-scout/SKILL.md +0 -25
- package/skills/frontend-scout/references/design-evidence.md +0 -16
- package/skills/frontend-scout/references/scout-evidence.md +0 -23
- /package/dist/{worker → infrastructure}/console/repo-fingerprint.js +0 -0
|
@@ -11,15 +11,30 @@ import {
|
|
|
11
11
|
} from "../constants.js";
|
|
12
12
|
import { clearNode, el } from "../dom.js";
|
|
13
13
|
import { attemptFailureLabel } from "../dag-history-labels.js";
|
|
14
|
+
import {
|
|
15
|
+
contextTransferReasonLabel,
|
|
16
|
+
describeTransferError,
|
|
17
|
+
formatContextTransferReasonList,
|
|
18
|
+
} from "../dag-context-reason-labels.js";
|
|
14
19
|
import {
|
|
15
20
|
fetchJson,
|
|
16
21
|
fetchSpecEvidenceFile,
|
|
17
22
|
fetchDagNodeInput,
|
|
23
|
+
fetchDagNodeContext,
|
|
24
|
+
fetchDagNodeArtifacts,
|
|
25
|
+
fetchDagNodeArtifactPreview,
|
|
26
|
+
fetchDagRunLineage,
|
|
18
27
|
fetchDagNodeProcess,
|
|
19
28
|
postOperatorAction,
|
|
20
29
|
fetchOperatorOperation,
|
|
30
|
+
downloadContextExport,
|
|
21
31
|
} from "../api.js";
|
|
22
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
badge,
|
|
34
|
+
formatLedgerSummary,
|
|
35
|
+
formatMs,
|
|
36
|
+
prettyJsonBlock,
|
|
37
|
+
} from "../format.js";
|
|
23
38
|
import { renderMarkdown, renderInspectorDocument } from "../markdown-render.js";
|
|
24
39
|
import { describeDagNodePurpose } from "../dag-node-purpose.js";
|
|
25
40
|
import { buildPromptRestartCandidateOptions } from "../prompt-restart-candidates.js";
|
|
@@ -49,6 +64,7 @@ import {
|
|
|
49
64
|
formatSessionEventTime,
|
|
50
65
|
renderSessionTimeline,
|
|
51
66
|
buildPageStyledDoc,
|
|
67
|
+
fetchPageStylesheetText,
|
|
52
68
|
} from "./session-timeline.js";
|
|
53
69
|
import { createCustomSelect } from "../custom-select.js";
|
|
54
70
|
|
|
@@ -58,6 +74,89 @@ import { createCustomSelect } from "../custom-select.js";
|
|
|
58
74
|
// reference, always resolving the currently selected node identity.
|
|
59
75
|
const inspectorSelection = { dagRunId: null, node: null };
|
|
60
76
|
|
|
77
|
+
/**
|
|
78
|
+
* 失败状态行统一入口:错误信息中嵌入的已知 reason code 先给中文语义
|
|
79
|
+
* (原码与原始信息保留),未知内容原样展示,不做成功方向的猜测。
|
|
80
|
+
*/
|
|
81
|
+
function transferErrorStatusText(error, fallback) {
|
|
82
|
+
const raw = error instanceof Error ? error.message : String(error ?? "");
|
|
83
|
+
return describeTransferError(raw) ?? (raw || fallback);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export const DAG_INSPECTOR_READ_ONLY_TABS = Object.freeze([
|
|
87
|
+
["overview", "节点概览", "ri-dashboard-line"],
|
|
88
|
+
["input", "执行设置", "ri-login-box-line"],
|
|
89
|
+
["context", "实际上下文", "ri-stack-line"],
|
|
90
|
+
["timeline", "执行记录", "ri-route-line"],
|
|
91
|
+
["spec-evidence", "规范证据", "ri-book-open-line"],
|
|
92
|
+
["output", "产出", "ri-file-text-line"],
|
|
93
|
+
["lineage", "来源与影响", "ri-git-branch-line"],
|
|
94
|
+
]);
|
|
95
|
+
|
|
96
|
+
export const DAG_INSPECTOR_MUTATION_TAB = Object.freeze([
|
|
97
|
+
"prompt-restart",
|
|
98
|
+
"导出与重跑",
|
|
99
|
+
"ri-share-forward-line",
|
|
100
|
+
]);
|
|
101
|
+
|
|
102
|
+
export const DAG_ACTION_SECTION_ORDER = Object.freeze([
|
|
103
|
+
"artifact-revision",
|
|
104
|
+
"prompt-restart",
|
|
105
|
+
"context-export",
|
|
106
|
+
]);
|
|
107
|
+
|
|
108
|
+
export const ARTIFACT_FILE_PREVIEW_SURFACES = Object.freeze({
|
|
109
|
+
produced: "produced-artifact-preview",
|
|
110
|
+
consumed: "consumed-artifact-preview",
|
|
111
|
+
revisionSelection: "artifact-revision-current-preview",
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
export const CONTEXT_EXPORT_TARGETS = Object.freeze([
|
|
115
|
+
{
|
|
116
|
+
value: "generic",
|
|
117
|
+
label: "通用 Agent 交接包",
|
|
118
|
+
description: "适合交给其他 Agent 或 LLM 继续处理",
|
|
119
|
+
icon: "ri-robot-2-line",
|
|
120
|
+
contents: "receipt · manifest · handoff.md",
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
value: "pi-session",
|
|
124
|
+
label: "Pi 可重开会话",
|
|
125
|
+
description: "兼容时附带可由 Pi 打开的 session JSONL",
|
|
126
|
+
icon: "ri-terminal-box-line",
|
|
127
|
+
contents: "receipt · manifest · session.jsonl(兼容时)",
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
value: "readable",
|
|
131
|
+
label: "可读执行记录",
|
|
132
|
+
description: "便于人工审阅,不承诺完整重现执行",
|
|
133
|
+
icon: "ri-file-list-3-line",
|
|
134
|
+
contents: "receipt · manifest · transcript.md",
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
value: "pi-handoff",
|
|
138
|
+
label: "Pi Handoff 交接包",
|
|
139
|
+
description: "面向 Pi Handoff 工作流的兼容格式",
|
|
140
|
+
icon: "ri-share-forward-box-line",
|
|
141
|
+
contents: "receipt · manifest · handoff.md",
|
|
142
|
+
},
|
|
143
|
+
]);
|
|
144
|
+
|
|
145
|
+
export const CONTEXT_EXPORT_PROFILES = Object.freeze([
|
|
146
|
+
{
|
|
147
|
+
value: "redacted",
|
|
148
|
+
label: "脱敏后导出",
|
|
149
|
+
description: "自动隐藏匹配到的疑似凭据",
|
|
150
|
+
badge: "推荐",
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
value: "raw",
|
|
154
|
+
label: "保留原始内容",
|
|
155
|
+
description: "可能包含凭据、绝对路径和未脱敏文本",
|
|
156
|
+
badge: "高风险",
|
|
157
|
+
},
|
|
158
|
+
]);
|
|
159
|
+
|
|
61
160
|
function createPromptRestartCopyButton(readCandidate) {
|
|
62
161
|
const buildIcon = (kind) => {
|
|
63
162
|
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
@@ -106,7 +205,7 @@ function createPromptRestartCopyButton(readCandidate) {
|
|
|
106
205
|
};
|
|
107
206
|
const button = el(
|
|
108
207
|
"button",
|
|
109
|
-
"dags-copy-btn
|
|
208
|
+
"dags-copy-btn prompt-restart-copy-button",
|
|
110
209
|
);
|
|
111
210
|
button.type = "button";
|
|
112
211
|
button.setAttribute("data-testid", "prompt-restart-copy");
|
|
@@ -114,6 +213,8 @@ function createPromptRestartCopyButton(readCandidate) {
|
|
|
114
213
|
button.setAttribute("title", "复制当前提示词候选");
|
|
115
214
|
button.appendChild(buildIcon("copy"));
|
|
116
215
|
button.appendChild(buildIcon("check"));
|
|
216
|
+
const label = el("span", "prompt-restart-copy-label", "复制提示词");
|
|
217
|
+
button.appendChild(label);
|
|
117
218
|
button.addEventListener("click", (event) => {
|
|
118
219
|
event.preventDefault();
|
|
119
220
|
event.stopPropagation();
|
|
@@ -127,10 +228,12 @@ function createPromptRestartCopyButton(readCandidate) {
|
|
|
127
228
|
button.classList.toggle("is-copied", ok);
|
|
128
229
|
button.setAttribute("aria-label", feedback);
|
|
129
230
|
button.setAttribute("title", feedback);
|
|
231
|
+
label.textContent = feedback;
|
|
130
232
|
setTimeout(() => {
|
|
131
233
|
button.classList.remove("is-copied");
|
|
132
234
|
button.setAttribute("aria-label", "复制当前提示词候选");
|
|
133
235
|
button.setAttribute("title", "复制当前提示词候选");
|
|
236
|
+
label.textContent = "复制提示词";
|
|
134
237
|
}, 1500);
|
|
135
238
|
});
|
|
136
239
|
});
|
|
@@ -1086,8 +1189,8 @@ function renderDagNodeInputDetail(content, data) {
|
|
|
1086
1189
|
"p",
|
|
1087
1190
|
"muted",
|
|
1088
1191
|
facts.parseError
|
|
1089
|
-
|
|
1090
|
-
|
|
1192
|
+
? `产物 ${facts.artifact} 读取失败(${facts.parseError})。`
|
|
1193
|
+
: `产物 ${facts.artifact} 尚未生成(节点未执行或缺失)。`,
|
|
1091
1194
|
),
|
|
1092
1195
|
);
|
|
1093
1196
|
} else {
|
|
@@ -1101,11 +1204,7 @@ function renderDagNodeInputDetail(content, data) {
|
|
|
1101
1204
|
null,
|
|
1102
1205
|
);
|
|
1103
1206
|
section.appendChild(dl);
|
|
1104
|
-
appendCodeList(
|
|
1105
|
-
section,
|
|
1106
|
-
"requested",
|
|
1107
|
-
facts.requested,
|
|
1108
|
-
);
|
|
1207
|
+
appendCodeList(section, "requested", facts.requested);
|
|
1109
1208
|
for (const missingEntry of facts.missing ?? []) {
|
|
1110
1209
|
section.appendChild(
|
|
1111
1210
|
el(
|
|
@@ -1306,9 +1405,7 @@ async function fetchPrewriteResultJson(dagRunId) {
|
|
|
1306
1405
|
* relying on source-string assertions.
|
|
1307
1406
|
*/
|
|
1308
1407
|
export async function fetchFrontendGateLedgerData(dagRunId) {
|
|
1309
|
-
const run = await fetchJson(
|
|
1310
|
-
`/api/dag-runs/${encodeURIComponent(dagRunId)}`,
|
|
1311
|
-
);
|
|
1408
|
+
const run = await fetchJson(`/api/dag-runs/${encodeURIComponent(dagRunId)}`);
|
|
1312
1409
|
const taskId = typeof run?.taskId === "string" ? run.taskId : "";
|
|
1313
1410
|
const prewriteResult = await fetchPrewriteResultJson(dagRunId);
|
|
1314
1411
|
let ledgerBody = null;
|
|
@@ -1361,14 +1458,733 @@ async function renderFrontendGateLedger(content, dagRunId) {
|
|
|
1361
1458
|
* restart flow: preflight (dagRerunPlan) binds the edit into planHash, dagRerun
|
|
1362
1459
|
* executes it as a Console operation, and on success we jump to the new run.
|
|
1363
1460
|
*/
|
|
1461
|
+
function buildContextExportSection(dagRunId, node) {
|
|
1462
|
+
const zone = el("section", "dag-action-export-zone");
|
|
1463
|
+
zone.dataset.actionSection = "context-export";
|
|
1464
|
+
const divider = el("div", "dag-action-export-divider");
|
|
1465
|
+
divider.append(
|
|
1466
|
+
el("span", null, "不需要重跑?"),
|
|
1467
|
+
el("p", null, "你也可以只把这个节点的上下文打包带走。"),
|
|
1468
|
+
);
|
|
1469
|
+
zone.appendChild(divider);
|
|
1470
|
+
const section = el("div", "prompt-restart-box context-export-box");
|
|
1471
|
+
section.setAttribute("role", "region");
|
|
1472
|
+
section.setAttribute("aria-labelledby", "context-export-title");
|
|
1473
|
+
const heading = el("div", "context-export-heading");
|
|
1474
|
+
const headingCopy = el("div", null);
|
|
1475
|
+
headingCopy.appendChild(el("span", "context-export-eyebrow", "上下文交接包"));
|
|
1476
|
+
const title = el("h4", null, "把这个节点交给另一个 Agent");
|
|
1477
|
+
title.id = "context-export-title";
|
|
1478
|
+
headingCopy.appendChild(title);
|
|
1479
|
+
headingCopy.appendChild(
|
|
1480
|
+
el("p", null, "选择接收方式和保护级别,系统会生成一个可核验的 ZIP 交接包。"),
|
|
1481
|
+
);
|
|
1482
|
+
const invariant = el("div", "context-export-invariant");
|
|
1483
|
+
const lockIcon = el("i", "ri-lock-2-line");
|
|
1484
|
+
lockIcon.setAttribute("aria-hidden", "true");
|
|
1485
|
+
invariant.append(lockIcon, document.createTextNode(" 原运行保持不变"));
|
|
1486
|
+
heading.append(headingCopy, invariant);
|
|
1487
|
+
section.appendChild(heading);
|
|
1488
|
+
|
|
1489
|
+
const workspace = el("div", "context-export-workspace");
|
|
1490
|
+
const controls = el("div", "context-export-controls");
|
|
1491
|
+
const targetFieldset = document.createElement("fieldset");
|
|
1492
|
+
targetFieldset.className = "context-export-fieldset";
|
|
1493
|
+
targetFieldset.dataset.testid = "context-export-target";
|
|
1494
|
+
targetFieldset.appendChild(el("legend", null, "交给谁使用?"));
|
|
1495
|
+
const targetChoices = el("div", "context-export-target-grid");
|
|
1496
|
+
for (const [index, option] of CONTEXT_EXPORT_TARGETS.entries()) {
|
|
1497
|
+
const choice = el("label", "context-export-choice");
|
|
1498
|
+
const input = document.createElement("input");
|
|
1499
|
+
input.type = "radio";
|
|
1500
|
+
input.name = "context-export-target";
|
|
1501
|
+
input.value = option.value;
|
|
1502
|
+
input.checked = index === 0;
|
|
1503
|
+
const icon = el("i", option.icon);
|
|
1504
|
+
icon.setAttribute("aria-hidden", "true");
|
|
1505
|
+
const copy = el("span", "context-export-choice-copy");
|
|
1506
|
+
copy.append(
|
|
1507
|
+
el("strong", null, option.label),
|
|
1508
|
+
el("small", null, option.description),
|
|
1509
|
+
);
|
|
1510
|
+
choice.append(input, icon, copy);
|
|
1511
|
+
targetChoices.appendChild(choice);
|
|
1512
|
+
}
|
|
1513
|
+
targetFieldset.appendChild(targetChoices);
|
|
1514
|
+
controls.appendChild(targetFieldset);
|
|
1515
|
+
|
|
1516
|
+
const profileFieldset = document.createElement("fieldset");
|
|
1517
|
+
profileFieldset.className = "context-export-fieldset";
|
|
1518
|
+
profileFieldset.dataset.testid = "context-export-profile";
|
|
1519
|
+
profileFieldset.appendChild(el("legend", null, "如何保护内容?"));
|
|
1520
|
+
const profileChoices = el("div", "context-export-profile-grid");
|
|
1521
|
+
for (const [index, option] of CONTEXT_EXPORT_PROFILES.entries()) {
|
|
1522
|
+
const choice = el(
|
|
1523
|
+
"label",
|
|
1524
|
+
`context-export-profile-choice${option.value === "raw" ? " is-risk" : ""}`,
|
|
1525
|
+
);
|
|
1526
|
+
const input = document.createElement("input");
|
|
1527
|
+
input.type = "radio";
|
|
1528
|
+
input.name = "context-export-profile";
|
|
1529
|
+
input.value = option.value;
|
|
1530
|
+
input.checked = index === 0;
|
|
1531
|
+
const copy = el("span", "context-export-choice-copy");
|
|
1532
|
+
const titleRow = el("span", "context-export-profile-title");
|
|
1533
|
+
titleRow.append(
|
|
1534
|
+
el("strong", null, option.label),
|
|
1535
|
+
el("span", `context-export-profile-badge is-${option.value}`, option.badge),
|
|
1536
|
+
);
|
|
1537
|
+
copy.append(titleRow, el("small", null, option.description));
|
|
1538
|
+
choice.append(input, copy);
|
|
1539
|
+
profileChoices.appendChild(choice);
|
|
1540
|
+
}
|
|
1541
|
+
profileFieldset.appendChild(profileChoices);
|
|
1542
|
+
controls.appendChild(profileFieldset);
|
|
1543
|
+
|
|
1544
|
+
const rawAckLabel = el("label", "prompt-restart-field context-export-raw-ack");
|
|
1545
|
+
const rawAck = document.createElement("input");
|
|
1546
|
+
rawAck.type = "checkbox";
|
|
1547
|
+
rawAck.dataset.testid = "context-export-raw-ack";
|
|
1548
|
+
rawAckLabel.appendChild(rawAck);
|
|
1549
|
+
const rawAckCopy = el("span", null);
|
|
1550
|
+
rawAckCopy.append(
|
|
1551
|
+
el("strong", null, "确认导出未脱敏内容"),
|
|
1552
|
+
el("small", null, "我理解文件可能包含凭据、绝对路径和执行原文。"),
|
|
1553
|
+
);
|
|
1554
|
+
rawAckLabel.appendChild(rawAckCopy);
|
|
1555
|
+
rawAckLabel.hidden = true;
|
|
1556
|
+
controls.appendChild(rawAckLabel);
|
|
1557
|
+
workspace.appendChild(controls);
|
|
1558
|
+
|
|
1559
|
+
const ticket = el("aside", "context-export-ticket");
|
|
1560
|
+
ticket.setAttribute("aria-label", "交接包预览");
|
|
1561
|
+
ticket.setAttribute("aria-live", "polite");
|
|
1562
|
+
const ticketTop = el("div", "context-export-ticket-top");
|
|
1563
|
+
ticketTop.append(
|
|
1564
|
+
el("span", "context-export-ticket-type", "CONTEXT / ZIP"),
|
|
1565
|
+
el("span", "context-export-ticket-safety", "已脱敏"),
|
|
1566
|
+
);
|
|
1567
|
+
const ticketTitle = el("strong", "context-export-ticket-title", "通用 Agent 交接包");
|
|
1568
|
+
const ticketRows = el("dl", "context-export-ticket-rows");
|
|
1569
|
+
const appendTicketRow = (label, value, className) => {
|
|
1570
|
+
const term = el("dt", null, label);
|
|
1571
|
+
const detail = el("dd", className ?? null, value);
|
|
1572
|
+
ticketRows.append(term, detail);
|
|
1573
|
+
return detail;
|
|
1574
|
+
};
|
|
1575
|
+
const recipientValue = appendTicketRow("用途", "适合交给其他 Agent 或 LLM 继续处理");
|
|
1576
|
+
const contentsValue = appendTicketRow("包含", CONTEXT_EXPORT_TARGETS[0].contents, "is-mono");
|
|
1577
|
+
const protectionValue = appendTicketRow("保护", "自动隐藏匹配到的疑似凭据");
|
|
1578
|
+
const attemptValue = appendTicketRow(
|
|
1579
|
+
"来源",
|
|
1580
|
+
`${node.nodeId} · attempt ${selectedAttemptNumber(dagRunId, node) ?? 1}`,
|
|
1581
|
+
"is-mono",
|
|
1582
|
+
);
|
|
1583
|
+
ticket.append(
|
|
1584
|
+
ticketTop,
|
|
1585
|
+
el("span", "context-export-ticket-label", "即将生成"),
|
|
1586
|
+
ticketTitle,
|
|
1587
|
+
ticketRows,
|
|
1588
|
+
el("p", "context-export-ticket-foot", "附带 manifest 哈希,可核对来源与完整性"),
|
|
1589
|
+
);
|
|
1590
|
+
workspace.appendChild(ticket);
|
|
1591
|
+
section.appendChild(workspace);
|
|
1592
|
+
|
|
1593
|
+
const selectedValue = (name) =>
|
|
1594
|
+
section.querySelector(`input[name="${name}"]:checked`)?.value ?? "";
|
|
1595
|
+
const updatePreview = () => {
|
|
1596
|
+
const targetValue = selectedValue("context-export-target");
|
|
1597
|
+
const profileValue = selectedValue("context-export-profile");
|
|
1598
|
+
const target =
|
|
1599
|
+
CONTEXT_EXPORT_TARGETS.find((option) => option.value === targetValue) ??
|
|
1600
|
+
CONTEXT_EXPORT_TARGETS[0];
|
|
1601
|
+
const profile =
|
|
1602
|
+
CONTEXT_EXPORT_PROFILES.find((option) => option.value === profileValue) ??
|
|
1603
|
+
CONTEXT_EXPORT_PROFILES[0];
|
|
1604
|
+
ticketTitle.textContent = target.label;
|
|
1605
|
+
recipientValue.textContent = target.description;
|
|
1606
|
+
contentsValue.textContent = target.contents;
|
|
1607
|
+
protectionValue.textContent = profile.description;
|
|
1608
|
+
attemptValue.textContent = `${node.nodeId} · attempt ${selectedAttemptNumber(dagRunId, node) ?? 1}`;
|
|
1609
|
+
const safety = ticket.querySelector(".context-export-ticket-safety");
|
|
1610
|
+
if (safety) {
|
|
1611
|
+
safety.textContent = profileValue === "raw" ? "原始内容" : "已脱敏";
|
|
1612
|
+
safety.classList.toggle("is-risk", profileValue === "raw");
|
|
1613
|
+
}
|
|
1614
|
+
rawAckLabel.hidden = profileValue !== "raw";
|
|
1615
|
+
if (profileValue !== "raw") rawAck.checked = false;
|
|
1616
|
+
};
|
|
1617
|
+
for (const input of section.querySelectorAll(
|
|
1618
|
+
'input[name="context-export-target"], input[name="context-export-profile"]',
|
|
1619
|
+
)) {
|
|
1620
|
+
input.addEventListener("change", updatePreview);
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
const actions = el("div", "prompt-restart-actions");
|
|
1624
|
+
const submit = document.createElement("button");
|
|
1625
|
+
submit.type = "button";
|
|
1626
|
+
submit.className = "prompt-restart-submit context-export-submit";
|
|
1627
|
+
submit.dataset.testid = "context-export-submit";
|
|
1628
|
+
const submitIcon = el("i", "ri-download-cloud-2-line");
|
|
1629
|
+
submitIcon.setAttribute("aria-hidden", "true");
|
|
1630
|
+
submit.append(submitIcon, document.createTextNode(" 生成交接包并下载"));
|
|
1631
|
+
actions.appendChild(submit);
|
|
1632
|
+
section.appendChild(actions);
|
|
1633
|
+
const status = el("p", "prompt-restart-status", "");
|
|
1634
|
+
status.setAttribute("role", "status");
|
|
1635
|
+
status.setAttribute("aria-live", "polite");
|
|
1636
|
+
section.appendChild(status);
|
|
1637
|
+
const setStatus = (message, error = false) => {
|
|
1638
|
+
status.textContent = message;
|
|
1639
|
+
status.classList.toggle("prompt-restart-error", error);
|
|
1640
|
+
};
|
|
1641
|
+
submit.addEventListener("click", () => {
|
|
1642
|
+
void (async () => {
|
|
1643
|
+
const targetValue = selectedValue("context-export-target");
|
|
1644
|
+
const profileValue = selectedValue("context-export-profile");
|
|
1645
|
+
if (profileValue === "raw" && !rawAck.checked) {
|
|
1646
|
+
setStatus("原始导出前必须勾选风险确认。", true);
|
|
1647
|
+
return;
|
|
1648
|
+
}
|
|
1649
|
+
submit.disabled = true;
|
|
1650
|
+
try {
|
|
1651
|
+
setStatus("正在生成可验证 Context Bundle…");
|
|
1652
|
+
const clientRequestId = `inspect-context-export-${Date.now()}-${Math.random()
|
|
1653
|
+
.toString(36)
|
|
1654
|
+
.slice(2, 10)}`;
|
|
1655
|
+
const response = await postOperatorAction(
|
|
1656
|
+
"contextExport",
|
|
1657
|
+
{
|
|
1658
|
+
runId: dagRunId,
|
|
1659
|
+
nodeId: node.nodeId,
|
|
1660
|
+
attempt: selectedAttemptNumber(dagRunId, node) ?? 1,
|
|
1661
|
+
target: targetValue,
|
|
1662
|
+
profile: profileValue,
|
|
1663
|
+
rawAcknowledged: profileValue === "raw" && rawAck.checked,
|
|
1664
|
+
},
|
|
1665
|
+
clientRequestId,
|
|
1666
|
+
);
|
|
1667
|
+
const operationId = response.body?.operationId;
|
|
1668
|
+
if (!response.ok || !operationId) {
|
|
1669
|
+
throw new Error(response.body?.error?.message ?? "导出操作未被接受");
|
|
1670
|
+
}
|
|
1671
|
+
const operation = await pollOperationToTerminal(operationId, (message) =>
|
|
1672
|
+
setStatus(message.replace("续跑", "导出")),
|
|
1673
|
+
);
|
|
1674
|
+
if (operation?.state !== "succeeded" || !operation.result?.downloadUrl) {
|
|
1675
|
+
throw new Error(operation?.errorMessage ?? "导出未成功完成");
|
|
1676
|
+
}
|
|
1677
|
+
setStatus("Bundle 已生成,正在安全下载…");
|
|
1678
|
+
const downloaded = await downloadContextExport(operation.result.downloadUrl);
|
|
1679
|
+
const objectUrl = URL.createObjectURL(downloaded.blob);
|
|
1680
|
+
const link = document.createElement("a");
|
|
1681
|
+
link.href = objectUrl;
|
|
1682
|
+
link.download = downloaded.filename;
|
|
1683
|
+
document.body.appendChild(link);
|
|
1684
|
+
link.click();
|
|
1685
|
+
link.remove();
|
|
1686
|
+
URL.revokeObjectURL(objectUrl);
|
|
1687
|
+
setStatus(
|
|
1688
|
+
`下载完成 · ${operation.result.bundleId} · ${new Intl.NumberFormat("zh-CN").format(operation.result.bytes)} bytes\nSHA-256 ${operation.result.bundleSha256}`,
|
|
1689
|
+
);
|
|
1690
|
+
} catch (error) {
|
|
1691
|
+
setStatus(transferErrorStatusText(error, "导出未成功完成"), true);
|
|
1692
|
+
} finally {
|
|
1693
|
+
submit.disabled = false;
|
|
1694
|
+
}
|
|
1695
|
+
})();
|
|
1696
|
+
});
|
|
1697
|
+
updatePreview();
|
|
1698
|
+
zone.appendChild(section);
|
|
1699
|
+
return zone;
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
function createDagActionCard({ eyebrow, title, description, icon, className }) {
|
|
1703
|
+
const section = el("section", `dag-action-card ${className ?? ""}`.trim());
|
|
1704
|
+
const header = el("div", "dag-action-card-header");
|
|
1705
|
+
const iconBox = el("div", "dag-action-card-icon");
|
|
1706
|
+
const iconElement = el("i", icon);
|
|
1707
|
+
iconElement.setAttribute("aria-hidden", "true");
|
|
1708
|
+
iconBox.appendChild(iconElement);
|
|
1709
|
+
const copy = el("div", "dag-action-card-copy");
|
|
1710
|
+
copy.append(
|
|
1711
|
+
el("span", "dag-action-eyebrow", eyebrow),
|
|
1712
|
+
el("h4", null, title),
|
|
1713
|
+
el("p", null, description),
|
|
1714
|
+
);
|
|
1715
|
+
header.append(iconBox, copy);
|
|
1716
|
+
const body = el("div", "dag-action-card-body");
|
|
1717
|
+
section.append(header, body);
|
|
1718
|
+
return { section, body };
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
function buildDagActionAvailability({ title, description, reasonCode, tone = "neutral" }) {
|
|
1722
|
+
const panel = el("div", `dag-action-availability is-${tone}`);
|
|
1723
|
+
const icon = el(
|
|
1724
|
+
"i",
|
|
1725
|
+
tone === "blocked"
|
|
1726
|
+
? "ri-forbid-2-line"
|
|
1727
|
+
: tone === "ready"
|
|
1728
|
+
? "ri-checkbox-circle-line"
|
|
1729
|
+
: "ri-information-line",
|
|
1730
|
+
);
|
|
1731
|
+
icon.setAttribute("aria-hidden", "true");
|
|
1732
|
+
const copy = el("div", null);
|
|
1733
|
+
copy.append(el("strong", null, title), el("p", null, description));
|
|
1734
|
+
if (reasonCode) {
|
|
1735
|
+
const label = contextTransferReasonLabel(reasonCode);
|
|
1736
|
+
if (label) copy.appendChild(el("p", "dag-action-reason-copy", label));
|
|
1737
|
+
copy.appendChild(el("code", null, reasonCode));
|
|
1738
|
+
}
|
|
1739
|
+
panel.append(icon, copy);
|
|
1740
|
+
return panel;
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
function buildFullContentCta() {
|
|
1744
|
+
// Honest limitation note (review 2026-08-26): the context bundle exports
|
|
1745
|
+
// node *inputs*; it does not necessarily contain the full bytes of a
|
|
1746
|
+
// produced artifact or a revision candidate, so pointing the truncated
|
|
1747
|
+
// preview at the export flow would overpromise. State the bound plainly.
|
|
1748
|
+
return el(
|
|
1749
|
+
"p",
|
|
1750
|
+
"artifact-content-preview-full-cta",
|
|
1751
|
+
"预览有截断;当前版本暂不提供完整文件下载。",
|
|
1752
|
+
);
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
function createArtifactContentPreview(
|
|
1756
|
+
dagRunId,
|
|
1757
|
+
nodeId,
|
|
1758
|
+
resolveArtifact,
|
|
1759
|
+
{ label = "查看文件内容", testId = "artifact-file-preview" } = {},
|
|
1760
|
+
) {
|
|
1761
|
+
const details = document.createElement("details");
|
|
1762
|
+
details.className = "artifact-content-preview";
|
|
1763
|
+
details.dataset.testid = testId;
|
|
1764
|
+
const summary = document.createElement("summary");
|
|
1765
|
+
const icon = el("i", "ri-eye-line");
|
|
1766
|
+
icon.setAttribute("aria-hidden", "true");
|
|
1767
|
+
const summaryText = el("span", null, label);
|
|
1768
|
+
summary.append(icon, summaryText);
|
|
1769
|
+
details.appendChild(summary);
|
|
1770
|
+
const host = el("div", "artifact-content-preview-body");
|
|
1771
|
+
details.appendChild(host);
|
|
1772
|
+
let loadedArtifactId = null;
|
|
1773
|
+
let requestVersion = 0;
|
|
1774
|
+
|
|
1775
|
+
const reset = () => {
|
|
1776
|
+
requestVersion += 1;
|
|
1777
|
+
loadedArtifactId = null;
|
|
1778
|
+
details.open = false;
|
|
1779
|
+
clearNode(host);
|
|
1780
|
+
};
|
|
1781
|
+
const renderStatus = (message, error = false) => {
|
|
1782
|
+
clearNode(host);
|
|
1783
|
+
const status = el(
|
|
1784
|
+
"p",
|
|
1785
|
+
error ? "artifact-content-preview-error" : "muted",
|
|
1786
|
+
message,
|
|
1787
|
+
);
|
|
1788
|
+
status.setAttribute("role", error ? "alert" : "status");
|
|
1789
|
+
status.setAttribute("aria-live", "polite");
|
|
1790
|
+
host.appendChild(status);
|
|
1791
|
+
};
|
|
1792
|
+
const load = async () => {
|
|
1793
|
+
const artifact = resolveArtifact();
|
|
1794
|
+
if (!artifact?.artifactId) {
|
|
1795
|
+
renderStatus("没有可预览的文件。", true);
|
|
1796
|
+
return;
|
|
1797
|
+
}
|
|
1798
|
+
if (loadedArtifactId === artifact.artifactId) return;
|
|
1799
|
+
const version = ++requestVersion;
|
|
1800
|
+
renderStatus("正在读取并核验文件内容…");
|
|
1801
|
+
const result = await fetchDagNodeArtifactPreview(
|
|
1802
|
+
dagRunId,
|
|
1803
|
+
nodeId,
|
|
1804
|
+
artifact.artifactId,
|
|
1805
|
+
);
|
|
1806
|
+
if (version !== requestVersion || !details.isConnected) return;
|
|
1807
|
+
if (!result.ok || !result.body) {
|
|
1808
|
+
renderStatus(result.body?.error || "文件内容暂时无法预览。", true);
|
|
1809
|
+
return;
|
|
1810
|
+
}
|
|
1811
|
+
loadedArtifactId = artifact.artifactId;
|
|
1812
|
+
const data = result.body;
|
|
1813
|
+
clearNode(host);
|
|
1814
|
+
const header = el("div", "artifact-content-preview-meta");
|
|
1815
|
+
const metaChildren = [
|
|
1816
|
+
el("code", null, data.path ?? artifact.path ?? artifact.artifactId),
|
|
1817
|
+
el(
|
|
1818
|
+
"span",
|
|
1819
|
+
null,
|
|
1820
|
+
`${data.mediaType ?? "unknown"} · ${new Intl.NumberFormat("zh-CN").format(data.sourceBytes ?? 0)} bytes`,
|
|
1821
|
+
),
|
|
1822
|
+
];
|
|
1823
|
+
if (typeof data.sha256 === "string" && data.sha256.length >= 12) {
|
|
1824
|
+
const sha = el(
|
|
1825
|
+
"span",
|
|
1826
|
+
null,
|
|
1827
|
+
` · sha256 ${data.sha256.slice(0, 12)}…`,
|
|
1828
|
+
);
|
|
1829
|
+
sha.title = data.sha256;
|
|
1830
|
+
metaChildren.push(sha);
|
|
1831
|
+
}
|
|
1832
|
+
header.append(...metaChildren);
|
|
1833
|
+
host.appendChild(header);
|
|
1834
|
+
if (data.previewKind !== "text" || typeof data.content !== "string") {
|
|
1835
|
+
host.appendChild(
|
|
1836
|
+
el(
|
|
1837
|
+
"p",
|
|
1838
|
+
"artifact-content-preview-unavailable",
|
|
1839
|
+
"该文件的媒体类型不支持安全内嵌显示;路径、大小和 SHA-256 已核验。",
|
|
1840
|
+
),
|
|
1841
|
+
);
|
|
1842
|
+
return;
|
|
1843
|
+
}
|
|
1844
|
+
const normalizedMedia = String(data.mediaType ?? "").toLowerCase();
|
|
1845
|
+
if (normalizedMedia.includes("markdown")) {
|
|
1846
|
+
const documentPreview = el("div", "artifact-content-preview-document");
|
|
1847
|
+
documentPreview.appendChild(renderMarkdown(data.content));
|
|
1848
|
+
host.appendChild(documentPreview);
|
|
1849
|
+
} else {
|
|
1850
|
+
const pre = el("pre", "artifact-content-preview-code");
|
|
1851
|
+
pre.textContent = normalizedMedia.includes("json")
|
|
1852
|
+
? prettyJsonBlock(data.content) ?? data.content
|
|
1853
|
+
: data.content;
|
|
1854
|
+
host.appendChild(pre);
|
|
1855
|
+
}
|
|
1856
|
+
const notes = [];
|
|
1857
|
+
if (data.redacted) notes.push("疑似凭据已脱敏");
|
|
1858
|
+
if (data.truncated) {
|
|
1859
|
+
notes.push(
|
|
1860
|
+
`仅显示前 ${new Intl.NumberFormat("zh-CN").format(data.maxBytes ?? 0)} bytes`,
|
|
1861
|
+
);
|
|
1862
|
+
}
|
|
1863
|
+
if (notes.length > 0) {
|
|
1864
|
+
host.appendChild(el("p", "artifact-content-preview-note", notes.join(" · ")));
|
|
1865
|
+
}
|
|
1866
|
+
if (data.truncated) {
|
|
1867
|
+
host.appendChild(buildFullContentCta());
|
|
1868
|
+
}
|
|
1869
|
+
};
|
|
1870
|
+
details.addEventListener("toggle", () => {
|
|
1871
|
+
if (details.open) void load();
|
|
1872
|
+
});
|
|
1873
|
+
return { element: details, reset };
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
function buildArtifactRevisionSection(dagRunId, node) {
|
|
1877
|
+
const { section, body } = createDagActionCard({
|
|
1878
|
+
eyebrow: "产出修订",
|
|
1879
|
+
title: "替换产出后继续运行",
|
|
1880
|
+
description: "上传一个候选新版本,先校验差异和影响范围,再决定是否创建新的运行。",
|
|
1881
|
+
icon: "ri-file-transfer-line",
|
|
1882
|
+
className: "artifact-revision-box",
|
|
1883
|
+
});
|
|
1884
|
+
section.dataset.actionSection = "artifact-revision";
|
|
1885
|
+
body.appendChild(
|
|
1886
|
+
buildDagActionAvailability({
|
|
1887
|
+
title: "正在检查是否可以安全修订",
|
|
1888
|
+
description: "读取已冻结的产出关系和修订规则。",
|
|
1889
|
+
}),
|
|
1890
|
+
);
|
|
1891
|
+
void (async () => {
|
|
1892
|
+
const response = await fetchDagNodeArtifacts(dagRunId, node.nodeId);
|
|
1893
|
+
clearNode(body);
|
|
1894
|
+
if (!response.ok || response.body?.binding?.available !== true) {
|
|
1895
|
+
body.appendChild(
|
|
1896
|
+
buildDagActionAvailability({
|
|
1897
|
+
tone: "blocked",
|
|
1898
|
+
title: "这个运行暂时不能安全修订产出",
|
|
1899
|
+
description:
|
|
1900
|
+
"缺少结构化的产出关系,系统无法可靠判断哪些后续节点会受影响,也不会根据文件名或提示词猜测。",
|
|
1901
|
+
reasonCode:
|
|
1902
|
+
response.body?.reasonCode ??
|
|
1903
|
+
response.body?.binding?.reasonCode ??
|
|
1904
|
+
"ARTIFACT_BINDING_UNAVAILABLE",
|
|
1905
|
+
}),
|
|
1906
|
+
);
|
|
1907
|
+
return;
|
|
1908
|
+
}
|
|
1909
|
+
const eligible = (response.body.produces ?? []).filter(
|
|
1910
|
+
(artifact) => artifact.revision?.eligible === true,
|
|
1911
|
+
);
|
|
1912
|
+
if (eligible.length === 0) {
|
|
1913
|
+
body.appendChild(
|
|
1914
|
+
buildDagActionAvailability({
|
|
1915
|
+
title: "这个节点没有可替换的已生成文件",
|
|
1916
|
+
description:
|
|
1917
|
+
"当前节点没有同时满足“已经生成”和“允许替换”的产出。失败节点仍可在下方修改提示词后重跑。",
|
|
1918
|
+
}),
|
|
1919
|
+
);
|
|
1920
|
+
return;
|
|
1921
|
+
}
|
|
1922
|
+
body.appendChild(
|
|
1923
|
+
buildDagActionAvailability({
|
|
1924
|
+
tone: "ready",
|
|
1925
|
+
title: `可修订 ${eligible.length} 个产出`,
|
|
1926
|
+
description: "父运行不会被修改;确认后会创建一条新的派生运行。",
|
|
1927
|
+
}),
|
|
1928
|
+
);
|
|
1929
|
+
const form = el("div", "dag-action-form artifact-revision-form");
|
|
1930
|
+
const artifactLabel = el("label", "prompt-restart-field");
|
|
1931
|
+
artifactLabel.appendChild(el("span", "dag-action-field-label", "选择要替换的产出"));
|
|
1932
|
+
const artifactSelect = document.createElement("select");
|
|
1933
|
+
artifactSelect.className = "prompt-restart-mode-select";
|
|
1934
|
+
artifactSelect.dataset.testid = "artifact-revision-artifact";
|
|
1935
|
+
for (const artifact of eligible) {
|
|
1936
|
+
const option = document.createElement("option");
|
|
1937
|
+
option.value = artifact.artifactId;
|
|
1938
|
+
option.textContent = `${artifact.artifactId} · ${artifact.path}`;
|
|
1939
|
+
artifactSelect.appendChild(option);
|
|
1940
|
+
}
|
|
1941
|
+
artifactLabel.appendChild(artifactSelect);
|
|
1942
|
+
form.appendChild(artifactLabel);
|
|
1943
|
+
const currentArtifactPreview = createArtifactContentPreview(
|
|
1944
|
+
dagRunId,
|
|
1945
|
+
node.nodeId,
|
|
1946
|
+
() => eligible.find((artifact) => artifact.artifactId === artifactSelect.value),
|
|
1947
|
+
{
|
|
1948
|
+
label: "查看当前文件",
|
|
1949
|
+
testId: ARTIFACT_FILE_PREVIEW_SURFACES.revisionSelection,
|
|
1950
|
+
},
|
|
1951
|
+
);
|
|
1952
|
+
form.appendChild(currentArtifactPreview.element);
|
|
1953
|
+
artifactSelect.addEventListener("change", currentArtifactPreview.reset);
|
|
1954
|
+
const fileLabel = el("label", "prompt-restart-field");
|
|
1955
|
+
fileLabel.appendChild(el("span", "dag-action-field-label", "选择新文件(最多 10 MiB)"));
|
|
1956
|
+
const file = document.createElement("input");
|
|
1957
|
+
file.type = "file";
|
|
1958
|
+
file.className = "dag-action-file-input";
|
|
1959
|
+
file.dataset.testid = "artifact-revision-file";
|
|
1960
|
+
fileLabel.appendChild(file);
|
|
1961
|
+
form.appendChild(fileLabel);
|
|
1962
|
+
const reasonLabel = el("label", "prompt-restart-field");
|
|
1963
|
+
reasonLabel.appendChild(el("span", "dag-action-field-label", "为什么要替换?"));
|
|
1964
|
+
const reason = document.createElement("textarea");
|
|
1965
|
+
reason.className = "prompt-restart-textarea";
|
|
1966
|
+
reason.rows = 3;
|
|
1967
|
+
reason.placeholder = "说明为什么要替换此产物";
|
|
1968
|
+
reason.dataset.testid = "artifact-revision-reason";
|
|
1969
|
+
reasonLabel.appendChild(reason);
|
|
1970
|
+
form.appendChild(reasonLabel);
|
|
1971
|
+
const actions = el("div", "prompt-restart-actions");
|
|
1972
|
+
const preview = document.createElement("button");
|
|
1973
|
+
preview.type = "button";
|
|
1974
|
+
preview.className = "prompt-restart-submit dag-action-primary";
|
|
1975
|
+
const previewIcon = el("i", "ri-radar-line");
|
|
1976
|
+
previewIcon.setAttribute("aria-hidden", "true");
|
|
1977
|
+
preview.append(previewIcon, document.createTextNode(" 校验文件并预览影响"));
|
|
1978
|
+
preview.dataset.testid = "artifact-revision-preview";
|
|
1979
|
+
actions.appendChild(preview);
|
|
1980
|
+
form.appendChild(actions);
|
|
1981
|
+
const status = el("p", "prompt-restart-status", "");
|
|
1982
|
+
status.setAttribute("role", "status");
|
|
1983
|
+
status.setAttribute("aria-live", "polite");
|
|
1984
|
+
form.appendChild(status);
|
|
1985
|
+
const planHost = el("div", "artifact-revision-plan");
|
|
1986
|
+
form.appendChild(planHost);
|
|
1987
|
+
body.appendChild(form);
|
|
1988
|
+
const setStatus = (message, error = false) => {
|
|
1989
|
+
status.textContent = message;
|
|
1990
|
+
status.classList.toggle("prompt-restart-error", error);
|
|
1991
|
+
};
|
|
1992
|
+
preview.addEventListener("click", () => {
|
|
1993
|
+
void (async () => {
|
|
1994
|
+
const selected = eligible.find((artifact) => artifact.artifactId === artifactSelect.value);
|
|
1995
|
+
const selectedFile = file.files?.[0];
|
|
1996
|
+
if (!selected || !selectedFile) {
|
|
1997
|
+
setStatus("请选择一个替换文件。", true);
|
|
1998
|
+
return;
|
|
1999
|
+
}
|
|
2000
|
+
if (selectedFile.size > 10 * 1024 * 1024) {
|
|
2001
|
+
setStatus("文件超过 10 MiB 限制。", true);
|
|
2002
|
+
return;
|
|
2003
|
+
}
|
|
2004
|
+
if (!reason.value.trim()) {
|
|
2005
|
+
setStatus("需要填写派生运行原因。", true);
|
|
2006
|
+
return;
|
|
2007
|
+
}
|
|
2008
|
+
preview.disabled = true;
|
|
2009
|
+
clearNode(planHost);
|
|
2010
|
+
try {
|
|
2011
|
+
const dataUrl = await new Promise((resolve, reject) => {
|
|
2012
|
+
const reader = new FileReader();
|
|
2013
|
+
reader.onerror = () => reject(reader.error ?? new Error("读取文件失败"));
|
|
2014
|
+
reader.onload = () => resolve(String(reader.result ?? ""));
|
|
2015
|
+
reader.readAsDataURL(selectedFile);
|
|
2016
|
+
});
|
|
2017
|
+
const candidateBase64 = String(dataUrl).split(",", 2)[1] ?? "";
|
|
2018
|
+
setStatus("正在校验候选字节、schema 与 diff…");
|
|
2019
|
+
const stageId = `artifact-stage-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
2020
|
+
const staged = await postOperatorAction(
|
|
2021
|
+
"artifactRevisionStage",
|
|
2022
|
+
{
|
|
2023
|
+
runId: dagRunId,
|
|
2024
|
+
nodeId: node.nodeId,
|
|
2025
|
+
artifactId: selected.artifactId,
|
|
2026
|
+
mediaType: selected.mediaType,
|
|
2027
|
+
candidateBase64,
|
|
2028
|
+
},
|
|
2029
|
+
stageId,
|
|
2030
|
+
);
|
|
2031
|
+
if (!staged.ok || !staged.body?.operationId) throw new Error(staged.body?.error?.message ?? "修订 staging 失败");
|
|
2032
|
+
const stageOp = await pollOperationToTerminal(staged.body.operationId, (message) =>
|
|
2033
|
+
setStatus(message.replace("续跑", "修订校验")),
|
|
2034
|
+
);
|
|
2035
|
+
const revision = stageOp?.result?.revision;
|
|
2036
|
+
if (stageOp?.state !== "succeeded" || !revision?.revisionId) {
|
|
2037
|
+
throw new Error(stageOp?.errorMessage ?? "修订校验失败");
|
|
2038
|
+
}
|
|
2039
|
+
setStatus("校验通过,正在计算 direct consumers 与传递影响闭包…");
|
|
2040
|
+
const planId = `artifact-plan-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
2041
|
+
const planned = await postOperatorAction(
|
|
2042
|
+
"artifactRevisionPlan",
|
|
2043
|
+
{ runId: dagRunId, artifactId: selected.artifactId, revisionId: revision.revisionId },
|
|
2044
|
+
planId,
|
|
2045
|
+
);
|
|
2046
|
+
if (!planned.ok || !planned.body?.operationId) throw new Error(planned.body?.error?.message ?? "影响预览失败");
|
|
2047
|
+
const planOp = await pollOperationToTerminal(planned.body.operationId, (message) =>
|
|
2048
|
+
setStatus(message.replace("续跑", "影响分析")),
|
|
2049
|
+
);
|
|
2050
|
+
const plan = planOp?.result?.plan;
|
|
2051
|
+
if (!plan) throw new Error(planOp?.errorMessage ?? "影响预览失败");
|
|
2052
|
+
planHost.appendChild(el("h5", null, "修订与影响预览"));
|
|
2053
|
+
planHost.appendChild(
|
|
2054
|
+
transparencyMetaList([
|
|
2055
|
+
["修订 ID", revision.revisionId],
|
|
2056
|
+
["候选文件 SHA-256", revision.candidate?.sha256],
|
|
2057
|
+
["差异类型", revision.diff?.kind],
|
|
2058
|
+
["风险级别", plan.risk],
|
|
2059
|
+
["直接使用者", plan.impact?.directConsumerNodeIds?.join(", ") || "无"],
|
|
2060
|
+
["需要重跑", plan.impact?.affectedNodeIds?.join(", ") || "无"],
|
|
2061
|
+
["可以沿用", plan.impact?.importedNodeIds?.join(", ") || "无"],
|
|
2062
|
+
["计划哈希", plan.planHash],
|
|
2063
|
+
]),
|
|
2064
|
+
);
|
|
2065
|
+
if (revision.diff?.summary) {
|
|
2066
|
+
const diff = document.createElement("pre");
|
|
2067
|
+
diff.className = "prompt-restart-readonly";
|
|
2068
|
+
diff.textContent = revision.diff.summary;
|
|
2069
|
+
planHost.appendChild(diff);
|
|
2070
|
+
}
|
|
2071
|
+
if (plan.eligible !== true) {
|
|
2072
|
+
setStatus(
|
|
2073
|
+
`派生运行被阻断:${formatContextTransferReasonList(plan.blockedReasons) ?? "当前不满足继续运行的条件"}`,
|
|
2074
|
+
true,
|
|
2075
|
+
);
|
|
2076
|
+
return;
|
|
2077
|
+
}
|
|
2078
|
+
const execute = document.createElement("button");
|
|
2079
|
+
execute.type = "button";
|
|
2080
|
+
execute.className = "prompt-restart-submit dag-action-primary";
|
|
2081
|
+
execute.textContent = "确认并创建新运行";
|
|
2082
|
+
execute.dataset.testid = "artifact-revision-execute";
|
|
2083
|
+
planHost.appendChild(execute);
|
|
2084
|
+
execute.addEventListener("click", () => {
|
|
2085
|
+
void (async () => {
|
|
2086
|
+
execute.disabled = true;
|
|
2087
|
+
try {
|
|
2088
|
+
setStatus("正在执行受影响闭包;父 run 保持不可变…");
|
|
2089
|
+
const executeId = `artifact-execute-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
2090
|
+
const accepted = await postOperatorAction(
|
|
2091
|
+
"artifactRevisionExecute",
|
|
2092
|
+
{
|
|
2093
|
+
runId: dagRunId,
|
|
2094
|
+
artifactId: selected.artifactId,
|
|
2095
|
+
revisionId: revision.revisionId,
|
|
2096
|
+
planHash: plan.planHash,
|
|
2097
|
+
reason: reason.value.trim(),
|
|
2098
|
+
},
|
|
2099
|
+
executeId,
|
|
2100
|
+
);
|
|
2101
|
+
if (!accepted.ok || !accepted.body?.operationId) throw new Error(accepted.body?.error?.message ?? "派生运行未被接受");
|
|
2102
|
+
const operation = await pollOperationToTerminal(accepted.body.operationId, setStatus);
|
|
2103
|
+
const childRunId = operation?.result?.newRunId;
|
|
2104
|
+
if (childRunId) {
|
|
2105
|
+
const open = document.createElement("button");
|
|
2106
|
+
open.type = "button";
|
|
2107
|
+
open.className =
|
|
2108
|
+
"prompt-restart-submit dag-action-secondary";
|
|
2109
|
+
open.textContent =
|
|
2110
|
+
operation?.state === "succeeded"
|
|
2111
|
+
? "打开新运行"
|
|
2112
|
+
: "打开失败的新运行";
|
|
2113
|
+
open.addEventListener("click", () => {
|
|
2114
|
+
window.location.hash = `#/dag/${encodeURIComponent(childRunId)}`;
|
|
2115
|
+
});
|
|
2116
|
+
planHost.appendChild(open);
|
|
2117
|
+
}
|
|
2118
|
+
if (operation?.state !== "succeeded" || !operation.result?.newRunId) {
|
|
2119
|
+
throw new Error(operation?.errorMessage ?? operation?.result?.message ?? "派生运行失败");
|
|
2120
|
+
}
|
|
2121
|
+
setStatus(`派生运行完成:${operation.result.newRunId}`);
|
|
2122
|
+
} catch (error) {
|
|
2123
|
+
setStatus(transferErrorStatusText(error, "派生运行失败"), true);
|
|
2124
|
+
} finally {
|
|
2125
|
+
execute.disabled = false;
|
|
2126
|
+
}
|
|
2127
|
+
})();
|
|
2128
|
+
});
|
|
2129
|
+
setStatus("影响预览已冻结;确认后将重新规划并校验 plan hash。" );
|
|
2130
|
+
} catch (error) {
|
|
2131
|
+
setStatus(transferErrorStatusText(error, "影响预览失败"), true);
|
|
2132
|
+
} finally {
|
|
2133
|
+
preview.disabled = false;
|
|
2134
|
+
}
|
|
2135
|
+
})();
|
|
2136
|
+
});
|
|
2137
|
+
})();
|
|
2138
|
+
return section;
|
|
2139
|
+
}
|
|
2140
|
+
|
|
2141
|
+
function buildDagActionIntro(node) {
|
|
2142
|
+
const intro = el("header", "dag-action-page-intro");
|
|
2143
|
+
const copy = el("div", null);
|
|
2144
|
+
copy.append(
|
|
2145
|
+
el("span", "dag-action-eyebrow", "节点操作"),
|
|
2146
|
+
el("h4", null, "选择下一步"),
|
|
2147
|
+
el(
|
|
2148
|
+
"p",
|
|
2149
|
+
null,
|
|
2150
|
+
"重跑会创建新的运行;当前运行及其已经保存的事实始终保持不变。",
|
|
2151
|
+
),
|
|
2152
|
+
);
|
|
2153
|
+
const state = el("span", "dag-action-node-state", node.status ?? "unknown");
|
|
2154
|
+
intro.append(copy, state);
|
|
2155
|
+
return intro;
|
|
2156
|
+
}
|
|
2157
|
+
|
|
1364
2158
|
async function renderPromptRestart(content, dagRunId, node) {
|
|
1365
2159
|
const nodeId = node.nodeId;
|
|
1366
2160
|
clearNode(content);
|
|
1367
|
-
|
|
1368
|
-
const
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
2161
|
+
const intro = buildDagActionIntro(node);
|
|
2162
|
+
const revisionSection = buildArtifactRevisionSection(dagRunId, node);
|
|
2163
|
+
const { section: promptSection, body: promptBody } = createDagActionCard({
|
|
2164
|
+
eyebrow: "提示词重跑",
|
|
2165
|
+
title: "修改失败提示后重新执行",
|
|
2166
|
+
description: "仅失败节点可编辑提示词并从当前节点继续;其他节点只提供冻结提示查看。",
|
|
2167
|
+
icon: "ri-restart-line",
|
|
2168
|
+
className: "prompt-rerun-action-box",
|
|
2169
|
+
});
|
|
2170
|
+
promptSection.dataset.actionSection = "prompt-restart";
|
|
2171
|
+
promptBody.appendChild(
|
|
2172
|
+
buildDagActionAvailability({
|
|
2173
|
+
title: "正在读取节点提示",
|
|
2174
|
+
description: "检查冻结版本和历史失败候选。",
|
|
2175
|
+
}),
|
|
2176
|
+
);
|
|
2177
|
+
const exportSection = buildContextExportSection(dagRunId, node);
|
|
2178
|
+
const sections = new Map([
|
|
2179
|
+
["artifact-revision", revisionSection],
|
|
2180
|
+
["prompt-restart", promptSection],
|
|
2181
|
+
["context-export", exportSection],
|
|
2182
|
+
]);
|
|
2183
|
+
content.appendChild(intro);
|
|
2184
|
+
for (const sectionId of DAG_ACTION_SECTION_ORDER) {
|
|
2185
|
+
const section = sections.get(sectionId);
|
|
2186
|
+
if (section) content.appendChild(section);
|
|
2187
|
+
}
|
|
1372
2188
|
const input = await fetchDagNodeInput(dagRunId, nodeId);
|
|
1373
2189
|
if (
|
|
1374
2190
|
!content.isConnected ||
|
|
@@ -1378,20 +2194,27 @@ async function renderPromptRestart(content, dagRunId, node) {
|
|
|
1378
2194
|
) {
|
|
1379
2195
|
return;
|
|
1380
2196
|
}
|
|
1381
|
-
clearNode(
|
|
2197
|
+
clearNode(promptBody);
|
|
1382
2198
|
|
|
1383
2199
|
if (!input.ok || !input.body?.available) {
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
2200
|
+
promptBody.appendChild(
|
|
2201
|
+
buildDagActionAvailability({
|
|
2202
|
+
tone: "blocked",
|
|
2203
|
+
title: "无法读取这个节点的冻结提示",
|
|
2204
|
+
description: "当前不能安全预览或修改提示词。可以继续使用最下方的上下文导出。",
|
|
2205
|
+
}),
|
|
2206
|
+
);
|
|
1387
2207
|
return;
|
|
1388
2208
|
}
|
|
1389
2209
|
const frozenBasePrompt =
|
|
1390
2210
|
typeof input.body.prompt?.text === "string" ? input.body.prompt.text : "";
|
|
1391
2211
|
if (!frozenBasePrompt) {
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
2212
|
+
promptBody.appendChild(
|
|
2213
|
+
buildDagActionAvailability({
|
|
2214
|
+
title: "这个节点没有冻结提示",
|
|
2215
|
+
description: "因此不能从这里修改提示词重跑。",
|
|
2216
|
+
}),
|
|
2217
|
+
);
|
|
1395
2218
|
return;
|
|
1396
2219
|
}
|
|
1397
2220
|
const promptCandidateOptions = buildPromptRestartCandidateOptions(
|
|
@@ -1401,7 +2224,9 @@ async function renderPromptRestart(content, dagRunId, node) {
|
|
|
1401
2224
|
let selectedCandidate = promptCandidateOptions[0];
|
|
1402
2225
|
const historicalCandidatesMissing = Array.isArray(input.body.warnings)
|
|
1403
2226
|
? input.body.warnings.some((warning) =>
|
|
1404
|
-
String(warning).startsWith(
|
|
2227
|
+
String(warning).startsWith(
|
|
2228
|
+
"Failed prompt candidate missing for attempt ",
|
|
2229
|
+
),
|
|
1405
2230
|
)
|
|
1406
2231
|
: false;
|
|
1407
2232
|
|
|
@@ -1441,18 +2266,19 @@ async function renderPromptRestart(content, dagRunId, node) {
|
|
|
1441
2266
|
});
|
|
1442
2267
|
panel.appendChild(picker.field);
|
|
1443
2268
|
panel.appendChild(promptCard.card);
|
|
1444
|
-
|
|
2269
|
+
promptBody.appendChild(panel);
|
|
1445
2270
|
return;
|
|
1446
2271
|
}
|
|
1447
2272
|
|
|
1448
2273
|
if (input.body.prompt?.truncated === true) {
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
2274
|
+
promptBody.appendChild(
|
|
2275
|
+
buildDagActionAvailability({
|
|
2276
|
+
tone: "blocked",
|
|
2277
|
+
title: "冻结提示不完整,无法安全编辑",
|
|
2278
|
+
description:
|
|
2279
|
+
"为避免用截断文本覆盖完整提示,请改用 CLI:dag rerun --prompt-override。",
|
|
2280
|
+
}),
|
|
1453
2281
|
);
|
|
1454
|
-
error.setAttribute("role", "alert");
|
|
1455
|
-
content.appendChild(error);
|
|
1456
2282
|
return;
|
|
1457
2283
|
}
|
|
1458
2284
|
|
|
@@ -1579,7 +2405,7 @@ async function renderPromptRestart(content, dagRunId, node) {
|
|
|
1579
2405
|
row.appendChild(submit);
|
|
1580
2406
|
box.appendChild(row);
|
|
1581
2407
|
|
|
1582
|
-
|
|
2408
|
+
promptBody.appendChild(box);
|
|
1583
2409
|
|
|
1584
2410
|
const setStatus = (text, isError = false) => {
|
|
1585
2411
|
status.textContent = text ?? "";
|
|
@@ -1593,8 +2419,7 @@ async function renderPromptRestart(content, dagRunId, node) {
|
|
|
1593
2419
|
return;
|
|
1594
2420
|
}
|
|
1595
2421
|
const mode = modeSelect.value === "replace" ? "replace" : "append";
|
|
1596
|
-
const prompt =
|
|
1597
|
-
mode === "append" ? addendumArea.value : replaceArea.value;
|
|
2422
|
+
const prompt = mode === "append" ? addendumArea.value : replaceArea.value;
|
|
1598
2423
|
const reason = reasonArea.value.trim();
|
|
1599
2424
|
if (!prompt.trim()) {
|
|
1600
2425
|
setStatus(
|
|
@@ -1628,7 +2453,7 @@ async function renderPromptRestart(content, dagRunId, node) {
|
|
|
1628
2453
|
if (!planRes.ok || plan?.eligible !== true || !plan?.planHash) {
|
|
1629
2454
|
setStatus(
|
|
1630
2455
|
planRes.body?.error?.message ??
|
|
1631
|
-
plan?.blockedReasons
|
|
2456
|
+
formatContextTransferReasonList(plan?.blockedReasons) ??
|
|
1632
2457
|
"资格预检未通过。",
|
|
1633
2458
|
true,
|
|
1634
2459
|
);
|
|
@@ -1717,10 +2542,325 @@ async function pollOperationToTerminal(operationId, onProgress) {
|
|
|
1717
2542
|
return null;
|
|
1718
2543
|
}
|
|
1719
2544
|
|
|
1720
|
-
function
|
|
1721
|
-
|
|
1722
|
-
|
|
2545
|
+
function transparencyMetaList(rows) {
|
|
2546
|
+
const dl = el("dl", "node-input-summary node-transparency-summary");
|
|
2547
|
+
for (const [label, value] of rows) {
|
|
2548
|
+
if (value == null || value === "") continue;
|
|
2549
|
+
appendMetaRow(dl, label, value, null);
|
|
2550
|
+
}
|
|
2551
|
+
return dl;
|
|
2552
|
+
}
|
|
2553
|
+
|
|
2554
|
+
function renderNodeOverview(content, dagRunId, node) {
|
|
2555
|
+
const root = el("div", "node-transparency-root");
|
|
2556
|
+
root.appendChild(
|
|
2557
|
+
el("p", "node-input-semantics", "本页只展示当前运行已持久化的节点事实。"),
|
|
2558
|
+
);
|
|
2559
|
+
const summary = el("section", "node-input-section");
|
|
2560
|
+
summary.appendChild(el("h4", null, "节点事实"));
|
|
2561
|
+
summary.appendChild(
|
|
2562
|
+
transparencyMetaList([
|
|
2563
|
+
["run", dagRunId],
|
|
2564
|
+
["node", node.nodeId],
|
|
2565
|
+
["status", node.status ?? "unknown"],
|
|
2566
|
+
["executor", node.executor ?? "unknown"],
|
|
2567
|
+
["model", node.model ?? null],
|
|
2568
|
+
["rank", node.rank ?? null],
|
|
2569
|
+
["origin", node.originKind ?? "unknown"],
|
|
2570
|
+
["duration", node.durationMs != null ? formatMs(node.durationMs) : null],
|
|
2571
|
+
]),
|
|
2572
|
+
);
|
|
2573
|
+
root.appendChild(summary);
|
|
2574
|
+
const semantics = el("section", "node-input-section");
|
|
2575
|
+
semantics.appendChild(el("h4", null, "事实阶段"));
|
|
2576
|
+
semantics.appendChild(
|
|
2577
|
+
el(
|
|
2578
|
+
"p",
|
|
2579
|
+
"muted",
|
|
2580
|
+
"输入页是 frozen declaration;有效上下文页是 dispatch-time receipt;产物页是执行后 materialization。缺失阶段会明确标记,不用预览冒充事实。",
|
|
2581
|
+
),
|
|
2582
|
+
);
|
|
2583
|
+
root.appendChild(semantics);
|
|
2584
|
+
content.appendChild(root);
|
|
2585
|
+
restoreInspectorContentScroll(content, dagRunId, node.nodeId, "overview");
|
|
2586
|
+
}
|
|
2587
|
+
|
|
2588
|
+
function selectedAttemptNumber(dagRunId, node) {
|
|
2589
|
+
const key = effectiveExecutionKey(dagRunId, node);
|
|
2590
|
+
const match = /^attempt-(\d+)$/.exec(key ?? "");
|
|
2591
|
+
return match ? Number(match[1]) : undefined;
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2594
|
+
async function renderEffectiveContext(content, dagRunId, node) {
|
|
2595
|
+
content.appendChild(el("p", "muted", "正在加载有效上下文事实…"));
|
|
2596
|
+
const result = await fetchDagNodeContext(
|
|
2597
|
+
dagRunId,
|
|
2598
|
+
node.nodeId,
|
|
2599
|
+
selectedAttemptNumber(dagRunId, node),
|
|
2600
|
+
);
|
|
2601
|
+
if (
|
|
2602
|
+
!content.isConnected ||
|
|
2603
|
+
content.dataset.dagRunId !== String(dagRunId) ||
|
|
2604
|
+
content.dataset.dagNodeId !== String(node.nodeId) ||
|
|
2605
|
+
activeInspectorTab() !== "context"
|
|
1723
2606
|
)
|
|
2607
|
+
return;
|
|
2608
|
+
clearNode(content);
|
|
2609
|
+
if (!result.ok || !result.body) {
|
|
2610
|
+
const error = el(
|
|
2611
|
+
"p",
|
|
2612
|
+
"node-input-error",
|
|
2613
|
+
result.body?.error || "无法加载有效上下文事实。",
|
|
2614
|
+
);
|
|
2615
|
+
error.setAttribute("role", "alert");
|
|
2616
|
+
content.appendChild(error);
|
|
2617
|
+
return;
|
|
2618
|
+
}
|
|
2619
|
+
const data = result.body;
|
|
2620
|
+
const receipt = data.effectiveContext;
|
|
2621
|
+
const root = el("div", "node-transparency-root");
|
|
2622
|
+
root.appendChild(
|
|
2623
|
+
transparencyMetaList([
|
|
2624
|
+
["attempt", data.attempt],
|
|
2625
|
+
["declared", data.declared?.status],
|
|
2626
|
+
["dispatched", data.dispatched?.status],
|
|
2627
|
+
["node status", data.dispatched?.nodeStatus],
|
|
2628
|
+
]),
|
|
2629
|
+
);
|
|
2630
|
+
if (receipt?.status !== "captured") {
|
|
2631
|
+
const missing = el("div", "node-transparency-omission");
|
|
2632
|
+
missing.appendChild(
|
|
2633
|
+
el(
|
|
2634
|
+
"h4",
|
|
2635
|
+
null,
|
|
2636
|
+
receipt?.status === "invalid" ? "Receipt 不可用" : "未捕获",
|
|
2637
|
+
),
|
|
2638
|
+
);
|
|
2639
|
+
missing.appendChild(
|
|
2640
|
+
el("code", null, receipt?.reasonCode ?? "CONTEXT_RECEIPT_MISSING"),
|
|
2641
|
+
);
|
|
2642
|
+
const missingLabel = contextTransferReasonLabel(
|
|
2643
|
+
receipt?.reasonCode ?? "CONTEXT_RECEIPT_MISSING",
|
|
2644
|
+
);
|
|
2645
|
+
if (missingLabel) {
|
|
2646
|
+
missing.appendChild(el("p", "muted", missingLabel));
|
|
2647
|
+
}
|
|
2648
|
+
missing.appendChild(
|
|
2649
|
+
el(
|
|
2650
|
+
"p",
|
|
2651
|
+
"muted",
|
|
2652
|
+
"当前只有 frozen input / prompt 指纹;它们不是 Effective Context。历史 run 不做事后回填。",
|
|
2653
|
+
),
|
|
2654
|
+
);
|
|
2655
|
+
root.appendChild(missing);
|
|
2656
|
+
} else {
|
|
2657
|
+
const captured = el("section", "node-input-section");
|
|
2658
|
+
captured.appendChild(el("h4", null, "Captured receipt"));
|
|
2659
|
+
captured.appendChild(
|
|
2660
|
+
transparencyMetaList([
|
|
2661
|
+
["context digest", receipt.contextDigest],
|
|
2662
|
+
["captured at", receipt.capturedAt],
|
|
2663
|
+
["components", receipt.componentCount],
|
|
2664
|
+
["truncations", receipt.truncationCount],
|
|
2665
|
+
["executor", receipt.runtime?.executor],
|
|
2666
|
+
["model", receipt.runtime?.model],
|
|
2667
|
+
["skills", receipt.runtime?.skillCount],
|
|
2668
|
+
["extensions", receipt.runtime?.extensionCount],
|
|
2669
|
+
["tools", receipt.runtime?.toolCount],
|
|
2670
|
+
]),
|
|
2671
|
+
);
|
|
2672
|
+
appendCodeList(
|
|
2673
|
+
captured,
|
|
2674
|
+
"组件类型",
|
|
2675
|
+
Object.entries(receipt.componentKinds ?? {}).map(
|
|
2676
|
+
([kind, count]) => `${kind}: ${count}`,
|
|
2677
|
+
),
|
|
2678
|
+
);
|
|
2679
|
+
appendCodeList(
|
|
2680
|
+
captured,
|
|
2681
|
+
"包含状态",
|
|
2682
|
+
Object.entries(receipt.inclusionCounts ?? {}).map(
|
|
2683
|
+
([kind, count]) => `${kind}: ${count}`,
|
|
2684
|
+
),
|
|
2685
|
+
);
|
|
2686
|
+
root.appendChild(captured);
|
|
2687
|
+
}
|
|
2688
|
+
content.appendChild(root);
|
|
2689
|
+
restoreInspectorContentScroll(content, dagRunId, node.nodeId, "context");
|
|
2690
|
+
}
|
|
2691
|
+
|
|
2692
|
+
function artifactInventorySection(data, dagRunId, nodeId) {
|
|
2693
|
+
const root = el("section", "node-input-section node-artifact-inventory");
|
|
2694
|
+
root.appendChild(el("h4", null, "声明产物与消费关系"));
|
|
2695
|
+
if (!data?.binding?.available) {
|
|
2696
|
+
const warning = el("div", "node-transparency-omission");
|
|
2697
|
+
const bindingCode =
|
|
2698
|
+
data?.binding?.reasonCode ?? "ARTIFACT_BINDING_UNAVAILABLE";
|
|
2699
|
+
warning.appendChild(el("code", null, bindingCode));
|
|
2700
|
+
const bindingLabel = contextTransferReasonLabel(bindingCode);
|
|
2701
|
+
if (bindingLabel) {
|
|
2702
|
+
warning.appendChild(el("p", "muted", bindingLabel));
|
|
2703
|
+
}
|
|
2704
|
+
warning.appendChild(
|
|
2705
|
+
el(
|
|
2706
|
+
"p",
|
|
2707
|
+
"muted",
|
|
2708
|
+
"该历史 run 没有 frozen Artifact Binding;不会从 prompt、outputContract、路径或文件名推断 consumer。",
|
|
2709
|
+
),
|
|
2710
|
+
);
|
|
2711
|
+
root.appendChild(warning);
|
|
2712
|
+
return root;
|
|
2713
|
+
}
|
|
2714
|
+
root.appendChild(
|
|
2715
|
+
transparencyMetaList([
|
|
2716
|
+
["origin", data.origin?.kind ?? "unknown"],
|
|
2717
|
+
["parent", data.origin?.parentRunId ?? null],
|
|
2718
|
+
["graph sha256", data.binding.artifactGraphSha256],
|
|
2719
|
+
]),
|
|
2720
|
+
);
|
|
2721
|
+
for (const artifact of data.produces ?? []) {
|
|
2722
|
+
const card = el("div", "node-artifact-card");
|
|
2723
|
+
card.appendChild(el("h5", null, artifact.artifactId));
|
|
2724
|
+
card.appendChild(
|
|
2725
|
+
transparencyMetaList([
|
|
2726
|
+
["path", artifact.path],
|
|
2727
|
+
["media", artifact.mediaType],
|
|
2728
|
+
["schema", artifact.schemaId ?? null],
|
|
2729
|
+
["materialization", artifact.materialization?.status],
|
|
2730
|
+
["bytes", artifact.materialization?.bytes],
|
|
2731
|
+
["origin", artifact.origin?.kind],
|
|
2732
|
+
["revision", artifact.origin?.revisionId],
|
|
2733
|
+
["supersedes", artifact.origin?.supersedes],
|
|
2734
|
+
[
|
|
2735
|
+
"revision",
|
|
2736
|
+
artifact.revision?.eligible
|
|
2737
|
+
? "eligible"
|
|
2738
|
+
: (contextTransferReasonLabel(artifact.revision?.reasonCode) ??
|
|
2739
|
+
artifact.revision?.reasonCode ??
|
|
2740
|
+
"blocked"),
|
|
2741
|
+
],
|
|
2742
|
+
]),
|
|
2743
|
+
);
|
|
2744
|
+
appendCodeList(
|
|
2745
|
+
card,
|
|
2746
|
+
"Direct consumers",
|
|
2747
|
+
artifact.directConsumerNodeIds ?? [],
|
|
2748
|
+
);
|
|
2749
|
+
if (artifact.materialization?.status === "materialized") {
|
|
2750
|
+
card.appendChild(
|
|
2751
|
+
createArtifactContentPreview(dagRunId, nodeId, () => artifact, {
|
|
2752
|
+
testId: `${ARTIFACT_FILE_PREVIEW_SURFACES.produced}-${artifact.artifactId}`,
|
|
2753
|
+
}).element,
|
|
2754
|
+
);
|
|
2755
|
+
}
|
|
2756
|
+
root.appendChild(card);
|
|
2757
|
+
}
|
|
2758
|
+
for (const artifact of data.consumes ?? []) {
|
|
2759
|
+
const card = el("div", "node-artifact-card is-consumed");
|
|
2760
|
+
card.appendChild(el("h5", null, `消费 ${artifact.artifactId}`));
|
|
2761
|
+
card.appendChild(
|
|
2762
|
+
transparencyMetaList([
|
|
2763
|
+
["producer", artifact.producerNodeId],
|
|
2764
|
+
["path", artifact.path],
|
|
2765
|
+
["required", artifact.required ? "yes" : "no"],
|
|
2766
|
+
]),
|
|
2767
|
+
);
|
|
2768
|
+
card.appendChild(
|
|
2769
|
+
createArtifactContentPreview(dagRunId, nodeId, () => artifact, {
|
|
2770
|
+
testId: `${ARTIFACT_FILE_PREVIEW_SURFACES.consumed}-${artifact.artifactId}`,
|
|
2771
|
+
}).element,
|
|
2772
|
+
);
|
|
2773
|
+
root.appendChild(card);
|
|
2774
|
+
}
|
|
2775
|
+
if (
|
|
2776
|
+
(data.produces ?? []).length === 0 &&
|
|
2777
|
+
(data.consumes ?? []).length === 0
|
|
2778
|
+
) {
|
|
2779
|
+
root.appendChild(el("p", "empty", "该节点没有声明的产物绑定。"));
|
|
2780
|
+
}
|
|
2781
|
+
return root;
|
|
2782
|
+
}
|
|
2783
|
+
|
|
2784
|
+
async function prependArtifactInventory(content, dagRunId, node) {
|
|
2785
|
+
const result = await fetchDagNodeArtifacts(dagRunId, node.nodeId);
|
|
2786
|
+
if (
|
|
2787
|
+
!content.isConnected ||
|
|
2788
|
+
content.dataset.dagRunId !== String(dagRunId) ||
|
|
2789
|
+
content.dataset.dagNodeId !== String(node.nodeId) ||
|
|
2790
|
+
activeInspectorTab() !== "output"
|
|
2791
|
+
)
|
|
2792
|
+
return;
|
|
2793
|
+
const section =
|
|
2794
|
+
result.ok && result.body
|
|
2795
|
+
? artifactInventorySection(result.body, dagRunId, node.nodeId)
|
|
2796
|
+
: el(
|
|
2797
|
+
"p",
|
|
2798
|
+
"node-input-error node-artifact-inventory",
|
|
2799
|
+
result.body?.error || "无法加载产物绑定事实。",
|
|
2800
|
+
);
|
|
2801
|
+
content.insertBefore(section, content.firstChild);
|
|
2802
|
+
}
|
|
2803
|
+
|
|
2804
|
+
async function renderRunLineage(content, dagRunId, node) {
|
|
2805
|
+
content.appendChild(el("p", "muted", "正在加载来源与影响关系…"));
|
|
2806
|
+
const result = await fetchDagRunLineage(dagRunId);
|
|
2807
|
+
if (
|
|
2808
|
+
!content.isConnected ||
|
|
2809
|
+
content.dataset.dagRunId !== String(dagRunId) ||
|
|
2810
|
+
content.dataset.dagNodeId !== String(node.nodeId) ||
|
|
2811
|
+
activeInspectorTab() !== "lineage"
|
|
2812
|
+
)
|
|
2813
|
+
return;
|
|
2814
|
+
clearNode(content);
|
|
2815
|
+
if (!result.ok || !result.body) {
|
|
2816
|
+
content.appendChild(
|
|
2817
|
+
el("p", "node-input-error", result.body?.error || "无法加载来源与影响关系。"),
|
|
2818
|
+
);
|
|
2819
|
+
return;
|
|
2820
|
+
}
|
|
2821
|
+
const data = result.body;
|
|
2822
|
+
const root = el("div", "node-transparency-root");
|
|
2823
|
+
const intro = el("section", "node-lineage-intro");
|
|
2824
|
+
intro.append(
|
|
2825
|
+
el("h4", null, "这个节点从哪里来,会影响哪里"),
|
|
2826
|
+
el(
|
|
2827
|
+
"p",
|
|
2828
|
+
"muted",
|
|
2829
|
+
"这里展示派生运行继承、替换或重新执行了哪些节点。产出文件的直接使用者请到“产出”中查看。",
|
|
2830
|
+
),
|
|
2831
|
+
);
|
|
2832
|
+
root.appendChild(intro);
|
|
2833
|
+
root.appendChild(
|
|
2834
|
+
transparencyMetaList([
|
|
2835
|
+
["运行类型", data.kind === "root" ? "首次运行" : "派生运行"],
|
|
2836
|
+
["来源运行", data.parentRunId ?? null],
|
|
2837
|
+
["发起方式", data.trigger ?? null],
|
|
2838
|
+
["人工选择节点", data.operatorSelectedNodeId ?? null],
|
|
2839
|
+
["从此节点重新执行", data.effectiveFromNodeId ?? null],
|
|
2840
|
+
["重跑计划哈希", data.planHash ?? null],
|
|
2841
|
+
]),
|
|
2842
|
+
);
|
|
2843
|
+
appendCodeList(root, "沿用既有结果的节点", data.importedNodeIds ?? []);
|
|
2844
|
+
appendCodeList(root, "替换结果的节点", data.overriddenNodeIds ?? []);
|
|
2845
|
+
if (data.kind === "root")
|
|
2846
|
+
root.appendChild(
|
|
2847
|
+
el("p", "muted", "这是首次运行,没有来源运行。"),
|
|
2848
|
+
);
|
|
2849
|
+
content.appendChild(root);
|
|
2850
|
+
restoreInspectorContentScroll(content, dagRunId, node.nodeId, "lineage");
|
|
2851
|
+
}
|
|
2852
|
+
|
|
2853
|
+
function activeInspectorTab() {
|
|
2854
|
+
return [
|
|
2855
|
+
"overview",
|
|
2856
|
+
"input",
|
|
2857
|
+
"context",
|
|
2858
|
+
"timeline",
|
|
2859
|
+
"output",
|
|
2860
|
+
"lineage",
|
|
2861
|
+
"spec-evidence",
|
|
2862
|
+
"prompt-restart",
|
|
2863
|
+
].includes(uiState.dagInspectorTab)
|
|
1724
2864
|
? uiState.dagInspectorTab
|
|
1725
2865
|
: "output";
|
|
1726
2866
|
}
|
|
@@ -2182,8 +3322,9 @@ function repaintOutputTabBody(content, dagRunId, node) {
|
|
|
2182
3322
|
return;
|
|
2183
3323
|
}
|
|
2184
3324
|
// Remove everything after the attempt bar (or all if no bar).
|
|
3325
|
+
const inventory = content.querySelector(".node-artifact-inventory");
|
|
2185
3326
|
const bar = content.querySelector(".dag-inspector-attempt-bar");
|
|
2186
|
-
const keep =
|
|
3327
|
+
const keep = [inventory, bar].filter(Boolean);
|
|
2187
3328
|
while (content.firstChild) content.removeChild(content.firstChild);
|
|
2188
3329
|
for (const nodeEl of keep) content.appendChild(nodeEl);
|
|
2189
3330
|
|
|
@@ -2254,7 +3395,9 @@ function renderProcessCommandCard(command) {
|
|
|
2254
3395
|
command.failureCategory ? command.failureCategory : null,
|
|
2255
3396
|
].filter(Boolean);
|
|
2256
3397
|
if (meta.length > 0) {
|
|
2257
|
-
body.appendChild(
|
|
3398
|
+
body.appendChild(
|
|
3399
|
+
el("div", "process-timeline-section-label", meta.join(" · ")),
|
|
3400
|
+
);
|
|
2258
3401
|
}
|
|
2259
3402
|
if (command.stdout) {
|
|
2260
3403
|
body.appendChild(el("div", "process-timeline-section-label", "stdout"));
|
|
@@ -2283,7 +3426,11 @@ function renderProcessStepCard(step) {
|
|
|
2283
3426
|
details.open = true;
|
|
2284
3427
|
const summary = el("summary", "process-timeline-summary");
|
|
2285
3428
|
summary.appendChild(
|
|
2286
|
-
el(
|
|
3429
|
+
el(
|
|
3430
|
+
"span",
|
|
3431
|
+
"process-timeline-summary-title",
|
|
3432
|
+
step.label || step.id || "step",
|
|
3433
|
+
),
|
|
2287
3434
|
);
|
|
2288
3435
|
summary.appendChild(
|
|
2289
3436
|
el(
|
|
@@ -2296,7 +3443,11 @@ function renderProcessStepCard(step) {
|
|
|
2296
3443
|
const body = el("div", "process-timeline-card-body");
|
|
2297
3444
|
if (step.durationMs != null) {
|
|
2298
3445
|
body.appendChild(
|
|
2299
|
-
el(
|
|
3446
|
+
el(
|
|
3447
|
+
"div",
|
|
3448
|
+
"process-timeline-section-label",
|
|
3449
|
+
`耗时 ${formatMs(step.durationMs)}`,
|
|
3450
|
+
),
|
|
2300
3451
|
);
|
|
2301
3452
|
}
|
|
2302
3453
|
if (step.detail) appendProcessPre(body, step.detail);
|
|
@@ -2310,7 +3461,11 @@ function renderProcessStepCard(step) {
|
|
|
2310
3461
|
|
|
2311
3462
|
function renderNonPiProcess(content, body) {
|
|
2312
3463
|
content.classList.add("process-timeline-scroll");
|
|
2313
|
-
if (
|
|
3464
|
+
if (
|
|
3465
|
+
!body ||
|
|
3466
|
+
body.emptyReason ||
|
|
3467
|
+
((body.commands ?? []).length === 0 && (body.steps ?? []).length === 0)
|
|
3468
|
+
) {
|
|
2314
3469
|
content.appendChild(el("p", "empty", processEmptyMessage(body)));
|
|
2315
3470
|
return;
|
|
2316
3471
|
}
|
|
@@ -2324,7 +3479,8 @@ function renderNonPiProcess(content, body) {
|
|
|
2324
3479
|
);
|
|
2325
3480
|
}
|
|
2326
3481
|
const ul = el("ul", "process-timeline process-timeline-rich");
|
|
2327
|
-
const items =
|
|
3482
|
+
const items =
|
|
3483
|
+
(body.commands ?? []).length > 0 ? body.commands : (body.steps ?? []);
|
|
2328
3484
|
for (let index = 0; index < items.length; index += 1) {
|
|
2329
3485
|
const item = items[index];
|
|
2330
3486
|
const failed = item.ok === false;
|
|
@@ -2366,13 +3522,7 @@ function appendProcessExportButton(content, body, meta) {
|
|
|
2366
3522
|
const btn = el("button", "btn btn-secondary", "导出 HTML");
|
|
2367
3523
|
btn.type = "button";
|
|
2368
3524
|
btn.addEventListener("click", async () => {
|
|
2369
|
-
|
|
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
|
-
}
|
|
3525
|
+
const cssText = await fetchPageStylesheetText();
|
|
2376
3526
|
// 复用页面真实渲染 DOM,保证导出排版与页面一致。
|
|
2377
3527
|
const clone = content.cloneNode(true);
|
|
2378
3528
|
const bar = clone.querySelector(".process-timeline-export-bar");
|
|
@@ -2396,8 +3546,7 @@ export async function mergeNodeProcess(dagRunId, node) {
|
|
|
2396
3546
|
const result = await fetchDagNodeProcess(dagRunId, nodeId, key);
|
|
2397
3547
|
if (uiState.dagNodeProcessIdentity !== identity) return;
|
|
2398
3548
|
if (!result.ok || !result.body) {
|
|
2399
|
-
uiState.dagNodeProcessError =
|
|
2400
|
-
result.body?.error || UI_TEXT.processError;
|
|
3549
|
+
uiState.dagNodeProcessError = result.body?.error || UI_TEXT.processError;
|
|
2401
3550
|
uiState.dagNodeProcessData = {
|
|
2402
3551
|
schemaVersion: 1,
|
|
2403
3552
|
dagRunId,
|
|
@@ -2510,7 +3659,13 @@ function fillDagInspectorContent(content, dagRunId, node) {
|
|
|
2510
3659
|
node.nodeId,
|
|
2511
3660
|
activeTab,
|
|
2512
3661
|
);
|
|
2513
|
-
if (activeTab === "
|
|
3662
|
+
if (activeTab === "overview") {
|
|
3663
|
+
renderNodeOverview(content, dagRunId, node);
|
|
3664
|
+
} else if (activeTab === "context") {
|
|
3665
|
+
void renderEffectiveContext(content, dagRunId, node);
|
|
3666
|
+
} else if (activeTab === "lineage") {
|
|
3667
|
+
void renderRunLineage(content, dagRunId, node);
|
|
3668
|
+
} else if (activeTab === "spec-evidence") {
|
|
2514
3669
|
void renderSpecEvidence(content, dagRunId, node.nodeId);
|
|
2515
3670
|
} else if (activeTab === "prompt-restart") {
|
|
2516
3671
|
void renderPromptRestart(content, dagRunId, node);
|
|
@@ -2549,6 +3704,7 @@ function fillDagInspectorContent(content, dagRunId, node) {
|
|
|
2549
3704
|
}
|
|
2550
3705
|
// Single-execution nodes (!key) already painted preview via renderCachedOrPreviewBody.
|
|
2551
3706
|
restoreInspectorContentScroll(content, dagRunId, node.nodeId, "output");
|
|
3707
|
+
void prependArtifactInventory(content, dagRunId, node);
|
|
2552
3708
|
}
|
|
2553
3709
|
}
|
|
2554
3710
|
|
|
@@ -2586,18 +3742,14 @@ function buildDagInspectorHeader(dagRunId, node) {
|
|
|
2586
3742
|
controls.appendChild(close);
|
|
2587
3743
|
header.appendChild(controls);
|
|
2588
3744
|
|
|
2589
|
-
const tabDefs = [
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
];
|
|
2595
|
-
// Prompt-override rerun: the server injects the SCOPED inspect mutation
|
|
2596
|
-
// token into /inspect HTML by default (it only authorizes
|
|
2597
|
-
// dagRerunPlan/dagRerun). The emergency kill switch suppresses the token;
|
|
3745
|
+
const tabDefs = DAG_INSPECTOR_READ_ONLY_TABS.map((definition) => [
|
|
3746
|
+
...definition,
|
|
3747
|
+
]);
|
|
3748
|
+
// Export/rerun mutation surface: the server injects a SCOPED inspect token.
|
|
3749
|
+
// The emergency kill switch suppresses the token;
|
|
2598
3750
|
// no token → no tab → the inspector stays strictly read-only.
|
|
2599
3751
|
if (globalThis.__INSPECT_MUTATION_TOKEN__) {
|
|
2600
|
-
tabDefs.push([
|
|
3752
|
+
tabDefs.push([...DAG_INSPECTOR_MUTATION_TAB]);
|
|
2601
3753
|
}
|
|
2602
3754
|
const tabs = el("div", "dag-inspector-tabs");
|
|
2603
3755
|
tabs.setAttribute("role", "tablist");
|