@tea-agent/loop-agent 0.39.0-next.8 → 0.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +4 -1
- package/CHANGELOG.md +264 -67
- package/README.md +11 -5
- package/bin/loop-agent.js +7 -3
- package/dist/application/dag/generate-task-dag.js +9 -0
- package/dist/application/task-lifecycle/advance.js +4 -0
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +16 -5
- package/dist/cli/program.js +16 -2
- package/dist/cli/update/notifier.js +2 -2
- package/dist/cli.js +2 -1
- package/dist/commands/dag-rerun.js +55 -1
- package/dist/commands/init-model-catalog.js +464 -0
- package/dist/commands/init-upgrade.js +265 -97
- package/dist/commands/init.js +428 -27
- package/dist/commands/inspect-next.js +8 -0
- package/dist/commands/task-advance.js +31 -0
- package/dist/executors/dag-pi-executor.js +124 -18
- package/dist/executors/pi-event-serializer.js +42 -1
- package/dist/executors/pi-executor.js +51 -0
- package/dist/executors/pi-extension-resolver.js +233 -0
- package/dist/executors/pi-playwright-cli-tool.js +74 -28
- package/dist/executors/pi-sdk-executor.js +187 -59
- package/dist/executors/shell-executor.js +554 -33
- package/dist/executors/shell-write-guard.js +7 -0
- package/dist/shared/dag-failure-category.js +138 -0
- package/dist/shared/dag-prompt-override.js +28 -0
- package/dist/shared/operator/capabilities.js +181 -27
- package/dist/shared/playwright-cli-command-policy.js +15 -0
- package/dist/shared/update/console-notifier.js +100 -0
- package/dist/{cli → shared}/update/npm-client.js +41 -8
- package/dist/{cli → shared}/update/state.js +41 -13
- package/dist/task/config-types.js +28 -0
- package/dist/task/contract/constants.js +1 -0
- package/dist/task/contract/diff.js +26 -0
- package/dist/task/contract/project.js +5 -0
- package/dist/task/contract/schema.js +6 -1
- package/dist/task/source-prepare/build-draft.js +15 -0
- package/dist/task/source-prepare/parse-intent.js +24 -4
- package/dist/worker/cli.js +31 -1
- package/dist/worker/console/chat/deferred-turn.js +91 -0
- package/dist/worker/console/chat/mcp-inventory.js +221 -0
- package/dist/worker/console/chat/operation-card.js +8 -1
- package/dist/worker/console/chat/pi-console-config.js +81 -10
- package/dist/worker/console/chat/pi-runtime.js +165 -83
- package/dist/worker/console/chat/repo-walk.js +10 -1
- package/dist/worker/console/chat/routes.js +480 -55
- package/dist/worker/console/chat/semantic-activity.js +20 -8
- package/dist/worker/console/chat/session-store.js +146 -2
- package/dist/worker/console/chat/turn-process.js +10 -10
- package/dist/worker/console/dag-execution-receipt.js +33 -0
- package/dist/worker/console/inspect-split.js +18 -0
- package/dist/worker/console/operation-run-facts.js +19 -3
- package/dist/worker/console/operator-actions.js +195 -4
- package/dist/worker/console/operator-user-error.js +2 -2
- package/dist/worker/console/pi-readiness.js +5 -4
- package/dist/worker/console/recovery-cta.js +4 -4
- package/dist/worker/console/recovery-error-copy.js +4 -4
- package/dist/worker/console/routes.js +43 -2
- package/dist/worker/console/security.js +63 -9
- package/dist/worker/console/server.js +42 -6
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-BLXFe4qI.js → abnfDiagram-N423BO3Z-Br92EGb7.js} +1 -1
- package/dist/worker/console/static/assets/{arc-7iOJeqho.js → arc-oulImtQq.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CYoyj5K7.js → architectureDiagram-T3A2C74G-tyHc6aqO.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-fK01JNlR.js → blockDiagram-VBNYF7ZC-CY86BSpT.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-bzbw5D_Z.js → c4Diagram-5PPSVZJV-BKKsDmNs.js} +1 -1
- package/dist/worker/console/static/assets/channel-TY36zt-i.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-Cf6hJFFG.js → chunk-2GRJ4B5K-CXZdjl3B.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DSFXR-et.js → chunk-2Q5K7J3B-B-aIFDQ5.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BagJechl.js → chunk-5RXB4S5H-BDgAW_B2.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-E7QNMFaW.js → chunk-5VM5RSS4-DABa4UPR.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BG_eOBrC.js → chunk-6Q2QTUOP-aGbysVOx.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-Bo42dqol.js → chunk-GF5L2VYU-B8MSZDVs.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-dibM-87v.js → chunk-JWPE2WC7-CpelV73g.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-Bytrp3Oe.js → chunk-KBJHAD2P-BE04ty3z.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DBABILrr.js → chunk-RYQCIY6F-BUxTkaaR.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-DCUk5Pf9.js → chunk-XXDRQBXY-CYYBVKfX.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Dk7QCChM.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Dk7QCChM.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BlGE9v6E.js → cose-bilkent-JH36ORCC-CEQF_xmQ.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-DGl5ewph.js → cynefin-VYW2F7L2-CqpxN5IT.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-DsJ_SVH_.js → cynefinDiagram-MW4NZA55-B3PzanVi.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BIhjFdzv.js → dagre-VZM6K2ZE-BSUGCNx1.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Dmy1ctTb.js → diagram-7IWD3JNH-CdLevU5T.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-DYCtWUF3.js → diagram-B4RE2ZJO-BjpF_Kok.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-BMZFHVd2.js → diagram-LBJQPF4R-C4I6NJny.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-b_U8gfaO.js → diagram-Q27KOJAE-C8hecCr5.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-Dzy_bn-I.js → diagram-UB23O5K3-Daod6tOS.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-DJCiMNEI.js → ebnfDiagram-BXEA7PRR-BvvmazGv.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-28lD016B.js → erDiagram-JOGREHBK-DeN_vfV4.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-DGyHJmIw.js → flowDiagram-UKHOOZJN-D6dq1t9z.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CzdwUjrO.js → ganttDiagram-PKOTCBZU-DziIdNY4.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-D9bGwuo8.js → gitGraphDiagram-DS77QQ5N-Dy4s5CXr.js} +1 -1
- package/dist/worker/console/static/assets/index-C3GgHg-g.css +1 -0
- package/dist/worker/console/static/assets/index-Ci8ksvBm.js +406 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-LJgJUQFC.js → infoDiagram-6WML65LV-Czv5eYx2.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-98LaB8zb.js → ishikawaDiagram-WSZJBQD7-Bsk3nFRT.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-D-ISzwlQ.js → journeyDiagram-NVQOT4AX-DxGmNj5K.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-gfYOI-EL.js → kanban-definition-27J2QSJJ-CYCFqkO_.js} +1 -1
- package/dist/worker/console/static/assets/{linear-CyntZgKV.js → linear-B0YzRwrd.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-BBOR7dg0.js → mermaid.core-t3EZtIGh.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-CQAgwPMk.js → mindmap-definition-FAOFIHXS-BTXpTv0T.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DkMiZFuV.js → pegDiagram-VL7TDLO6-DU8UkKqA.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DLNXa_0J.js → pieDiagram-7S7Q4E2Y-DP4y8ohp.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-Dc7bdzjm.js → quadrantDiagram-CIZ2JOQS-B07vNaYR.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BxMB0QqW.js → railroadDiagram-AXF67PYL-xDWktb2i.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-FjumoKn4.js → requirementDiagram-LRYGKXZP-BLNMKqeS.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-CkhKc_E3.js → sankeyDiagram-W5VNT64P-n0adalxH.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DoBWqTTH.js → sequenceDiagram-SI44F4Z6-BbnS-2DL.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-CQeh_YeE.js → sizeCapture-X5ZJPWSS-B4uPpCLu.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-LuHmFW-I.js → stateDiagram-OKZ733FA--MMbB0fX.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BqOigMQ4.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CHwbDctY.js → swimlanes-SLNWSIFB-BXUgp2QY.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Caxz9UA2.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CoMngk0X.js → timeline-definition-Z64GVDOM-CsaMxki7.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-CURYbtbO.js → vennDiagram-T6HMQDX7-C2IuG9iH.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-BdqSECjU.js → wardleyDiagram-T6FBY63Y-CmEWOXCW.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-DbMUYuAg.js → xychartDiagram-ELKLHX3M-D_9LE0MG.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/useOperatorActions.js +1 -1
- package/dist/worker/console/static-src/app/useRecoveryActions.js +2 -2
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +38 -2
- package/dist/worker/console/static-src/app/useRunProgress.js +46 -0
- package/dist/worker/console/static-src/operator-chat/cards/failure-category-advice.js +25 -0
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +74 -26
- package/dist/worker/console/static-src/operator-chat/input-history.js +76 -0
- package/dist/worker/console/static-src/operator-chat/mutation-gate.js +1 -1
- package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +328 -0
- package/dist/worker/console/static-src/operator-chat/resource-diagnostics.js +204 -0
- package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +8 -1
- package/dist/worker/console/static-src/operator-chat/turn-submission.js +54 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +3 -0
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +237 -11
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +50 -22
- package/dist/worker/console/static-src/operator-chat/useComposer.js +32 -0
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +26 -1
- package/dist/worker/console/static-src/pages/tasks/run-id-resolution.js +79 -0
- package/dist/worker/console/static-src/pages/tasks/run-ownership-verify.js +23 -0
- package/dist/worker/console/static-src/pages/tasks/run-panel-progress.js +110 -0
- package/dist/worker/loop-agent/loop-agent-client.js +7 -3
- package/dist/worker/materialize/harness-task-lineage.js +5 -2
- package/dist/worker/observability/read-model.js +22 -0
- package/dist/worker/observe/node-input.js +74 -3
- package/dist/worker/observe/node-process.js +377 -0
- package/dist/worker/observe/routes.js +36 -3
- package/dist/worker/observe/shell-handler-keys.js +34 -0
- package/dist/worker/observe/static/api.js +66 -0
- package/dist/worker/observe/static/constants.js +8 -1
- package/dist/worker/observe/static/dag-history-labels.js +4 -0
- package/dist/worker/observe/static/format.js +23 -0
- package/dist/worker/observe/static/markdown-render.js +20 -1
- package/dist/worker/observe/static/operator-chrome.js +4 -0
- package/dist/worker/observe/static/state.js +13 -2
- package/dist/worker/observe/static/styles.css +73 -0
- package/dist/worker/observe/static/views/dag-inspector.js +612 -8
- package/dist/worker/observe/static/views/dag.js +8 -1
- package/dist/worker/observe/static/views/failures.js +5 -2
- package/dist/worker/observe/static/views/session-timeline.js +78 -9
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +827 -53
- package/dist/workflows/dag/backend-test-case-manifest.js +4 -0
- package/dist/workflows/dag/backend-test-execution-contract.js +18 -9
- package/dist/workflows/dag/backend-test-gap-fill.js +205 -0
- package/dist/workflows/dag/backend-test-intake-context.js +32 -0
- package/dist/workflows/dag/backend-test-layout.js +133 -0
- package/dist/workflows/dag/backend-test-markdown-workflow.js +167 -32
- package/dist/workflows/dag/backend-test-module-stem.js +5 -0
- package/dist/workflows/dag/backend-test-pytest-collection.js +368 -33
- package/dist/workflows/dag/backend-test-result-contract.js +1 -1
- package/dist/workflows/dag/backend-test-scenario-param.js +884 -140
- package/dist/workflows/dag/backend-test-scenario-partitions.js +321 -0
- package/dist/workflows/dag/backend-test-writer-completeness.js +100 -56
- package/dist/workflows/dag/contract-output-registry.js +15 -0
- package/dist/workflows/dag/contract-validator-registrations.js +3 -1
- package/dist/workflows/dag/convergence/controller.js +141 -0
- package/dist/workflows/dag/dynamic-runtime/loop-until.js +1 -1
- package/dist/workflows/dag/dynamic-runtime/map.js +25 -9
- package/dist/workflows/dag/dynamic-runtime/shared.js +1 -1
- package/dist/workflows/dag/failure-category.js +7 -116
- package/dist/workflows/dag/frontend-implementation-contract.js +377 -4
- package/dist/workflows/dag/frontend-plan-render.js +1 -1
- package/dist/workflows/dag/frontend-prewrite-gate.js +96 -84
- package/dist/workflows/dag/frontend-repair.js +14 -0
- package/dist/workflows/dag/frontend-risk.js +15 -2
- package/dist/workflows/dag/frontend-test-case-checklist.js +30 -4
- package/dist/workflows/dag/frontend-test-case-manifest.js +11 -4
- package/dist/workflows/dag/frontend-test-case-quality.js +11 -16
- package/dist/workflows/dag/frontend-test-environment-probe.js +230 -0
- package/dist/workflows/dag/frontend-test-html-report.js +3 -1
- package/dist/workflows/dag/frontend-test-l5-report.js +3 -1
- package/dist/workflows/dag/frontend-test-layout.js +159 -0
- package/dist/workflows/dag/frontend-test-markdown.js +61 -0
- package/dist/workflows/dag/frontend-test-result-contract.js +188 -42
- package/dist/workflows/dag/frontend-test-standard-scenarios.js +70 -0
- package/dist/workflows/dag/frontend-verification-trace.js +29 -4
- package/dist/workflows/dag/init-hybrid.js +894 -205
- package/dist/workflows/dag/node-execution.js +183 -15
- package/dist/workflows/dag/prompt.js +60 -1
- package/dist/workflows/dag/rerun-feedback.js +212 -0
- package/dist/workflows/dag/rerun-plan.js +224 -27
- package/dist/workflows/dag/rerun-run.js +61 -6
- package/dist/workflows/dag/rerun-task.js +61 -2
- package/dist/workflows/dag/retry-policy.js +109 -0
- package/dist/workflows/dag/structured-output-repair.js +712 -0
- package/dist/workflows/dag/types.js +143 -1
- package/dist/workflows/dag/validate.js +64 -20
- package/docs/init-surface.manifest.json +5 -0
- package/docs/operations/README.md +1 -1
- package/docs/operations/local-development-environment.md +1 -5
- package/docs/skills/vetted-skill-registry.md +14 -0
- package/docs/templates/README.md +1 -1
- package/docs/templates/agent-dag.schema.json +44 -0
- package/docs/templates/backend-test-dag.json +60 -35
- package/docs/templates/frontend-implementation-contract.schema.json +1 -1
- package/docs/templates/frontend-test-case-checklist.md +2 -2
- package/docs/templates/frontend-test-dag.json +8 -10
- package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
- package/docs/templates/init-managed-agents.md +13 -3
- package/harness.json +6 -4
- package/package.json +12 -4
- package/scripts/next-info.mjs +356 -0
- package/scripts/next-publish-gate.mjs +238 -0
- package/scripts/release-source-binding.mjs +251 -0
- package/skills/analyze-product-requirements/SKILL.md +8 -5
- package/skills/analyze-product-requirements/references/acceptance-criteria.md +1 -1
- package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +10 -6
- package/skills/analyze-product-requirements/references/example.md +50 -0
- package/skills/analyze-product-requirements/references/forward-test-cases.md +11 -7
- package/skills/analyze-product-requirements/references/kb-integration.md +5 -5
- package/skills/analyze-product-requirements/references/product-analysis-schema.md +8 -4
- package/skills/analyze-product-requirements/references/product-requirement-schema.md +2 -2
- package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +1 -1
- package/skills/analyze-product-requirements/scripts/test-validators.mjs +11 -1
- package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +20 -1
- package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +9 -0
- package/skills/codebase-scout/SKILL.md +1 -1
- package/skills/fe-test-ui-scout/SKILL.md +65 -0
- package/skills/fe-test-ui-scout/references/ledger-schema.md +62 -0
- package/skills/fe-test-ui-scout/references/recon-protocol.md +54 -0
- package/skills/frontend-implementation/references/node-contracts.md +2 -2
- package/skills/improve-codebase-architecture/SKILL.md +81 -0
- package/skills/improve-codebase-architecture/deepening.md +37 -0
- package/skills/improve-codebase-architecture/html-report.md +123 -0
- package/skills/improve-codebase-architecture/interface-design.md +44 -0
- package/skills/improve-codebase-architecture/language.md +53 -0
- package/skills/loop-agent/references/command-reference.md +11 -4
- package/skills/playwright-cli/SKILL.md +23 -1
- package/skills/playwright-cli-case-generator/SKILL.md +29 -8
- package/dist/worker/console/static/assets/channel-BBOTJHvD.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Cm8Yf8tD.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Cm8Yf8tD.js +0 -1
- package/dist/worker/console/static/assets/index-B-qWGThd.js +0 -325
- package/dist/worker/console/static/assets/index-C-xRgY_0.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DZihfdKc.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CKnXCjHX.js +0 -8
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
|
-
import {
|
|
3
|
-
import { buildTurnProcessGroups } from "../../chat/turn-process.js";
|
|
2
|
+
import { buildTurnProcessGroups, hydrateSessionThread, selectRecentTurnGroups, } from "../../chat/turn-process.js";
|
|
4
3
|
import { createChatSseEventApplier, } from "./chat-sse-events.js";
|
|
5
4
|
import { taskContextUnchanged } from "./format.js";
|
|
6
5
|
const EMPTY_USAGE = {
|
|
@@ -32,6 +31,8 @@ export function useChatThread(params) {
|
|
|
32
31
|
const [contextLoading, setContextLoading] = useState(false);
|
|
33
32
|
const [usage, setUsage] = useState(EMPTY_USAGE);
|
|
34
33
|
const [streaming, setStreaming] = useState(false);
|
|
34
|
+
const [turnPhase, setTurnPhase] = useState("idle");
|
|
35
|
+
const [deferredTurn, setDeferredTurn] = useState(null);
|
|
35
36
|
/** Assistant bubble id for the in-flight turn; only this row shows the streaming cursor. */
|
|
36
37
|
const [streamingAssistantId, setStreamingAssistantId] = useState(null);
|
|
37
38
|
const [showSystemInMessages] = useState(false);
|
|
@@ -45,6 +46,8 @@ export function useChatThread(params) {
|
|
|
45
46
|
setHumanGateCards,
|
|
46
47
|
setArtifactCards,
|
|
47
48
|
setStreaming,
|
|
49
|
+
setTurnPhase,
|
|
50
|
+
setDeferredTurn,
|
|
48
51
|
setStreamingAssistantId,
|
|
49
52
|
setUnread,
|
|
50
53
|
setOperationCards,
|
|
@@ -121,9 +124,7 @@ export function useChatThread(params) {
|
|
|
121
124
|
}, 350);
|
|
122
125
|
return () => window.clearTimeout(timer);
|
|
123
126
|
}, [sessionId, operationCards, humanGateCards, interview, loadTaskContext]);
|
|
124
|
-
const
|
|
125
|
-
.filter((m) => m.role !== "system" || showSystemInMessages)
|
|
126
|
-
.slice(-historyLimit), [messages, showSystemInMessages, historyLimit]);
|
|
127
|
+
const timelineMessages = useMemo(() => messages.filter((m) => m.role !== "system" || showSystemInMessages), [messages, showSystemInMessages]);
|
|
127
128
|
useEffect(() => {
|
|
128
129
|
const onVisibility = () => {
|
|
129
130
|
if (!document.hidden)
|
|
@@ -158,12 +159,30 @@ export function useChatThread(params) {
|
|
|
158
159
|
statusKind,
|
|
159
160
|
};
|
|
160
161
|
}, [toolCalls]);
|
|
161
|
-
const turnGroups = useMemo(() =>
|
|
162
|
-
|
|
162
|
+
const turnGroups = useMemo(() => {
|
|
163
|
+
const all = buildTurnProcessGroups({
|
|
164
|
+
messages: timelineMessages,
|
|
165
|
+
toolCalls,
|
|
166
|
+
streaming,
|
|
167
|
+
activeAssistantId: streamingAssistantId,
|
|
168
|
+
});
|
|
169
|
+
return selectRecentTurnGroups(all, historyLimit);
|
|
170
|
+
}, [
|
|
171
|
+
timelineMessages,
|
|
163
172
|
toolCalls,
|
|
164
173
|
streaming,
|
|
165
|
-
|
|
166
|
-
|
|
174
|
+
streamingAssistantId,
|
|
175
|
+
historyLimit,
|
|
176
|
+
]);
|
|
177
|
+
const visibleMessages = useMemo(() => {
|
|
178
|
+
const next = [];
|
|
179
|
+
for (const group of turnGroups) {
|
|
180
|
+
if (group.user)
|
|
181
|
+
next.push(group.user);
|
|
182
|
+
next.push(...group.assistants);
|
|
183
|
+
}
|
|
184
|
+
return next;
|
|
185
|
+
}, [turnGroups]);
|
|
167
186
|
/** Reset all session-scoped projections before adopting a fresh session. */
|
|
168
187
|
const resetForNewSession = useCallback(() => {
|
|
169
188
|
setMessages([]);
|
|
@@ -175,6 +194,8 @@ export function useChatThread(params) {
|
|
|
175
194
|
setTaskContext(null);
|
|
176
195
|
setUsage(EMPTY_USAGE);
|
|
177
196
|
setStreaming(false);
|
|
197
|
+
setTurnPhase("idle");
|
|
198
|
+
setDeferredTurn(null);
|
|
178
199
|
setStreamingAssistantId(null);
|
|
179
200
|
setUnread(0);
|
|
180
201
|
setHistoryLimit(50);
|
|
@@ -192,6 +213,8 @@ export function useChatThread(params) {
|
|
|
192
213
|
setUsage(EMPTY_USAGE);
|
|
193
214
|
refs.usageResponseKeysRef.current = new Set();
|
|
194
215
|
setStreaming(next.streaming);
|
|
216
|
+
setTurnPhase(next.turnPhase ?? (next.streaming ? "running" : "idle"));
|
|
217
|
+
setDeferredTurn(next.deferredTurn ?? null);
|
|
195
218
|
setStreamingAssistantId(null);
|
|
196
219
|
setUnread(0);
|
|
197
220
|
setHistoryLimit(50);
|
|
@@ -213,6 +236,8 @@ export function useChatThread(params) {
|
|
|
213
236
|
const activeTurnId = body.session.activeTurnId ?? null;
|
|
214
237
|
refs.activeTurnIdRef.current = activeTurnId;
|
|
215
238
|
refs.streamingRef.current = Boolean(activeTurnId);
|
|
239
|
+
setTurnPhase(body.session.turnPhase ?? (activeTurnId ? "running" : "idle"));
|
|
240
|
+
setDeferredTurn(body.session.deferredTurn ?? null);
|
|
216
241
|
// Resume the events stream from the snapshot tail so the next
|
|
217
242
|
// reconnect never immediately re-expires (AC-003). The refs below
|
|
218
243
|
// are reset so any follow-up tail replay re-applies cleanly.
|
|
@@ -220,18 +245,9 @@ export function useChatThread(params) {
|
|
|
220
245
|
refs.seenEventIdsRef.current = new Set();
|
|
221
246
|
refs.turnAssistantIdsRef.current = new Map();
|
|
222
247
|
refs.sealedAssistantIdsRef.current = new Set();
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
const content = parseAssistantContent(message.content);
|
|
227
|
-
return {
|
|
228
|
-
id: message.id,
|
|
229
|
-
role: message.role,
|
|
230
|
-
text: message.text,
|
|
231
|
-
createdAt: Date.parse(message.at) || Date.now(),
|
|
232
|
-
...(content && content.length > 0 ? { content } : {}),
|
|
233
|
-
};
|
|
234
|
-
}));
|
|
248
|
+
const hydrated = hydrateSessionThread(body.session.messages ?? []);
|
|
249
|
+
setMessages(hydrated.messages);
|
|
250
|
+
setToolCalls(hydrated.toolCalls);
|
|
235
251
|
setStreamingAssistantId(null);
|
|
236
252
|
return true;
|
|
237
253
|
}
|
|
@@ -240,7 +256,15 @@ export function useChatThread(params) {
|
|
|
240
256
|
// trigger another reconcile and retry (best-effort recovery).
|
|
241
257
|
return false;
|
|
242
258
|
}
|
|
243
|
-
}, [
|
|
259
|
+
}, [
|
|
260
|
+
origin,
|
|
261
|
+
refs,
|
|
262
|
+
setMessages,
|
|
263
|
+
setToolCalls,
|
|
264
|
+
setStreamingAssistantId,
|
|
265
|
+
setTurnPhase,
|
|
266
|
+
setDeferredTurn,
|
|
267
|
+
]);
|
|
244
268
|
return {
|
|
245
269
|
messages,
|
|
246
270
|
setMessages,
|
|
@@ -259,6 +283,10 @@ export function useChatThread(params) {
|
|
|
259
283
|
usage,
|
|
260
284
|
streaming,
|
|
261
285
|
setStreaming,
|
|
286
|
+
turnPhase,
|
|
287
|
+
setTurnPhase,
|
|
288
|
+
deferredTurn,
|
|
289
|
+
setDeferredTurn,
|
|
262
290
|
streamingAssistantId,
|
|
263
291
|
setStreamingAssistantId,
|
|
264
292
|
visibleMessages,
|
|
@@ -2,6 +2,7 @@ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } f
|
|
|
2
2
|
import { filterSlashCommands, mergeSlashCommands, } from "../../chat/shortcuts.js";
|
|
3
3
|
import { confirmationToken } from "./format.js";
|
|
4
4
|
import { clampShortcutIndex } from "./slash-palette-nav.js";
|
|
5
|
+
import { createInputHistoryState, navigateInputHistory, recordInputHistoryEntry, resetInputHistoryBrowsing, } from "./input-history.js";
|
|
5
6
|
import { attachRuntimeSnapshotController } from "./useRuntimeSnapshot.js";
|
|
6
7
|
/** Composer state: input text, @file palette, image attachments, /shortcuts,
|
|
7
8
|
* IME composition guards, auto-grow and server-side draft sync. */
|
|
@@ -31,6 +32,8 @@ export function useComposer(params) {
|
|
|
31
32
|
const [dynamicCommandsError, setDynamicCommandsError] = useState(null);
|
|
32
33
|
const [commandsEpoch, setCommandsEpoch] = useState(0);
|
|
33
34
|
const textareaRef = useRef(null);
|
|
35
|
+
/** Arrow-key input history; unsent drafts survive history browsing. */
|
|
36
|
+
const historyRef = useRef(createInputHistoryState());
|
|
34
37
|
/** Active session generation for /commands; bumped immediately on switch (AC-2). */
|
|
35
38
|
const commandsSessionRef = useRef(sessionId);
|
|
36
39
|
const commandsGenerationRef = useRef(0);
|
|
@@ -38,6 +41,33 @@ export function useComposer(params) {
|
|
|
38
41
|
setInput(value);
|
|
39
42
|
setShortcutOpen(value.startsWith("/"));
|
|
40
43
|
setShortcutIndex(0);
|
|
44
|
+
// Manual typing edits a recalled entry in place → back to present draft.
|
|
45
|
+
historyRef.current = resetInputHistoryBrowsing(historyRef.current);
|
|
46
|
+
}, []);
|
|
47
|
+
/** Recall an earlier input via ↑/↓; returns true when the key was consumed. */
|
|
48
|
+
const navigateHistory = useCallback((direction) => {
|
|
49
|
+
const textarea = textareaRef.current;
|
|
50
|
+
const decision = navigateInputHistory(historyRef.current, direction, {
|
|
51
|
+
text: input,
|
|
52
|
+
caretStart: textarea?.selectionStart ?? input.length,
|
|
53
|
+
caretEnd: textarea?.selectionEnd ?? input.length,
|
|
54
|
+
});
|
|
55
|
+
if (!decision.handled)
|
|
56
|
+
return false;
|
|
57
|
+
historyRef.current = decision.state;
|
|
58
|
+
setInput(decision.text);
|
|
59
|
+
const caret = decision.text.length;
|
|
60
|
+
queueMicrotask(() => {
|
|
61
|
+
const el = textareaRef.current;
|
|
62
|
+
if (el && el.value === decision.text) {
|
|
63
|
+
el.setSelectionRange(caret, caret);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
return true;
|
|
67
|
+
}, [input, setInput]);
|
|
68
|
+
/** Record a submitted text into history (consecutive duplicates collapse). */
|
|
69
|
+
const recordHistory = useCallback((text) => {
|
|
70
|
+
historyRef.current = recordInputHistoryEntry(historyRef.current, text);
|
|
41
71
|
}, []);
|
|
42
72
|
// Immediate invalidation on A→B even when B palette never opens (AC-2).
|
|
43
73
|
useLayoutEffect(() => {
|
|
@@ -234,6 +264,8 @@ export function useComposer(params) {
|
|
|
234
264
|
input,
|
|
235
265
|
setInput,
|
|
236
266
|
handleInputChange,
|
|
267
|
+
navigateHistory,
|
|
268
|
+
recordHistory,
|
|
237
269
|
fileOptions,
|
|
238
270
|
pendingImages,
|
|
239
271
|
setPendingImages,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useEffect, useState } from "react";
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
2
|
import { ACTIVE_CHAT_SESSION_STORAGE_KEY } from "../../chat/workspace-landing.js";
|
|
3
3
|
/** Left-rail repo tree + file preview tabs state. */
|
|
4
4
|
export function useRepoBrowser(params) {
|
|
@@ -9,6 +9,8 @@ export function useRepoBrowser(params) {
|
|
|
9
9
|
const [previews, setPreviews] = useState([]);
|
|
10
10
|
const [activePreviewPath, setActivePreviewPath] = useState(null);
|
|
11
11
|
const [previewLoading, setPreviewLoading] = useState(false);
|
|
12
|
+
const expandedDirsRef = useRef(new Set());
|
|
13
|
+
expandedDirsRef.current = expandedDirs;
|
|
12
14
|
const loadTree = useCallback(async (directory = "") => {
|
|
13
15
|
try {
|
|
14
16
|
setTreeError(null);
|
|
@@ -28,6 +30,28 @@ export function useRepoBrowser(params) {
|
|
|
28
30
|
useEffect(() => {
|
|
29
31
|
void loadTree();
|
|
30
32
|
}, [loadTree]);
|
|
33
|
+
/** pi-web parity: re-fetch the root + every expanded directory. Bump
|
|
34
|
+
* `refreshTick` to trigger; cached entries are NOT reused, so newly
|
|
35
|
+
* created/deleted files appear without a manual refresh. */
|
|
36
|
+
const [refreshTick, setRefreshTick] = useState(0);
|
|
37
|
+
const refreshTree = useCallback(() => {
|
|
38
|
+
setRefreshTick((tick) => tick + 1);
|
|
39
|
+
}, []);
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
const dirs = ["", ...expandedDirsRef.current];
|
|
42
|
+
for (const dir of dirs)
|
|
43
|
+
void loadTree(dir);
|
|
44
|
+
}, [loadTree, refreshTick]);
|
|
45
|
+
// Also refresh when the tab regains focus: external tools (editor, git,
|
|
46
|
+
// CLI) may have changed the tree while the console was hidden.
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
const onVisibility = () => {
|
|
49
|
+
if (!document.hidden)
|
|
50
|
+
refreshTree();
|
|
51
|
+
};
|
|
52
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
53
|
+
return () => document.removeEventListener("visibilitychange", onVisibility);
|
|
54
|
+
}, [refreshTree]);
|
|
31
55
|
const openPreview = useCallback(async (file) => {
|
|
32
56
|
setPreviewLoading(true);
|
|
33
57
|
try {
|
|
@@ -116,6 +140,7 @@ export function useRepoBrowser(params) {
|
|
|
116
140
|
previewLoading,
|
|
117
141
|
setPreviews,
|
|
118
142
|
loadTree,
|
|
143
|
+
refreshTree,
|
|
119
144
|
openPreview,
|
|
120
145
|
closePreview,
|
|
121
146
|
toggleDir,
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure decision core for the TasksPage run-id resolver (2026-08-23).
|
|
3
|
+
*
|
|
4
|
+
* Extracted from the React effect so the full lifecycle — wizard first run,
|
|
5
|
+
* rerun dispatch, task switch, foreign shared selection, baseline-gated
|
|
6
|
+
* lookup — is unit-testable as state transitions without mounting React.
|
|
7
|
+
*/
|
|
8
|
+
export function initialRunResolutionState() {
|
|
9
|
+
return { baseline: null, ownedRunId: "", pendingSelection: "" };
|
|
10
|
+
}
|
|
11
|
+
export function observeRunResolution(state, input) {
|
|
12
|
+
const firstObservation = state.task === undefined;
|
|
13
|
+
const taskChanged = !firstObservation && state.task !== input.task;
|
|
14
|
+
const dispatchChanged = !firstObservation && state.dispatch !== input.dispatchId;
|
|
15
|
+
const next = {
|
|
16
|
+
...state,
|
|
17
|
+
task: input.task,
|
|
18
|
+
dispatch: input.dispatchId,
|
|
19
|
+
pendingSelection: "",
|
|
20
|
+
};
|
|
21
|
+
if (taskChanged) {
|
|
22
|
+
next.baseline = null;
|
|
23
|
+
next.ownedRunId = "";
|
|
24
|
+
return { state: next, observation: { action: "reset-selection" } };
|
|
25
|
+
}
|
|
26
|
+
if (dispatchChanged) {
|
|
27
|
+
next.ownedRunId = "";
|
|
28
|
+
if (input.selectedRunId) {
|
|
29
|
+
next.baseline = input.selectedRunId;
|
|
30
|
+
return { state: next, observation: { action: "invalidate-selection" } };
|
|
31
|
+
}
|
|
32
|
+
// Nothing shown yet: keep the existing baseline and keep probing.
|
|
33
|
+
}
|
|
34
|
+
const selected = input.selectedRunId;
|
|
35
|
+
if (selected && selected !== next.ownedRunId) {
|
|
36
|
+
next.pendingSelection = selected;
|
|
37
|
+
return { state: next, observation: { action: "verify-ownership" } };
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
state: next,
|
|
41
|
+
observation: { action: selected ? "idle" : "probe" },
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export function markBaselineUnknown(state) {
|
|
45
|
+
return { ...state, baseline: null };
|
|
46
|
+
}
|
|
47
|
+
export function applyBaselineSnapshot(state, dagRunId) {
|
|
48
|
+
return { ...state, baseline: dagRunId ?? "" };
|
|
49
|
+
}
|
|
50
|
+
/** Accept an observeLink lookup: only a run of this task that differs from
|
|
51
|
+
* the pre-dispatch baseline may be adopted — never the previous run, and
|
|
52
|
+
* never anything while the baseline is still unknown. */
|
|
53
|
+
export function applyRunLookup(state, outcome) {
|
|
54
|
+
if (!outcome.ok || !outcome.dagRunId) {
|
|
55
|
+
return { state, acceptedRunId: null };
|
|
56
|
+
}
|
|
57
|
+
if (state.baseline === null || outcome.dagRunId === state.baseline) {
|
|
58
|
+
return { state, acceptedRunId: null };
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
state: { ...state, ownedRunId: outcome.dagRunId },
|
|
62
|
+
acceptedRunId: outcome.dagRunId,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/** Apply an authoritative ownership answer for the pending selection.
|
|
66
|
+
* `clearSelection` tells the adapter to drop the shared selection and
|
|
67
|
+
* snapshot a fresh baseline; owning marks it as resolver-owned. */
|
|
68
|
+
export function applyOwnership(state, owns) {
|
|
69
|
+
if (owns) {
|
|
70
|
+
return {
|
|
71
|
+
state: { ...state, ownedRunId: state.pendingSelection, pendingSelection: "" },
|
|
72
|
+
clearSelection: false,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
state: { ...state, ownedRunId: "", pendingSelection: "", baseline: null },
|
|
77
|
+
clearSelection: true,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ownership re-check backoff for an unverified shared DAG-run selection
|
|
3
|
+
* (2026-08-23): a transient candidates-lookup failure must neither leave
|
|
4
|
+
* another task's run displayed indefinitely (waiting for a state change
|
|
5
|
+
* that may never come) nor hammer the API.
|
|
6
|
+
*/
|
|
7
|
+
const OWNERSHIP_RETRY_DELAYS_MS = [
|
|
8
|
+
2_000, 2_000, 4_000, 4_000, 8_000, 8_000, 15_000, 15_000, 30_000, 30_000,
|
|
9
|
+
];
|
|
10
|
+
/** `owns`: true = same task, false = foreign task, null = check unavailable. */
|
|
11
|
+
export function planOwnershipVerification(owns, attempt) {
|
|
12
|
+
if (owns === true)
|
|
13
|
+
return { kind: "own" };
|
|
14
|
+
if (owns === false)
|
|
15
|
+
return { kind: "clear" };
|
|
16
|
+
const delayMs = OWNERSHIP_RETRY_DELAYS_MS[attempt];
|
|
17
|
+
return delayMs === undefined
|
|
18
|
+
? { kind: "give-up" }
|
|
19
|
+
: { kind: "retry", delayMs };
|
|
20
|
+
}
|
|
21
|
+
/** Total bounded re-verification window before giving up (keeps the last
|
|
22
|
+
* selection; the next state change restarts verification). */
|
|
23
|
+
export const OWNERSHIP_RETRY_WINDOW_MS = OWNERSHIP_RETRY_DELAYS_MS.reduce((total, delay) => total + delay, 0);
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure projection from a DAG run detail payload (GET /api/dag-runs/:id) into
|
|
3
|
+
* the structured run progress shown on the wizard RunPanel: node counts,
|
|
4
|
+
* current node + elapsed time, failure pointer, and stall signal. The panel
|
|
5
|
+
* used to show only a raw SSE log tail — node/phase information drowned in
|
|
6
|
+
* log noise during 20-40 minute runs.
|
|
7
|
+
*/
|
|
8
|
+
export function classifyRunProgressNodeStatus(raw) {
|
|
9
|
+
const status = (raw ?? "").trim().toLowerCase();
|
|
10
|
+
if (!status)
|
|
11
|
+
return "pending";
|
|
12
|
+
if (status.includes("running") || status === "in_progress" || status === "active") {
|
|
13
|
+
return "running";
|
|
14
|
+
}
|
|
15
|
+
if (status.includes("fail") || status.includes("error") || status === "blocked") {
|
|
16
|
+
return "failed";
|
|
17
|
+
}
|
|
18
|
+
if (status.includes("finish") ||
|
|
19
|
+
status.includes("success") ||
|
|
20
|
+
status === "done" ||
|
|
21
|
+
status === "ok") {
|
|
22
|
+
return "finished";
|
|
23
|
+
}
|
|
24
|
+
if (status.includes("skip"))
|
|
25
|
+
return "skipped";
|
|
26
|
+
return "pending";
|
|
27
|
+
}
|
|
28
|
+
function toProgressNode(node) {
|
|
29
|
+
const nodeId = String(node.nodeId ?? node.id ?? "").trim();
|
|
30
|
+
return {
|
|
31
|
+
nodeId,
|
|
32
|
+
label: String((node.label ?? node.name ?? nodeId) || "未命名节点"),
|
|
33
|
+
status: String(node.status ?? ""),
|
|
34
|
+
rank: typeof node.rank === "string" ? node.rank : undefined,
|
|
35
|
+
startedAt: typeof node.startedAt === "string" ? node.startedAt : undefined,
|
|
36
|
+
durationMs: typeof node.durationMs === "number" ? node.durationMs : undefined,
|
|
37
|
+
errorPreview: typeof node.errorPreview === "string" ? node.errorPreview : undefined,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function elapsedSince(iso, now) {
|
|
41
|
+
if (!iso)
|
|
42
|
+
return null;
|
|
43
|
+
const started = Date.parse(iso);
|
|
44
|
+
if (Number.isNaN(started))
|
|
45
|
+
return null;
|
|
46
|
+
const elapsed = now - started;
|
|
47
|
+
return elapsed >= 0 ? elapsed : null;
|
|
48
|
+
}
|
|
49
|
+
export function summarizeRunProgress(dag, now = Date.now()) {
|
|
50
|
+
if (!dag) {
|
|
51
|
+
return {
|
|
52
|
+
total: 0,
|
|
53
|
+
finished: 0,
|
|
54
|
+
running: null,
|
|
55
|
+
failed: null,
|
|
56
|
+
terminal: false,
|
|
57
|
+
effectiveStatus: null,
|
|
58
|
+
stalled: false,
|
|
59
|
+
currentNodeElapsedMs: null,
|
|
60
|
+
runElapsedMs: null,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
const rawNodes = Array.isArray(dag.nodes) ? dag.nodes : [];
|
|
64
|
+
const nodes = rawNodes.map(toProgressNode);
|
|
65
|
+
const classes = nodes.map((node) => classifyRunProgressNodeStatus(node.status));
|
|
66
|
+
const finished = classes.filter((cls) => cls === "finished" || cls === "skipped").length;
|
|
67
|
+
const runningIndex = classes.indexOf("running");
|
|
68
|
+
const running = runningIndex >= 0 ? nodes[runningIndex] : null;
|
|
69
|
+
const failedIndex = classes.indexOf("failed");
|
|
70
|
+
const failed = failedIndex >= 0 ? nodes[failedIndex] : null;
|
|
71
|
+
const effectiveStatus = String(dag.effectiveStatus ??
|
|
72
|
+
dag.status ??
|
|
73
|
+
"").toLowerCase();
|
|
74
|
+
const terminal = TERMINAL_RUN_PROGRESS_STATUSES.has(effectiveStatus);
|
|
75
|
+
const runStartedAt = typeof dag.startedAt === "string" ? dag.startedAt : undefined;
|
|
76
|
+
const runDurationMs = typeof dag.durationMs === "number" ? dag.durationMs : undefined;
|
|
77
|
+
return {
|
|
78
|
+
total: nodes.length,
|
|
79
|
+
finished,
|
|
80
|
+
running,
|
|
81
|
+
failed,
|
|
82
|
+
terminal,
|
|
83
|
+
effectiveStatus: effectiveStatus || null,
|
|
84
|
+
stalled: effectiveStatus === "running-suspected-stall",
|
|
85
|
+
currentNodeElapsedMs: running?.durationMs ?? elapsedSince(running?.startedAt, now),
|
|
86
|
+
runElapsedMs: terminal
|
|
87
|
+
? (runDurationMs ?? elapsedSince(runStartedAt, now))
|
|
88
|
+
: elapsedSince(runStartedAt, now),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const TERMINAL_RUN_PROGRESS_STATUSES = new Set([
|
|
92
|
+
"finished",
|
|
93
|
+
"failed",
|
|
94
|
+
"partial_failed",
|
|
95
|
+
"superseded",
|
|
96
|
+
"abandoned",
|
|
97
|
+
]);
|
|
98
|
+
export function formatDurationMs(ms) {
|
|
99
|
+
if (ms == null || !Number.isFinite(ms) || ms < 0)
|
|
100
|
+
return "—";
|
|
101
|
+
const totalSeconds = Math.floor(ms / 1000);
|
|
102
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
103
|
+
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
104
|
+
const seconds = totalSeconds % 60;
|
|
105
|
+
if (hours > 0)
|
|
106
|
+
return `${hours} 时 ${minutes} 分`;
|
|
107
|
+
if (minutes > 0)
|
|
108
|
+
return `${minutes} 分 ${seconds} 秒`;
|
|
109
|
+
return `${seconds} 秒`;
|
|
110
|
+
}
|
|
@@ -292,9 +292,13 @@ function assertControllerIdentityUnchangedBeforeSpawn(identity) {
|
|
|
292
292
|
}
|
|
293
293
|
}
|
|
294
294
|
function controllerIdentityDriftError(message, cause) {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
295
|
+
// Preserve the raw drift detail verbatim; the actionable suffix serves
|
|
296
|
+
// every consumer surface — operation errorMessage, SSE events, and agent/
|
|
297
|
+
// API callers that never reach the UI translation layer — with the exact
|
|
298
|
+
// recovery step: the running Console holds a stale controller fingerprint
|
|
299
|
+
// after rebuilds or commits touching fingerprint-scoped paths
|
|
300
|
+
// (package.json, bin/**, dist/**, skills/**).
|
|
301
|
+
return new Error(`controller identity changed before spawn: ${message}. Fix: restart the Console process so it re-resolves the current build, then retry the operation; avoid rebuilding or committing fingerprint-scoped files (package.json, bin/**, dist/**, skills/**) mid-session.`, { cause });
|
|
298
302
|
}
|
|
299
303
|
/**
|
|
300
304
|
* Detect whether loopAgentBin is a Node-like executable and baseArgs
|
|
@@ -129,11 +129,14 @@ export async function resolveRevisionDigestsForTaskSpec(input) {
|
|
|
129
129
|
};
|
|
130
130
|
}
|
|
131
131
|
/** Union TaskSpec allowed_paths with workflow-owned write roots. */
|
|
132
|
-
export function expandAllowedPathsForWorkflow(workflow, allowedPaths) {
|
|
132
|
+
export function expandAllowedPathsForWorkflow(workflow, allowedPaths, options) {
|
|
133
133
|
const extras = workflow === "backend-test"
|
|
134
134
|
? ["testcase/**", "docs/test-reports/**"]
|
|
135
135
|
: workflow === "frontend-test"
|
|
136
|
-
? [
|
|
136
|
+
? [
|
|
137
|
+
`${options?.frontendTestRoot ?? "testcase/frontend"}/**`,
|
|
138
|
+
"docs/test-reports/**",
|
|
139
|
+
]
|
|
137
140
|
: [];
|
|
138
141
|
return Array.from(new Set([...allowedPaths, ...extras]));
|
|
139
142
|
}
|
|
@@ -1483,6 +1483,9 @@ function mergeDagRun(existing, incoming) {
|
|
|
1483
1483
|
computeDurationMs(startedAt, finishedAt);
|
|
1484
1484
|
return {
|
|
1485
1485
|
dagRunId: incoming.dagRunId,
|
|
1486
|
+
...((incoming.taskId ?? existing.taskId)
|
|
1487
|
+
? { taskId: incoming.taskId ?? existing.taskId }
|
|
1488
|
+
: {}),
|
|
1486
1489
|
status: incoming.status ?? existing.status,
|
|
1487
1490
|
lifecycle: incoming.lifecycle ?? existing.lifecycle,
|
|
1488
1491
|
executionMode: incoming.executionMode ?? existing.executionMode,
|
|
@@ -1914,8 +1917,10 @@ async function parseDagStateFile(statePath, now) {
|
|
|
1914
1917
|
}
|
|
1915
1918
|
: undefined;
|
|
1916
1919
|
const failureCategory = readString(parsed, "failureCategory");
|
|
1920
|
+
const boundTaskId = await readRunSpecTaskBinding(runSpecPath);
|
|
1917
1921
|
return {
|
|
1918
1922
|
dagRunId,
|
|
1923
|
+
...(boundTaskId ? { taskId: boundTaskId } : {}),
|
|
1919
1924
|
status,
|
|
1920
1925
|
...(lifecycle ? { lifecycle } : {}),
|
|
1921
1926
|
...(executionMode ? { executionMode } : {}),
|
|
@@ -2435,6 +2440,23 @@ async function safeReadJson(filePath) {
|
|
|
2435
2440
|
return undefined;
|
|
2436
2441
|
return parsed;
|
|
2437
2442
|
}
|
|
2443
|
+
/** Task binding for a DAG run from run.json — same precedence as
|
|
2444
|
+
* console/resolve-dag-run-for-task (taskContractBinding → sourceBinding →
|
|
2445
|
+
* Hybrid DAG title). */
|
|
2446
|
+
async function readRunSpecTaskBinding(runSpecPath) {
|
|
2447
|
+
const parsed = await safeReadJson(runSpecPath);
|
|
2448
|
+
if (!parsed)
|
|
2449
|
+
return undefined;
|
|
2450
|
+
const binding = parsed.taskContractBinding;
|
|
2451
|
+
const source = parsed.sourceBinding;
|
|
2452
|
+
const fromBinding = (typeof binding?.taskId === "string" && binding.taskId.trim()) ||
|
|
2453
|
+
(typeof source?.taskId === "string" && source.taskId.trim());
|
|
2454
|
+
if (fromBinding)
|
|
2455
|
+
return fromBinding;
|
|
2456
|
+
const title = typeof parsed.title === "string" ? parsed.title.trim() : "";
|
|
2457
|
+
const hybrid = /^Hybrid DAG:\s*(.+)$/i.exec(title);
|
|
2458
|
+
return hybrid?.[1]?.trim() || undefined;
|
|
2459
|
+
}
|
|
2438
2460
|
function safeParseJson(raw) {
|
|
2439
2461
|
try {
|
|
2440
2462
|
const parsed = JSON.parse(raw);
|