@tea-agent/loop-agent 0.43.0-next.1 → 0.43.0-next.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +144 -9
- package/dist/application/dag/run-dag.js +5 -0
- package/dist/application/evaluation/budget.js +19 -1
- package/dist/application/task-lifecycle/advance.js +17 -4
- package/dist/application/task-lifecycle/observe.js +43 -29
- package/dist/application/task-lifecycle/plan-transitions.js +5 -4
- package/dist/build-stamp.json +3 -3
- package/dist/executors/dag-pi-executor.js +1237 -586
- package/dist/executors/pi-executor.js +22 -1
- package/dist/executors/pi-sdk-executor.js +140 -39
- package/dist/executors/shell-executor.js +8 -4
- package/dist/shared/frontend-execution-policy.js +23 -0
- package/dist/task/config-types.js +4 -0
- package/dist/task/source-prepare/ledger-reconciliation.js +2 -2
- package/dist/task/source-prepare/ledger-review.js +6 -9
- package/dist/task/source-prepare/semantic-intake.js +16 -26
- package/dist/task/source-prepare/source-fidelity-pi.js +28 -9
- package/dist/task/source-references.js +48 -23
- package/dist/worker/console/chat/assistant-content.js +23 -2
- package/dist/worker/console/chat/browser-policy.js +113 -0
- package/dist/worker/console/chat/browser-routes.js +140 -0
- package/dist/worker/console/chat/explore-tools.js +13 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/ask-user-question.js +44 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/explore-tools.js +202 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/goal-tools.js +79 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/operator-tools.js +86 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/todo-write.js +38 -0
- package/dist/worker/console/chat/pi-runtime/inspection-types.js +28 -0
- package/dist/worker/console/chat/pi-runtime/inspection.js +439 -0
- package/dist/worker/console/chat/pi-runtime/sdk-bindings.js +153 -0
- package/dist/worker/console/chat/pi-runtime/sdk-session.js +5 -0
- package/dist/worker/console/chat/pi-runtime.js +207 -1178
- package/dist/worker/console/chat/routes.js +208 -71
- package/dist/worker/console/chat/scm-routes.js +217 -0
- package/dist/worker/console/chat/scm-service.js +283 -0
- package/dist/worker/console/chat/scm-tools.js +111 -0
- package/dist/worker/console/chat/sdd-data-alignment.js +65 -9
- package/dist/worker/console/chat/session-catalog.js +32 -0
- package/dist/worker/console/chat/session-mode-view.js +49 -0
- package/dist/worker/console/chat/session-mode.js +219 -0
- package/dist/worker/console/chat/session-store.js +22 -6
- package/dist/worker/console/chat/shortcuts.js +6 -0
- package/dist/worker/console/chat/terminal-routes.js +216 -0
- package/dist/worker/console/chat/terminal-sessions.js +348 -0
- package/dist/worker/console/chat/terminal-tools.js +211 -0
- package/dist/worker/console/chat/tool-preview.js +11 -0
- package/dist/worker/console/chat/tools.js +2 -0
- package/dist/worker/console/server.js +2 -28
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-B0Q0nClf.js → abnfDiagram-N423BO3Z-CTSK0xbU.js} +1 -1
- package/dist/worker/console/static/assets/{arc-DCPjC19G.js → arc-_2MfctFP.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CP5n9jmG.js → architectureDiagram-T3A2C74G-DsDe2wTg.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-CmaWi_Wg.js → blockDiagram-VBNYF7ZC-Dtz_779U.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-Ct5tcMfZ.js → c4Diagram-5PPSVZJV-LaULi-gw.js} +1 -1
- package/dist/worker/console/static/assets/channel-CqS4yBWT.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-C0osm1Zf.js → chunk-2GRJ4B5K-DuHBqqX8.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-CDWviED7.js → chunk-2Q5K7J3B-BZgZs1dn.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-mtSk1-j7.js → chunk-5RXB4S5H-tzwmlIO_.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-D5J9PHC7.js → chunk-5VM5RSS4-CeYrLkSn.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-Begg4WAa.js → chunk-6Q2QTUOP-C92cgilm.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-sF1AEy7T.js → chunk-GF5L2VYU-XOXpGC0F.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BqbsWXZb.js → chunk-JWPE2WC7-CGHbJyCH.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BKJrPcJ6.js → chunk-KBJHAD2P-Bs2-LTzf.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CcwNMRho.js → chunk-RYQCIY6F-rN7up7E4.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-BEJi3iIs.js → chunk-XXDRQBXY-B_fWjKpR.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DVOxNOq0.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DVOxNOq0.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-DRLgTvVu.js → cose-bilkent-JH36ORCC-DE3SdJvB.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BoAVG3cs.js → cynefin-VYW2F7L2-B-4yuRAX.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Bzol06hR.js → cynefinDiagram-MW4NZA55-TiCPFuO8.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-UmliJM77.js → dagre-VZM6K2ZE-7crzLMzA.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-BWLcJkFo.js → diagram-7IWD3JNH-Cvr-x6Fd.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-CgApOm-O.js → diagram-B4RE2ZJO-BzcRHUjH.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-Bh3RgTLs.js → diagram-LBJQPF4R-BjkAXilh.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-Dsh-D5nE.js → diagram-Q27KOJAE-BkzyATK7.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-Dkbbpcpb.js → diagram-UB23O5K3-rZuTarYP.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-3EBaA3t3.js → ebnfDiagram-BXEA7PRR-CAYENaN1.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-Bq2Dc5ok.js → erDiagram-JOGREHBK-DCEam0Uq.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-De7y55ha.js → flowDiagram-UKHOOZJN-e_dH4wVO.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-fLiDbQRh.js → ganttDiagram-PKOTCBZU-CvZa2zOK.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Du617mp1.js → gitGraphDiagram-DS77QQ5N-CbT5TuKp.js} +1 -1
- package/dist/worker/console/static/assets/index-CUPeoE8W.js +468 -0
- package/dist/worker/console/static/assets/index-SAANIK8g.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-BwmlF-XP.js → infoDiagram-6WML65LV-BWzHQa_Z.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-zsGtS59u.js → ishikawaDiagram-WSZJBQD7-BTjlovtU.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DTTPTe3f.js → journeyDiagram-NVQOT4AX-DkvQoCc1.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BkXg9aP-.js → kanban-definition-27J2QSJJ-CHSzGi3W.js} +1 -1
- package/dist/worker/console/static/assets/{linear-7U2ue5IE.js → linear-DZMkTCWH.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-BUuGHmWO.js → mermaid.core-BIMhty7A.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-7q6fX0Sv.js → mindmap-definition-FAOFIHXS-Dx-tyMl3.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-CkQPJN55.js → pegDiagram-VL7TDLO6-C6G34rbY.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-CCal_tfX.js → pieDiagram-7S7Q4E2Y-CHK9XOfm.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-B7bpxyAZ.js → quadrantDiagram-CIZ2JOQS-CDARkI0w.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-CJmww7D-.js → railroadDiagram-AXF67PYL-DiuUe0ZQ.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-D6ldWJEv.js → requirementDiagram-LRYGKXZP-RE61UR56.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-VGh33I9e.js → sankeyDiagram-W5VNT64P-KDNLHMEG.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-CCkDrIjU.js → sequenceDiagram-SI44F4Z6-CeVgXnD9.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-NC8f5Otb.js → sizeCapture-X5ZJPWSS-CXwGyjjG.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-_92ezZdF.js → stateDiagram-OKZ733FA-BjTfozab.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BXoYsyr2.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-BdUCGtbP.js → swimlanes-SLNWSIFB-BaXOB8GT.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DfKliS4e.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CdD1H8ia.js → timeline-definition-Z64GVDOM-C3r2hqUU.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-Dkdk3oFo.js → vennDiagram-T6HMQDX7-MWKgl1S9.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CKu2uPPY.js → wardleyDiagram-T6FBY63Y-C9iFcZPk.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-ClqAnuG8.js → xychartDiagram-ELKLHX3M-DqnM24Jp.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/active-turn-tail.js +10 -0
- package/dist/worker/console/static-src/operator-chat/chat-link.js +94 -0
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +122 -10
- package/dist/worker/console/static-src/operator-chat/compaction-message.js +7 -0
- package/dist/worker/console/static-src/operator-chat/pending-user-message.js +27 -15
- package/dist/worker/console/static-src/operator-chat/process-label.js +41 -0
- package/dist/worker/console/static-src/operator-chat/turn-group-equality.js +13 -0
- package/dist/worker/console/static-src/operator-chat/turn-process-disclosure.js +10 -0
- package/dist/worker/console/static-src/operator-chat/use-searchable-hidden.js +51 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +44 -24
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +18 -2
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +41 -8
- package/dist/worker/console/static-src/operator-chat/useComposer.js +26 -11
- package/dist/worker/console/static-src/operator-chat/user-turn-anchor-equality.js +21 -0
- package/dist/worker/console/workspace-context.js +15 -1
- package/dist/worker/observe/node-transparency.js +81 -72
- package/dist/worker/observe/routes.js +20 -1
- package/dist/worker/observe/static/dag-inspector-humanize.js +3 -0
- package/dist/worker/observe/static/dom.js +20 -1
- package/dist/worker/observe/static/format-pool.d.ts +2 -0
- package/dist/worker/observe/static/format-pool.js +6 -0
- package/dist/worker/observe/static/format.js +7 -0
- package/dist/worker/observe/static/inspect-workspace.js +34 -7
- package/dist/worker/observe/static/inspector-submission.js +32 -0
- package/dist/worker/observe/static/kpi.js +1 -0
- package/dist/worker/observe/static/relations.js +2 -0
- package/dist/worker/observe/static/router.js +13 -0
- package/dist/worker/observe/static/run-processing.js +2 -0
- package/dist/worker/observe/static/shell-chrome.js +36 -3
- package/dist/worker/observe/static/state.js +35 -2
- package/dist/worker/observe/static/styles.css +263 -39
- package/dist/worker/observe/static/task-failure-labels.d.ts +4 -0
- package/dist/worker/observe/static/task-failure-labels.js +67 -0
- package/dist/worker/observe/static/task-history.js +12 -0
- package/dist/worker/observe/static/views/batch.js +6 -13
- package/dist/worker/observe/static/views/dag-graph.js +50 -3
- package/dist/worker/observe/static/views/dag-inspector.js +742 -268
- package/dist/worker/observe/static/views/dag-trajectory.js +3 -0
- package/dist/worker/observe/static/views/dag.d.ts +6 -0
- package/dist/worker/observe/static/views/dag.js +49 -11
- package/dist/worker/observe/static/views/dags.js +2 -0
- package/dist/worker/observe/static/views/dashboard.js +21 -12
- package/dist/worker/observe/static/views/failures.js +21 -11
- package/dist/worker/observe/static/views/feature.js +11 -29
- package/dist/worker/observe/static/views/pool.js +37 -28
- package/dist/worker/observe/static/views/run.js +48 -5
- package/dist/worker/observe/static/views/session-timeline.js +189 -240
- package/dist/worker/observe/static/views/task.js +81 -62
- package/dist/workflows/dag/budget-enforcement.js +53 -3
- package/dist/workflows/dag/frontend-capacity.js +9 -0
- package/dist/workflows/dag/frontend-closeout.js +1 -1
- package/dist/workflows/dag/frontend-design-policy.js +2 -1
- package/dist/workflows/dag/frontend-durable-tools.js +193 -0
- package/dist/workflows/dag/frontend-execution-groups.js +24 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +13 -0
- package/dist/workflows/dag/frontend-input-projection.js +76 -0
- package/dist/workflows/dag/frontend-plan-completeness.js +186 -0
- package/dist/workflows/dag/frontend-plan-recovery-policy.js +18 -0
- package/dist/workflows/dag/frontend-plan-render.js +10 -3
- package/dist/workflows/dag/frontend-prewrite-gate.js +3 -3
- package/dist/workflows/dag/frontend-recovery-controller.js +7 -7
- package/dist/workflows/dag/frontend-recovery-lineage.js +13 -0
- package/dist/workflows/dag/frontend-recovery-run.js +4 -0
- package/dist/workflows/dag/frontend-review-context.js +2 -1
- package/dist/workflows/dag/frontend-review-scopes.js +139 -0
- package/dist/workflows/dag/frontend-session-budget.js +249 -0
- package/dist/workflows/dag/frontend-shadow-dual-write.js +20 -2
- package/dist/workflows/dag/frontend-shape-facts.js +12 -2
- package/dist/workflows/dag/frontend-shape.js +30 -1
- package/dist/workflows/dag/frontend-test-execution-evidence.js +3 -2
- package/dist/workflows/dag/frontend-typed-event-store.js +11 -0
- package/dist/workflows/dag/frontend-verification-trace.js +1 -1
- package/dist/workflows/dag/init-hybrid.js +65 -167
- package/dist/workflows/dag/node-execution.js +38 -155
- package/dist/workflows/dag/prompt.js +4 -0
- package/dist/workflows/dag/rerun-plan.js +7 -1
- package/dist/workflows/dag/runner.js +26 -1
- package/dist/workflows/dag/types.js +6 -0
- package/docs/operations/README.md +1 -0
- package/docs/templates/frontend-design-contract.md +4 -4
- package/docs/templates/frontend-implementation-contract.schema.json +34 -2
- package/docs/templates/frontend-implementation-dag.json +14 -13
- package/package.json +6 -3
- package/skills/frontend-bounded-implement/SKILL.md +3 -3
- package/skills/frontend-bounded-implement/references/code-standards.md +3 -3
- package/skills/frontend-contract/SKILL.md +2 -1
- package/skills/frontend-contract/references/contract-protocol.md +22 -4
- package/skills/frontend-design-review/SKILL.md +22 -16
- package/skills/frontend-design-review/references/review-checklist.md +7 -7
- package/skills/frontend-plan/SKILL.md +21 -2
- package/skills/frontend-plan/references/decision-contract.md +42 -23
- package/skills/frontend-review/SKILL.md +22 -29
- package/skills/frontend-review/references/review-findings.md +24 -43
- package/skills/frontend-scout/references/scout-evidence.md +4 -0
- package/dist/worker/console/static/assets/channel-DAS07MdS.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Cwk-5jiW.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Cwk-5jiW.js +0 -1
- package/dist/worker/console/static/assets/index-C0O48S_P.js +0 -449
- package/dist/worker/console/static/assets/index-CzKf4U8P.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DyJg5gzW.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-B1cw_0Uy.js +0 -8
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { readFileBoundarySafe } from "../../shared/path-safety.js";
|
|
1
2
|
import { createHash } from "node:crypto";
|
|
2
|
-
import {
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
3
4
|
import path from "node:path";
|
|
4
|
-
import { redactSecrets, truncateUtf8Preview } from "../../shared/preview.js";
|
|
5
|
-
import { effectiveNodeContextV1Schema, } from "../../shared/context-transfer.js";
|
|
5
|
+
import { redactSecrets, truncateUtf8Preview, redactSensitivePathsWithFindings } from "../../shared/preview.js";
|
|
6
|
+
import { effectiveNodeContextV1Schema, computeContextDigest, } from "../../shared/context-transfer.js";
|
|
6
7
|
import { buildArtifactBindingGraph, } from "../../workflows/dag/artifact-bindings.js";
|
|
7
8
|
import { dagSpecSchema } from "../../workflows/dag/types.js";
|
|
8
9
|
import { isSafeObservabilityIdentifier } from "../observability/event-store.js";
|
|
@@ -133,8 +134,7 @@ export async function loadDagNodeContextSummary(repoRoot, dagRunId, nodeId, atte
|
|
|
133
134
|
};
|
|
134
135
|
}
|
|
135
136
|
else {
|
|
136
|
-
const
|
|
137
|
-
const parsedReceipt = effectiveNodeContextV1Schema.safeParse(rawReceipt);
|
|
137
|
+
const parsedReceipt = await readValidatedContextReceipt(loaded.facts.runDir, receiptResolved.result.absolutePath);
|
|
138
138
|
if (!parsedReceipt.success ||
|
|
139
139
|
parsedReceipt.data.identity.runId !== dagRunId ||
|
|
140
140
|
parsedReceipt.data.identity.nodeId !== nodeId ||
|
|
@@ -160,6 +160,14 @@ export async function loadDagNodeContextSummary(repoRoot, dagRunId, nodeId, atte
|
|
|
160
160
|
contextDigest: receipt.contextDigest,
|
|
161
161
|
capturedAt: receipt.identity.capturedAt,
|
|
162
162
|
componentCount: receipt.orderedComponents.length,
|
|
163
|
+
components: receipt.orderedComponents.map((component) => ({
|
|
164
|
+
index: component.index, kind: component.kind,
|
|
165
|
+
logicalName: redactSensitivePathsWithFindings(redactSecrets(component.logicalName)).redacted,
|
|
166
|
+
sourceIdentity: redactSensitivePathsWithFindings(redactSecrets(component.source.identity)).redacted,
|
|
167
|
+
inclusion: component.inclusion, bytes: component.bytes, mediaType: component.mediaType,
|
|
168
|
+
previewAvailable: Boolean(component.materializedPath && !["reference-only", "omitted"].includes(component.inclusion)),
|
|
169
|
+
...(component.omissionReason ? { omissionReason: redactSecrets(component.omissionReason) } : {}),
|
|
170
|
+
})),
|
|
163
171
|
componentKinds,
|
|
164
172
|
inclusionCounts,
|
|
165
173
|
truncationCount: receipt.promptAssembly.truncations.length,
|
|
@@ -353,11 +361,7 @@ function isTextPreviewMediaType(mediaType) {
|
|
|
353
361
|
"application/sql",
|
|
354
362
|
].includes(normalized) || normalized.endsWith("+json") || normalized.endsWith("+xml");
|
|
355
363
|
}
|
|
356
|
-
function
|
|
357
|
-
const normalizedRoot = root.endsWith(path.sep) ? root : `${root}${path.sep}`;
|
|
358
|
-
return candidate.startsWith(normalizedRoot);
|
|
359
|
-
}
|
|
360
|
-
export async function loadDagNodeArtifactPreview(repoRoot, dagRunId, nodeId, artifactId) {
|
|
364
|
+
async function loadVerifiedArtifactBytes(repoRoot, dagRunId, nodeId, artifactId) {
|
|
361
365
|
if (!isSafeObservabilityIdentifier(nodeId) || !isSafeObservabilityIdentifier(artifactId)) {
|
|
362
366
|
return {
|
|
363
367
|
status: 400,
|
|
@@ -410,21 +414,7 @@ export async function loadDagNodeArtifactPreview(repoRoot, dagRunId, nodeId, art
|
|
|
410
414
|
if (!resolved.ok)
|
|
411
415
|
return failureForResolution(resolved.reason);
|
|
412
416
|
try {
|
|
413
|
-
const
|
|
414
|
-
realpath(resolved.result.runDir),
|
|
415
|
-
realpath(resolved.result.absolutePath),
|
|
416
|
-
lstat(resolved.result.absolutePath),
|
|
417
|
-
]);
|
|
418
|
-
if (!isInsideDirectory(runRoot, artifactRealPath) ||
|
|
419
|
-
!fileStat.isFile() ||
|
|
420
|
-
fileStat.isSymbolicLink() ||
|
|
421
|
-
fileStat.nlink !== 1) {
|
|
422
|
-
return {
|
|
423
|
-
status: 422,
|
|
424
|
-
body: { error: "Artifact is not a safe regular file", reason: "unsafe-path" },
|
|
425
|
-
};
|
|
426
|
-
}
|
|
427
|
-
const bytes = await readFile(artifactRealPath);
|
|
417
|
+
const bytes = await readFileBoundarySafe({ boundaryRoot: resolved.result.runDir, targetPath: resolved.result.absolutePath, label: "recorded artifact" });
|
|
428
418
|
const actualSha256 = createHash("sha256").update(bytes).digest("hex");
|
|
429
419
|
if (actualSha256 !== fact.sha256 ||
|
|
430
420
|
(typeof fact.bytes === "number" && fact.bytes !== bytes.byteLength)) {
|
|
@@ -436,57 +426,76 @@ export async function loadDagNodeArtifactPreview(repoRoot, dagRunId, nodeId, art
|
|
|
436
426
|
},
|
|
437
427
|
};
|
|
438
428
|
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
body: {
|
|
443
|
-
schemaVersion: NODE_TRANSPARENCY_SCHEMA_VERSION,
|
|
444
|
-
dagRunId,
|
|
445
|
-
nodeId,
|
|
446
|
-
producerNodeId: artifact.producerNodeId,
|
|
447
|
-
artifactId,
|
|
448
|
-
path: artifact.path,
|
|
449
|
-
mediaType: artifact.mediaType,
|
|
450
|
-
kind: artifact.kind,
|
|
451
|
-
sha256: actualSha256,
|
|
452
|
-
sourceBytes: bytes.byteLength,
|
|
453
|
-
maxBytes: DAG_ARTIFACT_PREVIEW_MAX_BYTES,
|
|
454
|
-
previewKind: "unsupported",
|
|
455
|
-
content: null,
|
|
456
|
-
truncated: false,
|
|
457
|
-
redacted: false,
|
|
458
|
-
},
|
|
459
|
-
};
|
|
460
|
-
}
|
|
461
|
-
const raw = bytes.toString("utf8");
|
|
462
|
-
const redacted = redactSecrets(raw);
|
|
463
|
-
const content = truncateUtf8Preview(redacted, DAG_ARTIFACT_PREVIEW_MAX_BYTES);
|
|
429
|
+
return { status: 200, artifact, bytes, sha256: actualSha256 };
|
|
430
|
+
}
|
|
431
|
+
catch (error) {
|
|
464
432
|
return {
|
|
465
|
-
status:
|
|
466
|
-
body: {
|
|
467
|
-
schemaVersion: NODE_TRANSPARENCY_SCHEMA_VERSION,
|
|
468
|
-
dagRunId,
|
|
469
|
-
nodeId,
|
|
470
|
-
producerNodeId: artifact.producerNodeId,
|
|
471
|
-
artifactId,
|
|
472
|
-
path: artifact.path,
|
|
473
|
-
mediaType: artifact.mediaType,
|
|
474
|
-
kind: artifact.kind,
|
|
475
|
-
sha256: actualSha256,
|
|
476
|
-
sourceBytes: bytes.byteLength,
|
|
477
|
-
maxBytes: DAG_ARTIFACT_PREVIEW_MAX_BYTES,
|
|
478
|
-
previewKind: "text",
|
|
479
|
-
content,
|
|
480
|
-
truncated: content !== redacted,
|
|
481
|
-
redacted: redacted !== raw,
|
|
482
|
-
},
|
|
433
|
+
status: error.code === "ENOENT" ? 404 : 422,
|
|
434
|
+
body: { error: "Artifact file is unavailable or unsafe", reason: error.code === "ENOENT" ? "not-found" : "unsafe-path" },
|
|
483
435
|
};
|
|
484
436
|
}
|
|
437
|
+
}
|
|
438
|
+
export async function loadDagNodeArtifactPreview(repoRoot, dagRunId, nodeId, artifactId) {
|
|
439
|
+
const verified = await loadVerifiedArtifactBytes(repoRoot, dagRunId, nodeId, artifactId);
|
|
440
|
+
if (verified.status !== 200)
|
|
441
|
+
return verified;
|
|
442
|
+
const { artifact, bytes, sha256 } = verified;
|
|
443
|
+
const supported = isTextPreviewMediaType(artifact.mediaType);
|
|
444
|
+
const raw = supported ? bytes.toString("utf8") : "";
|
|
445
|
+
const safe = redactSecrets(raw);
|
|
446
|
+
const content = supported ? truncateUtf8Preview(safe, DAG_ARTIFACT_PREVIEW_MAX_BYTES) : null;
|
|
447
|
+
return { status: 200, body: { schemaVersion: 1, dagRunId, nodeId, producerNodeId: artifact.producerNodeId, artifactId, path: artifact.path, mediaType: artifact.mediaType, kind: artifact.kind, sha256, sourceBytes: bytes.byteLength, maxBytes: DAG_ARTIFACT_PREVIEW_MAX_BYTES, previewKind: supported ? "text" : "unsupported", content, truncated: supported && content !== safe, redacted: safe !== raw } };
|
|
448
|
+
}
|
|
449
|
+
/** Full text uses the same binding and safety checks as the bounded preview. */
|
|
450
|
+
export async function loadDagNodeArtifactDownload(repoRoot, dagRunId, nodeId, artifactId) {
|
|
451
|
+
const verified = await loadVerifiedArtifactBytes(repoRoot, dagRunId, nodeId, artifactId);
|
|
452
|
+
if (verified.status !== 200)
|
|
453
|
+
return verified;
|
|
454
|
+
if (!isTextPreviewMediaType(verified.artifact.mediaType))
|
|
455
|
+
return { status: 422, body: { error: "Only text artifacts support redacted download", reason: "unsupported-media" } };
|
|
456
|
+
return { status: 200, download: { filename: `${artifactId}.redacted.txt`, bytes: Buffer.from(redactSecrets(verified.bytes.toString("utf8")), "utf8") } };
|
|
457
|
+
}
|
|
458
|
+
async function readValidatedContextReceipt(boundaryRoot, targetPath) {
|
|
459
|
+
try {
|
|
460
|
+
const bytes = await readFileBoundarySafe({ boundaryRoot, targetPath, label: "recorded context receipt" });
|
|
461
|
+
const parsed = effectiveNodeContextV1Schema.safeParse(JSON.parse(bytes.toString("utf8")));
|
|
462
|
+
if (parsed.success) {
|
|
463
|
+
const { contextDigest, ...context } = parsed.data;
|
|
464
|
+
if (computeContextDigest(context) === contextDigest)
|
|
465
|
+
return parsed;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
catch { /* Missing, changed and unsafe receipts are unavailable. */ }
|
|
469
|
+
return effectiveNodeContextV1Schema.safeParse(null);
|
|
470
|
+
}
|
|
471
|
+
export async function loadDagNodeContextComponent(repoRoot, dagRunId, nodeId, attempt, componentIndex) {
|
|
472
|
+
if (!isSafeObservabilityIdentifier(nodeId) || !Number.isSafeInteger(attempt) || attempt < 1 || !Number.isSafeInteger(componentIndex) || componentIndex < 0)
|
|
473
|
+
return { status: 400, body: { error: "Invalid context selection", reason: "invalid-id" } };
|
|
474
|
+
const loaded = await loadRunFacts(repoRoot, dagRunId);
|
|
475
|
+
if (!loaded.ok)
|
|
476
|
+
return loaded.failure;
|
|
477
|
+
if (!loaded.facts.spec.tasks.some((task) => task.id === nodeId))
|
|
478
|
+
return { status: 404, body: { error: "DAG node not found", reason: "not-found" } };
|
|
479
|
+
const resolved = resolveDagRunArtifact(repoRoot, dagRunId, [nodeId, "context-receipts", `attempt-${attempt}.json`]);
|
|
480
|
+
if (!resolved.ok)
|
|
481
|
+
return failureForResolution(resolved.reason);
|
|
482
|
+
try {
|
|
483
|
+
const parsed = await readValidatedContextReceipt(loaded.facts.runDir, resolved.result.absolutePath);
|
|
484
|
+
if (!parsed.success || parsed.data.identity.runId !== dagRunId || parsed.data.identity.nodeId !== nodeId || parsed.data.identity.attempt !== attempt)
|
|
485
|
+
return { status: 422, body: { error: "Invalid recorded context", reason: "CONTEXT_COMPONENT_UNAVAILABLE" } };
|
|
486
|
+
const component = parsed.data.orderedComponents.find((entry) => entry.index === componentIndex);
|
|
487
|
+
if (!component?.materializedPath || ["reference-only", "omitted"].includes(component.inclusion))
|
|
488
|
+
return { status: 404, body: { error: "No recorded body for this component", reason: "CONTEXT_COMPONENT_UNAVAILABLE" } };
|
|
489
|
+
const bytes = await readFileBoundarySafe({ boundaryRoot: loaded.facts.runDir, targetPath: path.resolve(loaded.facts.runDir, ...component.materializedPath.split("/")), label: "recorded context component" });
|
|
490
|
+
if (bytes.byteLength !== component.bytes || createHash("sha256").update(bytes).digest("hex") !== component.sha256)
|
|
491
|
+
return { status: 422, body: { error: "Recorded context component has changed", reason: "CONTEXT_COMPONENT_UNAVAILABLE" } };
|
|
492
|
+
const raw = bytes.toString("utf8");
|
|
493
|
+
const safe = redactSensitivePathsWithFindings(redactSecrets(raw)).redacted;
|
|
494
|
+
const content = truncateUtf8Preview(safe, DAG_ARTIFACT_PREVIEW_MAX_BYTES);
|
|
495
|
+
return { status: 200, body: { dagRunId, nodeId, attempt, componentIndex, sourceBytes: bytes.byteLength, content, redacted: safe !== raw, truncated: content !== safe, maxBytes: DAG_ARTIFACT_PREVIEW_MAX_BYTES } };
|
|
496
|
+
}
|
|
485
497
|
catch {
|
|
486
|
-
return {
|
|
487
|
-
status: 404,
|
|
488
|
-
body: { error: "Artifact file is unavailable", reason: "not-found" },
|
|
489
|
-
};
|
|
498
|
+
return { status: 422, body: { error: "Recorded context is unavailable or unsafe", reason: "CONTEXT_COMPONENT_UNAVAILABLE" } };
|
|
490
499
|
}
|
|
491
500
|
}
|
|
492
501
|
function stringField(value, key) {
|
|
@@ -21,7 +21,7 @@ import { extractSpecEvidence, extractSpecReadContent, } from "./spec-evidence.js
|
|
|
21
21
|
import { buildDagNodeInput } from "./node-input.js";
|
|
22
22
|
import { loadDagNodeExecutionOutput } from "./dag-node-execution-output.js";
|
|
23
23
|
import { loadDagNodeProcess } from "./node-process.js";
|
|
24
|
-
import { loadDagNodeArtifactInventory, loadDagNodeArtifactPreview, loadDagNodeContextSummary, loadDagRunLineage, } from "./node-transparency.js";
|
|
24
|
+
import { loadDagNodeArtifactInventory, loadDagNodeArtifactPreview, loadDagNodeArtifactDownload, loadDagNodeContextComponent, loadDagNodeContextSummary, loadDagRunLineage, } from "./node-transparency.js";
|
|
25
25
|
import { buildObserveHealthV1 } from "./health.js";
|
|
26
26
|
import { buildNightJobDetail, buildNightJobsDoctor, buildNightJobsList, buildNightJobsMorning, } from "./night-jobs.js";
|
|
27
27
|
const ARTIFACT_PREVIEW_MAX_BYTES = 64 * 1024;
|
|
@@ -125,6 +125,8 @@ export const ROUTES = [
|
|
|
125
125
|
pattern: /^\/api\/dag-runs\/([^/]+)\/nodes\/([^/]+)\/artifacts$/,
|
|
126
126
|
handler: handleDagNodeArtifacts,
|
|
127
127
|
},
|
|
128
|
+
{ method: "GET", pattern: /^\/api\/dag-runs\/([^/]+)\/nodes\/([^/]+)\/context\/component$/, handler: handleDagNodeContextComponent },
|
|
129
|
+
{ method: "GET", pattern: /^\/api\/dag-runs\/([^/]+)\/nodes\/([^/]+)\/artifacts\/download$/, handler: handleDagNodeArtifactDownload },
|
|
128
130
|
{
|
|
129
131
|
method: "GET",
|
|
130
132
|
pattern: /^\/api\/dag-runs\/([^/]+)\/nodes\/([^/]+)\/execution-output$/,
|
|
@@ -795,6 +797,23 @@ async function handleDagNodeContext(_req, res, match, ctx) {
|
|
|
795
797
|
const result = await loadDagNodeContextSummary(ctx.repoRoot, match.params.id, match.params.sub, attempt);
|
|
796
798
|
sendJson(res, result.status, result.body);
|
|
797
799
|
}
|
|
800
|
+
async function handleDagNodeContextComponent(_req, res, match, ctx) {
|
|
801
|
+
if (!/^[1-9]\d*$/.test(match.query.get("attempt") ?? "") || !/^(0|[1-9]\d*)$/.test(match.query.get("componentIndex") ?? "") || match.query.has("path") || match.query.has("artifactPath")) {
|
|
802
|
+
sendJson(res, 400, { error: "Select a recorded attempt and component index" });
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
const result = await loadDagNodeContextComponent(ctx.repoRoot, match.params.id, match.params.sub, Number(match.query.get("attempt")), Number(match.query.get("componentIndex")));
|
|
806
|
+
sendJson(res, result.status, result.body);
|
|
807
|
+
}
|
|
808
|
+
async function handleDagNodeArtifactDownload(_req, res, match, ctx) {
|
|
809
|
+
const result = await loadDagNodeArtifactDownload(ctx.repoRoot, match.params.id, match.params.sub, match.query.get("artifactId") ?? "");
|
|
810
|
+
if (result.status !== 200) {
|
|
811
|
+
sendJson(res, result.status, result.body);
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8", "Content-Disposition": `attachment; filename="${result.download.filename}"`, "Content-Length": result.download.bytes.byteLength, "Cache-Control": "no-store", "X-Content-Type-Options": "nosniff" });
|
|
815
|
+
res.end(result.download.bytes);
|
|
816
|
+
}
|
|
798
817
|
async function handleDagNodeArtifacts(_req, res, match, ctx) {
|
|
799
818
|
const result = await loadDagNodeArtifactInventory(ctx.repoRoot, match.params.id, match.params.sub);
|
|
800
819
|
sendJson(res, result.status, result.body);
|
|
@@ -278,6 +278,9 @@ const COMMON_VALUE_LABELS = Object.freeze({
|
|
|
278
278
|
true: "是",
|
|
279
279
|
false: "否",
|
|
280
280
|
"system-prompt": "系统说明",
|
|
281
|
+
"dag-pi-system-prompt": "系统说明",
|
|
282
|
+
"dag-pi-user-message": "任务消息",
|
|
283
|
+
"managed-task-contract": "任务约定",
|
|
281
284
|
"developer-prompt": "开发约束",
|
|
282
285
|
"user-prompt": "用户要求",
|
|
283
286
|
"task-contract": "任务约定",
|
|
@@ -1,5 +1,24 @@
|
|
|
1
|
+
import { isRowNavigationClick } from "./router.js";
|
|
1
2
|
/** DOM helpers (Observe UI R5). */
|
|
2
3
|
|
|
4
|
+
/** Compact action with a visible icon and an accessible, focusable tooltip. */
|
|
5
|
+
export function iconButton(label, icon, className = "") {
|
|
6
|
+
const button = el("button", `observe-icon-button ${className}`.trim());
|
|
7
|
+
button.type = "button";
|
|
8
|
+
setIconButton(button, label, icon);
|
|
9
|
+
return button;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function setIconButton(button, label, icon) {
|
|
13
|
+
button.setAttribute("aria-label", label);
|
|
14
|
+
button.setAttribute("data-tooltip", label);
|
|
15
|
+
button.setAttribute("title", label);
|
|
16
|
+
while (button.firstChild) button.removeChild(button.firstChild);
|
|
17
|
+
const glyph = el("i", icon);
|
|
18
|
+
glyph.setAttribute("aria-hidden", "true");
|
|
19
|
+
button.appendChild(glyph);
|
|
20
|
+
}
|
|
21
|
+
|
|
3
22
|
/**
|
|
4
23
|
* Stable key → node maps, kept per (parent, scope) so reconcileKeyed never
|
|
5
24
|
* depends on dataset support. A WeakMap keyed by parent holds a map of
|
|
@@ -205,7 +224,7 @@ export function buildTable(headers, rows) {
|
|
|
205
224
|
if (row.className) tr.className = row.className;
|
|
206
225
|
if (row.onClick) {
|
|
207
226
|
tr.classList.add("clickable");
|
|
208
|
-
tr.addEventListener("click", row.onClick);
|
|
227
|
+
tr.addEventListener("click", (event) => { if (isRowNavigationClick(event)) row.onClick(event); });
|
|
209
228
|
}
|
|
210
229
|
for (const cell of row.cells) {
|
|
211
230
|
const td = document.createElement("td");
|
|
@@ -151,3 +151,9 @@ export function collectPlannerReadyTasks(features) {
|
|
|
151
151
|
export function taskIdentityKey(featureId, taskId) {
|
|
152
152
|
return `${featureId ?? ""}\0${taskId ?? ""}`;
|
|
153
153
|
}
|
|
154
|
+
|
|
155
|
+
export function matchesPoolTaskFilter(task, filters = {}) {
|
|
156
|
+
if (!matchesPoolStatusFilter(task.status, filters.status)) return false;
|
|
157
|
+
const query = String(filters.q ?? "").trim().toLocaleLowerCase();
|
|
158
|
+
return !query || [task.title, task.taskId, task.featureId, task.workerRunId, task.failureCategory, task.recommendedFollowUp].join(" ").toLocaleLowerCase().includes(query);
|
|
159
|
+
}
|
|
@@ -405,3 +405,10 @@ export function formatLedgerSummary(summary) {
|
|
|
405
405
|
}
|
|
406
406
|
return lines.join("\n");
|
|
407
407
|
}
|
|
408
|
+
|
|
409
|
+
/** A batch ending does not imply all child tasks passed. */
|
|
410
|
+
export function batchStatusBadge(batch) {
|
|
411
|
+
const status = String(batch?.status ?? "").toLowerCase();
|
|
412
|
+
if (["succeeded", "completed"].includes(status)) return el("span", "badge", "已结束");
|
|
413
|
+
return badge(batch?.status);
|
|
414
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { inspectRouteStates } from "./state.js";
|
|
1
2
|
/**
|
|
2
3
|
* Hosted Inspect workspace adapter.
|
|
3
4
|
*
|
|
@@ -44,6 +45,9 @@ import {
|
|
|
44
45
|
|
|
45
46
|
let instance = null;
|
|
46
47
|
let lastInspectHash = "";
|
|
48
|
+
let renderedHash = "";
|
|
49
|
+
let renderedScope = null;
|
|
50
|
+
let routeToken = 0;
|
|
47
51
|
let listenersBound = false;
|
|
48
52
|
|
|
49
53
|
export function getInspectWorkspaceInstance() {
|
|
@@ -129,7 +133,7 @@ function startView(routeValue) {
|
|
|
129
133
|
if (routeValue.view === "dashboard") {
|
|
130
134
|
startDashboardPolling(routeValue.scrollTo);
|
|
131
135
|
} else if (routeValue.view === "dags") {
|
|
132
|
-
|
|
136
|
+
return startDagsView();
|
|
133
137
|
} else if (routeValue.view === "batch") {
|
|
134
138
|
void renderBatch(routeValue.batchRunId);
|
|
135
139
|
} else if (routeValue.view === "run") {
|
|
@@ -137,9 +141,9 @@ function startView(routeValue) {
|
|
|
137
141
|
} else if (routeValue.view === "dag") {
|
|
138
142
|
startDagPolling(routeValue.dagRunId);
|
|
139
143
|
} else if (routeValue.view === "failures") {
|
|
140
|
-
|
|
144
|
+
return renderFailures();
|
|
141
145
|
} else if (routeValue.view === "pool") {
|
|
142
|
-
startPoolView();
|
|
146
|
+
return startPoolView();
|
|
143
147
|
} else if (routeValue.view === "night") {
|
|
144
148
|
void renderNightJobs(routeValue.scheduleId);
|
|
145
149
|
} else if (routeValue.view === "task") {
|
|
@@ -150,11 +154,34 @@ function startView(routeValue) {
|
|
|
150
154
|
}
|
|
151
155
|
|
|
152
156
|
export function route(hashValue) {
|
|
157
|
+
const scope = workspaceScope();
|
|
158
|
+
const scrollRoot = instance?.root ?? document.scrollingElement;
|
|
159
|
+
const destination = hashValue || (typeof location !== "undefined" ? location.hash : "#/inspect");
|
|
160
|
+
if (renderedHash && renderedScope === scope) {
|
|
161
|
+
const previous = parseHashRouteWithFilters(renderedHash);
|
|
162
|
+
if (["pool", "dags", "failures"].includes(previous.view)) {
|
|
163
|
+
inspectRouteStates.set(`list:${renderedHash}`, { top: scrollRoot?.scrollTop ?? 0, left: scrollRoot?.scrollLeft ?? 0 });
|
|
164
|
+
inspectRouteStates.set("return-list", renderedHash);
|
|
165
|
+
}
|
|
166
|
+
if (previous.view === "dag" && uiState.currentDagRunId) inspectRouteStates.set(`dag:${uiState.currentDagRunId}`, {
|
|
167
|
+
nodeId: uiState.selectedDagNodeId, tab: uiState.dagInspectorTab,
|
|
168
|
+
executionKey: uiState.dagInspectorAttempt?.key ?? null, open: uiState.dagInspectorOpen,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
renderedHash = destination;
|
|
172
|
+
renderedScope = scope;
|
|
173
|
+
const token = ++routeToken;
|
|
153
174
|
stopTimers();
|
|
154
|
-
const parsed =
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
175
|
+
const parsed = parseHashRouteWithFilters(destination);
|
|
176
|
+
const saved = inspectRouteStates.get(`list:${destination}`);
|
|
177
|
+
Promise.resolve(startView(parsed)).then(() => {
|
|
178
|
+
if (!saved || token !== routeToken || scope !== workspaceScope()) return;
|
|
179
|
+
requestAnimationFrame(() => {
|
|
180
|
+
if (token !== routeToken || scope !== workspaceScope() || !scrollRoot) return;
|
|
181
|
+
scrollRoot.scrollTop = saved.top;
|
|
182
|
+
scrollRoot.scrollLeft = saved.left;
|
|
183
|
+
});
|
|
184
|
+
});
|
|
158
185
|
}
|
|
159
186
|
|
|
160
187
|
function resumeView(hashValue) {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** A retry always replays the exact reviewed payload and idempotency key. */
|
|
2
|
+
export function createInspectorSubmission(action, payload) {
|
|
3
|
+
return {
|
|
4
|
+
action,
|
|
5
|
+
payload: JSON.parse(JSON.stringify(payload)),
|
|
6
|
+
requestId: `inspect-${action}-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`,
|
|
7
|
+
inFlight: false,
|
|
8
|
+
operationId: null,
|
|
9
|
+
rejected: false,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function sendInspectorSubmission(submission, send) {
|
|
14
|
+
if (submission.pending) return submission.pending;
|
|
15
|
+
if (submission.operationId) return submission.response;
|
|
16
|
+
submission.inFlight = true;
|
|
17
|
+
submission.pending = (async () => {
|
|
18
|
+
const response = await send(submission.action, submission.payload, submission.requestId);
|
|
19
|
+
submission.response = response;
|
|
20
|
+
submission.operationId = response.ok ? response.body?.operationId ?? null : null;
|
|
21
|
+
// Only a definitive client rejection permits a different payload. Transport
|
|
22
|
+
// failure and server failure can follow acceptance, so they require replay.
|
|
23
|
+
submission.rejected = !response.ok && response.status >= 400 && response.status < 500;
|
|
24
|
+
return response;
|
|
25
|
+
})();
|
|
26
|
+
try { return await submission.pending; }
|
|
27
|
+
finally { submission.inFlight = false; submission.pending = null; }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function hasUnresolvedSubmission(submission) {
|
|
31
|
+
return Boolean(submission?.payload && !submission.operationId && !submission.rejected);
|
|
32
|
+
}
|
|
@@ -21,6 +21,7 @@ export function artifactLink(label, refPath) {
|
|
|
21
21
|
const a = el("a", "artifact-link", label);
|
|
22
22
|
a.href = `/api/artifacts?path=${encodeURIComponent(refPath)}&tail=200`;
|
|
23
23
|
a.target = "_blank";
|
|
24
|
+
a.addEventListener("click", (event) => event.stopPropagation());
|
|
24
25
|
return a;
|
|
25
26
|
}
|
|
26
27
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isPlainNavigationClick, isRowNavigationClick } from "./router.js";
|
|
1
2
|
/** Object relation navigation (Observe UI R5). */
|
|
2
3
|
import { el } from "./dom.js";
|
|
3
4
|
import { inspectPathToCanonicalHref, navigate } from "./router.js";
|
|
@@ -50,6 +51,7 @@ export function objectRouteLink(kind, id, snapshot, featureId) {
|
|
|
50
51
|
const a = el("a", "object-link", id);
|
|
51
52
|
a.href = inspectPathToCanonicalHref(path);
|
|
52
53
|
a.addEventListener("click", (e) => {
|
|
54
|
+
if (!isPlainNavigationClick(e)) return;
|
|
53
55
|
e.preventDefault();
|
|
54
56
|
e.stopPropagation();
|
|
55
57
|
navigate(path);
|
|
@@ -201,6 +201,11 @@ export function parseHashRouteWithFilters(hashValue) {
|
|
|
201
201
|
const raw = String(hashValue ?? "");
|
|
202
202
|
const withoutQuery = raw.replace(/^#/, "").split("?")[0];
|
|
203
203
|
const base = parseHashRoute("#" + (withoutQuery || "/"));
|
|
204
|
+
if (base.view === "dag") {
|
|
205
|
+
const params = new URLSearchParams(raw.includes("?") ? raw.slice(raw.indexOf("?") + 1) : "");
|
|
206
|
+
const nodeId = params.get("node");
|
|
207
|
+
if (nodeId) return { ...base, inspector: { nodeId, tab: params.get("tab") || null, executionKey: /^(attempt|pass)-[1-9]\d*$/.test(params.get("attempt") || "") ? params.get("attempt") : null } };
|
|
208
|
+
}
|
|
204
209
|
if (base.view === "dags") {
|
|
205
210
|
const history = parseDagHistoryQuery(raw);
|
|
206
211
|
return { ...base, page: history.page, pageSize: history.pageSize };
|
|
@@ -220,3 +225,11 @@ export function buildHashPath(path, filters = {}) {
|
|
|
220
225
|
const qs = params.toString();
|
|
221
226
|
return qs ? `${base}?${qs}` : base;
|
|
222
227
|
}
|
|
228
|
+
|
|
229
|
+
/** Leave browser-native modified clicks and text selection intact. */
|
|
230
|
+
export function isPlainNavigationClick(event) {
|
|
231
|
+
return !event.defaultPrevented && (event.button == null || event.button === 0) && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey;
|
|
232
|
+
}
|
|
233
|
+
export function isRowNavigationClick(event) {
|
|
234
|
+
return isPlainNavigationClick(event) && !event.target?.closest?.("a, button, input, select, textarea, summary, [role='button']") && !String(globalThis.getSelection?.() ?? "").trim();
|
|
235
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isPlainNavigationClick, isRowNavigationClick } from "./router.js";
|
|
1
2
|
/** Run step/command/artifact processing and task history rows. */
|
|
2
3
|
import {
|
|
3
4
|
STEP_LABELS,
|
|
@@ -132,6 +133,7 @@ export function taskRunHistoryRow(run) {
|
|
|
132
133
|
);
|
|
133
134
|
a.textContent = run.workerRunId;
|
|
134
135
|
a.addEventListener("click", (e) => {
|
|
136
|
+
if (!isPlainNavigationClick(e)) return;
|
|
135
137
|
e.preventDefault();
|
|
136
138
|
navigate(`#/run/${encodeURIComponent(run.workerRunId)}`);
|
|
137
139
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { isPlainNavigationClick, isRowNavigationClick } from "./router.js";
|
|
1
2
|
/** Shell chrome: view switching, breadcrumb, header refresh, relation bar. */
|
|
2
3
|
import { UI_TEXT } from "./constants.js";
|
|
3
|
-
import { clearNode, el } from "./dom.js";
|
|
4
|
+
import { clearNode, el, iconButton } from "./dom.js";
|
|
4
5
|
import { formatAgo } from "./format.js";
|
|
5
6
|
import { inspectPathToCanonicalHref, navigate } from "./router.js";
|
|
6
7
|
import { buildObjectRelations, renderObjectRelationBar } from "./relations.js";
|
|
@@ -26,12 +27,15 @@ export function setBreadcrumb(parts) {
|
|
|
26
27
|
a.href = inspectPathToCanonicalHref(part.href);
|
|
27
28
|
a.textContent = part.label;
|
|
28
29
|
a.addEventListener("click", (e) => {
|
|
30
|
+
if (!isPlainNavigationClick(e)) return;
|
|
29
31
|
e.preventDefault();
|
|
30
32
|
navigate(part.href);
|
|
31
33
|
});
|
|
32
34
|
nav.appendChild(a);
|
|
33
35
|
} else {
|
|
34
|
-
|
|
36
|
+
// 当前位置包一层 span:窄屏可对末级做 ellipsis 截断。
|
|
37
|
+
const current = el("span", "breadcrumb-current", part.label);
|
|
38
|
+
nav.appendChild(current);
|
|
35
39
|
}
|
|
36
40
|
});
|
|
37
41
|
}
|
|
@@ -43,7 +47,7 @@ export function updateHeaderRefresh(generatedAt, options = {}) {
|
|
|
43
47
|
elRefresh.textContent = "";
|
|
44
48
|
return;
|
|
45
49
|
}
|
|
46
|
-
const modeLabel =
|
|
50
|
+
const modeLabel = options.mode === "ended" ? "已结束 · 按需刷新" : options.mode === "paused" ? "刷新已暂停" :
|
|
47
51
|
options.mode === "manual"
|
|
48
52
|
? UI_TEXT.manualRefresh
|
|
49
53
|
: options.mode === "auto"
|
|
@@ -63,3 +67,32 @@ export function mountRelationBar(containerId, ids, snapshot) {
|
|
|
63
67
|
elBar.appendChild(el("p", "empty", "关联投影暂时不可用。"));
|
|
64
68
|
}
|
|
65
69
|
}
|
|
70
|
+
|
|
71
|
+
export function bindManualViewRefresh(view, refresh) {
|
|
72
|
+
const heading = document.querySelector(`#view-${view} .view-heading`);
|
|
73
|
+
if (!heading) return;
|
|
74
|
+
let button = heading.querySelector(".view-manual-refresh");
|
|
75
|
+
if (!button) { button = iconButton("刷新本页", "ri-refresh-line", "view-manual-refresh"); button.type = "button"; heading.appendChild(button); }
|
|
76
|
+
button.onclick = async () => { button.disabled = true; try { await refresh(); } finally { button.disabled = false; } };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function mountTaskRelationPicker(containerId, ids, tasks, snapshot) {
|
|
80
|
+
const host = document.getElementById(containerId);
|
|
81
|
+
if (!host) return;
|
|
82
|
+
const previous = host.dataset.selectedTask;
|
|
83
|
+
const taskKey = (task) => JSON.stringify([task.featureId ?? ids.featureId ?? "", task.taskId]);
|
|
84
|
+
clearNode(host);
|
|
85
|
+
const select = el("select", "relation-task-picker");
|
|
86
|
+
select.setAttribute("aria-label", `选择关联任务,共 ${tasks.length} 个`);
|
|
87
|
+
for (const task of tasks) { const option = el("option", null, `${task.title || task.taskId} · ${task.status ?? "未记录"}`); option.value = taskKey(task); select.appendChild(option); }
|
|
88
|
+
select.value = tasks.some((task) => taskKey(task) === previous) ? previous : (tasks[0] ? taskKey(tasks[0]) : "");
|
|
89
|
+
const relations = el("div");
|
|
90
|
+
const render = () => {
|
|
91
|
+
host.dataset.selectedTask = select.value;
|
|
92
|
+
const task = tasks.find((task) => taskKey(task) === select.value) ?? tasks[0];
|
|
93
|
+
clearNode(relations);
|
|
94
|
+
relations.appendChild(renderObjectRelationBar(buildObjectRelations({ ...ids, taskId: task?.taskId, featureId: task?.featureId ?? ids.featureId, workerRunId: task?.workerRunId, dagRunId: task?.dagRunId, batchRunId: ids.batchRunId ?? task?.batchRunId }, snapshot), snapshot));
|
|
95
|
+
};
|
|
96
|
+
if (tasks.length > 1) { host.appendChild(el("span", "muted", `${tasks.length} 个关联任务 `)); host.appendChild(select); }
|
|
97
|
+
host.appendChild(relations); select.addEventListener("change", render); render();
|
|
98
|
+
}
|
|
@@ -173,8 +173,25 @@ export function updateDagTrajectoryViewportState(
|
|
|
173
173
|
export const dagInspectorViewportStates = new Map();
|
|
174
174
|
|
|
175
175
|
/** Build a stable inspector scroll region key (AC-005 context isolation). */
|
|
176
|
-
export function inspectorViewportRegionKey(dagRunId, nodeId, tab) {
|
|
177
|
-
return `dag:${dagRunId ?? ""}:${nodeId ?? ""}:${tab ?? "output"}`;
|
|
176
|
+
export function inspectorViewportRegionKey(dagRunId, nodeId, tab, executionKey = "") {
|
|
177
|
+
return `dag:${dagRunId ?? ""}:${nodeId ?? ""}:${tab ?? "output"}${executionKey ? `:${executionKey}` : ""}`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Session-only reading/draft state. Never persists prompt text to disk. */
|
|
181
|
+
export const inspectorReadingStates = new Map();
|
|
182
|
+
export const inspectorNodeSelections = new Map();
|
|
183
|
+
export const inspectorDrafts = new Map();
|
|
184
|
+
export const inspectorOperations = new Map();
|
|
185
|
+
export const inspectRouteStates = new Map();
|
|
186
|
+
export const taskHistoryStates = new Map();
|
|
187
|
+
export function inspectorReadingState(dagRunId, nodeId, tab, executionKey, followLatest = false) {
|
|
188
|
+
const key = inspectorViewportRegionKey(dagRunId, nodeId, tab, executionKey || "latest");
|
|
189
|
+
if (!inspectorReadingStates.has(key)) inspectorReadingStates.set(key, {
|
|
190
|
+
key, scrollTop: 0, scrollLeft: 0, followLatest,
|
|
191
|
+
filter: "all", query: "", errorsOnly: false, showProtocol: false,
|
|
192
|
+
openDetails: new Set(), seenCount: 0, eventCount: 0,
|
|
193
|
+
});
|
|
194
|
+
return inspectorReadingStates.get(key);
|
|
178
195
|
}
|
|
179
196
|
|
|
180
197
|
export function updateDagInspectorViewportState(
|
|
@@ -403,6 +420,7 @@ export function applyWorkspaceScopeSwitch(
|
|
|
403
420
|
trace.push("abort-old");
|
|
404
421
|
// 2. clear old
|
|
405
422
|
uiState.lastSnapshot = null;
|
|
423
|
+
for (const cache of [dagInspectorViewportStates, inspectorReadingStates, inspectorNodeSelections, inspectorDrafts, inspectorOperations, inspectRouteStates, taskHistoryStates]) cache.clear();
|
|
406
424
|
uiState.specEvidenceDetail = null;
|
|
407
425
|
uiState.specEvidenceListEvidence = null;
|
|
408
426
|
clearDagNodeExecutionOutputCache();
|
|
@@ -531,6 +549,21 @@ export function computeFollowLatest(
|
|
|
531
549
|
return height - top - client < Math.max(0, Number(threshold) || 0);
|
|
532
550
|
}
|
|
533
551
|
|
|
552
|
+
// Chat-style jump-to-bottom visibility: the floating control is only useful
|
|
553
|
+
// when the region can scroll and the viewport is away from the bottom. It
|
|
554
|
+
// applies to every execution-record viewport, running or ended alike.
|
|
555
|
+
export function isScrollRegionAboveBottom(content, overflowPx = 4) {
|
|
556
|
+
const scrollHeight = Number(content?.scrollHeight ?? 0);
|
|
557
|
+
const clientHeight = Number(content?.clientHeight ?? 0);
|
|
558
|
+
if (!Number.isFinite(scrollHeight) || !Number.isFinite(clientHeight)) {
|
|
559
|
+
return false;
|
|
560
|
+
}
|
|
561
|
+
if (scrollHeight - clientHeight <= Math.max(0, Number(overflowPx) || 0)) {
|
|
562
|
+
return false;
|
|
563
|
+
}
|
|
564
|
+
return !computeFollowLatest(content?.scrollTop ?? 0, scrollHeight, clientHeight);
|
|
565
|
+
}
|
|
566
|
+
|
|
534
567
|
// Generalised viewport-state record keyed by `regionKey`, so the same helper
|
|
535
568
|
// serves the DAG node inspector output ("dag:<runId>:<nodeId>:output") and the
|
|
536
569
|
// Worker run output ("run:<runId>:output").
|