@tea-agent/loop-agent 0.42.0-next.1 → 0.42.0-next.10
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 +1 -1
- package/CHANGELOG.md +319 -1
- package/dist/application/dag/generate-task-dag.js +75 -19
- package/dist/application/dag/run-dag.js +41 -0
- package/dist/application/task-lifecycle/advance.js +24 -5
- package/dist/application/task-lifecycle/observe.js +171 -17
- package/dist/application/task-lifecycle/plan-transitions.js +42 -7
- package/dist/application/task-lifecycle/recommendations.js +13 -2
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +7 -0
- package/dist/cli/program.js +6 -1
- package/dist/commands/client-recovery.js +3 -0
- package/dist/commands/dag-follow-up.js +138 -0
- package/dist/commands/dag-rerun-task.js +2 -0
- package/dist/commands/init.js +27 -1
- package/dist/commands/task-advance.js +19 -0
- package/dist/executors/dag-pi-executor.js +3976 -76
- package/dist/executors/pi-executor.js +15 -4
- package/dist/executors/pi-read-budget-policy.js +239 -0
- package/dist/executors/shell-executor.js +978 -188
- package/dist/executors/shell-write-guard.js +7 -0
- package/dist/infrastructure/console/operation-store.js +226 -6
- package/dist/shared/dag-failure-category.js +12 -0
- package/dist/shared/openspec-spec.js +70 -4
- package/dist/shared/operator/capabilities.js +7 -0
- package/dist/task/config-types.js +105 -5
- package/dist/task/contract/adopt.js +4 -0
- package/dist/task/contract/import-revision.js +4 -0
- package/dist/task/contract/project.js +3 -0
- package/dist/task/contract/schema.js +2 -1
- package/dist/task/frontend-project-capability.js +203 -20
- package/dist/task/runtime.js +5 -2
- package/dist/task/source-prepare/build-draft.js +3 -3
- package/dist/task/source-prepare/fragment-inventory.js +64 -25
- package/dist/task/source-prepare/prepare.js +126 -21
- package/dist/task/source-prepare/semantic-intake.js +6 -2
- package/dist/task/source-references.js +22 -1
- package/dist/worker/console/chat/chat-ui-policy.js +4 -2
- package/dist/worker/console/chat/model-resolver.js +7 -4
- package/dist/worker/console/chat/pi-runtime.js +258 -11
- package/dist/worker/console/chat/repo-browser.js +10 -3
- package/dist/worker/console/chat/routes.js +165 -71
- package/dist/worker/console/chat/sdd-data-alignment.js +222 -0
- package/dist/worker/console/chat/session-catalog.js +2 -0
- package/dist/worker/console/chat/session-store.js +159 -70
- package/dist/worker/console/chat/shortcuts.js +19 -3
- package/dist/worker/console/chat/turn-process.js +111 -56
- package/dist/worker/console/frontend-human-decision-adapter.js +19 -0
- package/dist/worker/console/frontend-split-operation-adapter.js +20 -0
- package/dist/worker/console/index.js +3 -0
- package/dist/worker/console/operator-actions.js +169 -7
- package/dist/worker/console/pi-readiness.js +3 -2
- package/dist/worker/console/server.js +23 -1
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-DZZ8m3PO.js → abnfDiagram-N423BO3Z-CcS17TBr.js} +1 -1
- package/dist/worker/console/static/assets/{arc-D6PvaVd-.js → arc-COptKq2S.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-B_OTOiI8.js → architectureDiagram-T3A2C74G-h4LKMHjP.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-Bv6rqHBg.js → blockDiagram-VBNYF7ZC-COA1MOH0.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-B8eHr0oz.js → c4Diagram-5PPSVZJV-DJUf0QPm.js} +1 -1
- package/dist/worker/console/static/assets/channel-DdBCaOJ6.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-DYwR0im2.js → chunk-2GRJ4B5K-mtWfKrUX.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-D2WPGqXt.js → chunk-2Q5K7J3B-B8pXsxDQ.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-CQISJ_I7.js → chunk-5RXB4S5H-ipKzByl1.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-C0o2Du1e.js → chunk-5VM5RSS4-DYi3Ald_.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-4f8kr-U7.js → chunk-6Q2QTUOP-DQtGYoty.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-D_OWvXzX.js → chunk-GF5L2VYU-BU0qS2YV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-CasdPz5X.js → chunk-JWPE2WC7-DVH9dIGE.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-Cj8lRrla.js → chunk-KBJHAD2P-Dt60SmgM.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CUvD1FSd.js → chunk-RYQCIY6F-BoaUGuEY.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CqLw_eqB.js → chunk-XXDRQBXY-_WHFDTp2.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DZFra1GO.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DZFra1GO.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-ku-WqJPx.js → cose-bilkent-JH36ORCC-BxkbTIRd.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-Bq_PYDhl.js → cynefin-VYW2F7L2-Bf2UVnoG.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-BiGZV641.js → cynefinDiagram-MW4NZA55-Bo23q1J_.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-C4JRlglF.js → dagre-VZM6K2ZE-Dd_UU49i.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-B0dNeWYG.js → diagram-7IWD3JNH-ebUa1a9y.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-Cj35YvYM.js → diagram-B4RE2ZJO-BucthU8r.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-uzQoJ2-8.js → diagram-LBJQPF4R-BgqNpAkm.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-D1a-Buoz.js → diagram-Q27KOJAE-DC4q5NGa.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-XjRrLRSs.js → diagram-UB23O5K3-CBqCaMeb.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Da_O24cW.js → ebnfDiagram-BXEA7PRR-CGtnbQ3-.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-BLJ8jrYU.js → erDiagram-JOGREHBK-CG3LUao5.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-B9GrLjM0.js → flowDiagram-UKHOOZJN-D2ZVgoFS.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CeJ0TqiK.js → ganttDiagram-PKOTCBZU-DoYFZiKt.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Bm2eNNFX.js → gitGraphDiagram-DS77QQ5N-CKoP1s6j.js} +1 -1
- package/dist/worker/console/static/assets/index-CAZ2fC_X.css +1 -0
- package/dist/worker/console/static/assets/index-vbTcFnFs.js +449 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DB26i3d8.js → infoDiagram-6WML65LV-Duofv8p2.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-BOGRyeZT.js → ishikawaDiagram-WSZJBQD7-D2nlCkA1.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-CZRoBO_6.js → journeyDiagram-NVQOT4AX-Dd4IHdus.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-qlWhJyoB.js → kanban-definition-27J2QSJJ-Bi20AOdt.js} +1 -1
- package/dist/worker/console/static/assets/{linear-CfUiDDB3.js → linear-D59YJ9kB.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-BQe6fpqj.js → mermaid.core-Bl13LpNa.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-zFzWHw64.js → mindmap-definition-FAOFIHXS-C6DLP5eY.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DFGUqjZO.js → pegDiagram-VL7TDLO6-BjO4cy64.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-BB5i0l8Z.js → pieDiagram-7S7Q4E2Y-D7i2qZTG.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-obbVZ_X5.js → quadrantDiagram-CIZ2JOQS-Dx8o_h0y.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BwdaNzc1.js → railroadDiagram-AXF67PYL-B55orI_t.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-CkTJaugh.js → requirementDiagram-LRYGKXZP-zWaehp6f.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-BwJ-hIgq.js → sankeyDiagram-W5VNT64P-CcA-pjvD.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BzSgmm7w.js → sequenceDiagram-SI44F4Z6-BOFbzNHI.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-Bn3oj5Q1.js → sizeCapture-X5ZJPWSS-BjAejah1.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-dxhGtL8K.js → stateDiagram-OKZ733FA-DXYwxJwZ.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Clg3V9t1.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-BLpEInya.js → swimlanes-SLNWSIFB-OYiOch8n.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DX1dxAAW.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-ChJGYcXy.js → timeline-definition-Z64GVDOM-cZfH3nmU.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DK-qjRer.js → vennDiagram-T6HMQDX7-BDE7b3E1.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-3qeaWAg-.js → wardleyDiagram-T6FBY63Y-kyyGy9WJ.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CootlyP9.js → xychartDiagram-ELKLHX3M-CJj6VTog.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/active-run-badge.js +17 -0
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +5 -5
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +19 -8
- package/dist/worker/console/static-src/operator-chat/input-history.js +8 -6
- package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +15 -7
- package/dist/worker/console/static-src/operator-chat/runtime-snapshot-store.js +10 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +258 -23
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +112 -4
- package/dist/worker/console/static-src/operator-chat/useComposer.js +13 -4
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +29 -6
- package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +8 -2
- package/dist/worker/console/static-src/shell/console-update-reload.js +36 -9
- package/dist/worker/console/static-src/shell/workspace-route.js +11 -0
- package/dist/worker/console/workspace-context.js +115 -1
- package/dist/worker/materialize/frontend-split-task-materializer.js +72 -0
- package/dist/worker/observe/routes.js +4 -0
- package/dist/worker/observe/static/dag-helpers.js +4 -2
- package/dist/worker/observe/static/dag-node-purpose.js +5 -0
- package/dist/worker/observe/static/inspect-workspace.js +23 -0
- package/dist/worker/observe/static/kpi.js +2 -2
- package/dist/worker/observe/static/operator-chrome.d.ts +10 -2
- package/dist/worker/observe/static/operator-chrome.js +37 -23
- package/dist/worker/observe/static/relations.js +2 -2
- package/dist/worker/observe/static/router.d.ts +12 -1
- package/dist/worker/observe/static/router.js +48 -6
- package/dist/worker/observe/static/run-processing.js +4 -2
- package/dist/worker/observe/static/shell-chrome.js +2 -2
- package/dist/worker/observe/static/state.js +5 -0
- package/dist/worker/observe/static/styles.css +95 -0
- package/dist/worker/observe/static/views/dag-inspector.js +14 -7
- package/dist/worker/observe/static/views/dag.js +10 -2
- package/dist/worker/observe/static/views/dags.js +3 -1
- package/dist/worker/observe/static/views/dashboard.js +14 -6
- package/dist/worker/observe/static/views/pool.js +7 -2
- package/dist/worker/observe/static/views/run.js +12 -3
- package/dist/worker/observe/static/views/session-timeline.js +69 -10
- package/dist/worker/observe/static/views/task.js +7 -2
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +215 -28
- package/dist/workflows/dag/backend-test-markdown-workflow.js +13 -1
- package/dist/workflows/dag/backend-test-plan-protocol.js +106 -0
- package/dist/workflows/dag/backend-test-scenario-param.js +172 -25
- package/dist/workflows/dag/backend-test-scenario-partitions.js +62 -1
- package/dist/workflows/dag/backend-test-writer-completeness.js +104 -12
- package/dist/workflows/dag/contract-validator-registrations.js +1 -2
- package/dist/workflows/dag/dag-retry-schema.js +138 -0
- package/dist/workflows/dag/frontend-closeout.js +221 -0
- package/dist/workflows/dag/frontend-design-policy.js +400 -0
- package/dist/workflows/dag/frontend-human-decision.js +182 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +1237 -192
- package/dist/workflows/dag/frontend-plan-render.js +2 -1
- package/dist/workflows/dag/frontend-prewrite-gate.js +256 -350
- package/dist/workflows/dag/frontend-provider-capability-matrix.js +159 -0
- package/dist/workflows/dag/frontend-recovery-capsule.js +455 -0
- package/dist/workflows/dag/frontend-recovery-controller.js +226 -0
- package/dist/workflows/dag/frontend-recovery-lineage.js +178 -0
- package/dist/workflows/dag/frontend-recovery-plan.js +21 -10
- package/dist/workflows/dag/frontend-recovery-run.js +166 -34
- package/dist/workflows/dag/frontend-repair.js +1 -432
- package/dist/workflows/dag/frontend-review-context.js +261 -15
- package/dist/workflows/dag/frontend-review-findings.js +270 -0
- package/dist/workflows/dag/frontend-shadow-dual-write.js +941 -0
- package/dist/workflows/dag/frontend-shape-capsule-store.js +191 -0
- package/dist/workflows/dag/frontend-shape-facts.js +419 -0
- package/dist/workflows/dag/frontend-shape.js +435 -0
- package/dist/workflows/dag/frontend-source-fidelity-ledger.js +108 -0
- package/dist/workflows/dag/frontend-split-application-service.js +203 -0
- package/dist/workflows/dag/frontend-split-orchestrator.js +899 -0
- package/dist/workflows/dag/frontend-typed-event-store.js +452 -0
- package/dist/workflows/dag/frontend-typed-event-transaction.js +180 -0
- package/dist/workflows/dag/frontend-verification-trace.js +252 -25
- package/dist/workflows/dag/frontend-worktree-diff.js +250 -17
- package/dist/workflows/dag/frontend-writer-admission.js +319 -0
- package/dist/workflows/dag/frontend-writer-status.js +256 -0
- package/dist/workflows/dag/init-hybrid.js +1028 -562
- package/dist/workflows/dag/interrupt-request.js +7 -0
- package/dist/workflows/dag/node-execution.js +828 -4
- package/dist/workflows/dag/prompt.js +70 -3
- package/dist/workflows/dag/recovery-lease.js +170 -0
- package/dist/workflows/dag/report.js +37 -1
- package/dist/workflows/dag/rerun-feedback.js +315 -1
- package/dist/workflows/dag/rerun-plan.js +17 -6
- package/dist/workflows/dag/rerun-run.js +44 -6
- package/dist/workflows/dag/rerun-task.js +251 -13
- package/dist/workflows/dag/retry-policy.js +219 -104
- package/dist/workflows/dag/runner.js +580 -124
- package/dist/workflows/dag/scheduler.js +133 -20
- package/dist/workflows/dag/types.js +244 -16
- package/dist/workflows/dag/validate.js +28 -12
- package/docs/examples/README.md +5 -0
- package/docs/init-surface.manifest.json +30 -12
- package/docs/skills/vetted-skill-registry.md +4 -2
- package/docs/templates/README.md +2 -0
- package/docs/templates/agent-dag-report.schema.json +8 -2
- package/docs/templates/agent-dag.schema.json +1 -1
- package/docs/templates/backend-test-dag.json +23 -20
- package/docs/templates/frontend-implementation-contract.schema.json +4 -1
- package/docs/templates/frontend-implementation-dag.json +89 -0
- package/docs/templates/spec-registry.schema.json +45 -0
- package/harness.json +1 -1
- package/package.json +4 -3
- package/skills/frontend-bounded-implement/SKILL.md +15 -14
- package/skills/frontend-bounded-implement/references/code-standards.md +19 -0
- package/skills/frontend-contract/SKILL.md +23 -0
- package/skills/frontend-contract/references/contract-protocol.md +34 -0
- package/skills/frontend-design-review/SKILL.md +22 -41
- package/skills/frontend-plan/SKILL.md +26 -0
- package/skills/frontend-plan/references/decision-contract.md +37 -0
- package/skills/frontend-plan/references/design-decisions.md +17 -0
- package/skills/frontend-review/SKILL.md +20 -15
- package/skills/frontend-review/references/review-findings.md +6 -7
- package/skills/frontend-scout/SKILL.md +25 -0
- package/skills/frontend-scout/references/design-evidence.md +16 -0
- package/skills/frontend-scout/references/scout-evidence.md +23 -0
- package/skills/frontend-verification/SKILL.md +1 -1
- package/skills/loop-agent/references/command-reference.md +1 -0
- package/skills/loop-agent/references/hybrid-dag.md +2 -2
- package/dist/worker/console/static/assets/channel-BU5gOilw.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DIzKJGHr.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DIzKJGHr.js +0 -1
- package/dist/worker/console/static/assets/index-H9rFJiGL.css +0 -1
- package/dist/worker/console/static/assets/index-xwu9GxEc.js +0 -451
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Cld2qK9v.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-TIHpiT7w.js +0 -8
- package/skills/frontend-implementation/SKILL.md +0 -52
- package/skills/frontend-implementation/references/code-standards.md +0 -33
- package/skills/frontend-implementation/references/design-spec.md +0 -56
- package/skills/frontend-implementation/references/node-contracts.md +0 -31
|
@@ -7,7 +7,7 @@ import { attachRuntimeSnapshotController } from "./useRuntimeSnapshot.js";
|
|
|
7
7
|
/** Composer state: input text, @file palette, image attachments, /shortcuts,
|
|
8
8
|
* IME composition guards, auto-grow and server-side draft sync. */
|
|
9
9
|
export function useComposer(params) {
|
|
10
|
-
const { origin, sessionId, refs, setError } = params;
|
|
10
|
+
const { origin, sessionId, refs, setError, onUnrecoverableSession } = params;
|
|
11
11
|
const [input, setInputState] = useState("");
|
|
12
12
|
const [fileOptions, setFileOptions] = useState([]);
|
|
13
13
|
const [pendingImages, setPendingImages] = useState([]);
|
|
@@ -28,6 +28,7 @@ export function useComposer(params) {
|
|
|
28
28
|
}, []);
|
|
29
29
|
const [shortcutIndex, setShortcutIndex] = useState(0);
|
|
30
30
|
const [dynamicCommands, setDynamicCommands] = useState([]);
|
|
31
|
+
const [commandCapabilities, setCommandCapabilities] = useState({ sddBingo: false });
|
|
31
32
|
const [dynamicCommandsLoading, setDynamicCommandsLoading] = useState(false);
|
|
32
33
|
const [dynamicCommandsError, setDynamicCommandsError] = useState(null);
|
|
33
34
|
const [commandsEpoch, setCommandsEpoch] = useState(0);
|
|
@@ -74,6 +75,7 @@ export function useComposer(params) {
|
|
|
74
75
|
commandsSessionRef.current = sessionId;
|
|
75
76
|
commandsGenerationRef.current += 1;
|
|
76
77
|
setDynamicCommands([]);
|
|
78
|
+
setCommandCapabilities({ sddBingo: false });
|
|
77
79
|
setDynamicCommandsError(null);
|
|
78
80
|
setDynamicCommandsLoading(false);
|
|
79
81
|
}, [sessionId]);
|
|
@@ -122,6 +124,7 @@ export function useComposer(params) {
|
|
|
122
124
|
if (!isCurrent())
|
|
123
125
|
return;
|
|
124
126
|
setDynamicCommands(items);
|
|
127
|
+
setCommandCapabilities({ sddBingo: body?.capabilities?.sddBingo === true });
|
|
125
128
|
// Compact server fail-soft / partial-source warning; local Operator/Web still merge.
|
|
126
129
|
const warning = typeof body?.warning === "string" ? body.warning.trim() : "";
|
|
127
130
|
setDynamicCommandsError(warning ? warning.slice(0, 160) : null);
|
|
@@ -131,6 +134,7 @@ export function useComposer(params) {
|
|
|
131
134
|
return;
|
|
132
135
|
setDynamicCommandsError("动态命令加载失败");
|
|
133
136
|
setDynamicCommands([]);
|
|
137
|
+
setCommandCapabilities({ sddBingo: false });
|
|
134
138
|
}
|
|
135
139
|
finally {
|
|
136
140
|
if (isCurrent()) {
|
|
@@ -227,7 +231,7 @@ export function useComposer(params) {
|
|
|
227
231
|
}, 400);
|
|
228
232
|
return () => window.clearTimeout(timer);
|
|
229
233
|
}, [origin, sessionId, input]);
|
|
230
|
-
const mergedCommands = useMemo(() => mergeSlashCommands(dynamicCommands), [dynamicCommands]);
|
|
234
|
+
const mergedCommands = useMemo(() => mergeSlashCommands(dynamicCommands, commandCapabilities), [dynamicCommands, commandCapabilities]);
|
|
231
235
|
const filteredShortcuts = useMemo(() => input.startsWith("/")
|
|
232
236
|
? filterSlashCommands(mergedCommands, input)
|
|
233
237
|
: [], [input, mergedCommands]);
|
|
@@ -258,8 +262,13 @@ export function useComposer(params) {
|
|
|
258
262
|
// origin + sessionId + refs in a writable file; the snapshot store is
|
|
259
263
|
// consumed by ChatComposer / panels / runtime-context popover).
|
|
260
264
|
useEffect(() => {
|
|
261
|
-
attachRuntimeSnapshotController({
|
|
262
|
-
|
|
265
|
+
attachRuntimeSnapshotController({
|
|
266
|
+
origin,
|
|
267
|
+
sessionId,
|
|
268
|
+
refs,
|
|
269
|
+
onUnrecoverableSession,
|
|
270
|
+
});
|
|
271
|
+
}, [origin, sessionId, refs, onUnrecoverableSession]);
|
|
263
272
|
return {
|
|
264
273
|
input,
|
|
265
274
|
setInput,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import { formatModelRef, mergeSessionRuntime, sessionRuntimeMatches, } from "../../chat/runtime-selection.js";
|
|
3
3
|
import { confirmationToken } from "./format.js";
|
|
4
|
-
import { mergeRuntimeContextWithSnapshot, runtimeSelectionFromSnapshot, } from "./runtime-snapshot-store.js";
|
|
4
|
+
import { isLostChatSessionError, mergeRuntimeContextWithSnapshot, runtimeSelectionFromSnapshot, } from "./runtime-snapshot-store.js";
|
|
5
5
|
import { buildToolCatalog } from "./tools-catalog.js";
|
|
6
6
|
import { adoptMutationSnapshot, dispatchRuntimeSnapshot, getRuntimeSnapshotState, refreshRuntimeSnapshot, settleMutationLifecycle, useRuntimeSnapshot, } from "./useRuntimeSnapshot.js";
|
|
7
7
|
/**
|
|
@@ -14,7 +14,7 @@ let mutationSeq = 0;
|
|
|
14
14
|
/** Capabilities catalog, model / thinking-level switcher, runtime-context
|
|
15
15
|
* popover and the outside-click dismissal for both transient menus. */
|
|
16
16
|
export function useRuntimeControls(params) {
|
|
17
|
-
const { origin, session, setSession, refs, setError } = params;
|
|
17
|
+
const { origin, session, setSession, refs, setError, onUnrecoverableSession } = params;
|
|
18
18
|
const [capabilities, setCapabilities] = useState(null);
|
|
19
19
|
const [models, setModels] = useState([]);
|
|
20
20
|
const [modelsStatus, setModelsStatus] = useState("idle");
|
|
@@ -84,9 +84,20 @@ export function useRuntimeControls(params) {
|
|
|
84
84
|
if (refs.sessionRef.current?.sessionId !== sessionId)
|
|
85
85
|
return;
|
|
86
86
|
if (!res.ok || !body) {
|
|
87
|
+
const error = body
|
|
88
|
+
?.error;
|
|
89
|
+
if (isLostChatSessionError({
|
|
90
|
+
status: res.status,
|
|
91
|
+
code: error?.code,
|
|
92
|
+
message: error?.message,
|
|
93
|
+
})) {
|
|
94
|
+
setModelsStatus("idle");
|
|
95
|
+
setModelsError(null);
|
|
96
|
+
onUnrecoverableSession?.(sessionId);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
87
99
|
setModelsStatus("error");
|
|
88
|
-
setModelsError(
|
|
89
|
-
?.error?.message ?? `模型列表读取失败(HTTP ${res.status})`);
|
|
100
|
+
setModelsError(error?.message ?? `模型列表读取失败(HTTP ${res.status})`);
|
|
90
101
|
return;
|
|
91
102
|
}
|
|
92
103
|
setModels(body.models ?? []);
|
|
@@ -99,7 +110,7 @@ export function useRuntimeControls(params) {
|
|
|
99
110
|
setModelsStatus("error");
|
|
100
111
|
setModelsError(reason instanceof Error ? reason.message : String(reason));
|
|
101
112
|
}
|
|
102
|
-
}, [origin, session, refs]);
|
|
113
|
+
}, [origin, session, refs, onUnrecoverableSession]);
|
|
103
114
|
useEffect(() => {
|
|
104
115
|
void loadModels();
|
|
105
116
|
}, [loadModels]);
|
|
@@ -126,6 +137,18 @@ export function useRuntimeControls(params) {
|
|
|
126
137
|
if (refs.sessionRef.current?.sessionId !== sessionId)
|
|
127
138
|
return;
|
|
128
139
|
if (!body?.context) {
|
|
140
|
+
if (!r.ok &&
|
|
141
|
+
isLostChatSessionError({
|
|
142
|
+
status: r.status,
|
|
143
|
+
code: body
|
|
144
|
+
?.error?.code,
|
|
145
|
+
message: body
|
|
146
|
+
?.error?.message,
|
|
147
|
+
})) {
|
|
148
|
+
setRuntimeContextLoading(false);
|
|
149
|
+
onUnrecoverableSession?.(sessionId);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
129
152
|
if (!r.ok)
|
|
130
153
|
setRuntimeContextError("注入上下文读取失败,请重试");
|
|
131
154
|
return;
|
|
@@ -158,7 +181,7 @@ export function useRuntimeControls(params) {
|
|
|
158
181
|
return;
|
|
159
182
|
setRuntimeContextError("注入上下文读取失败,请重试");
|
|
160
183
|
});
|
|
161
|
-
}, [origin, session?.sessionId, refs]);
|
|
184
|
+
}, [origin, session?.sessionId, refs, onUnrecoverableSession]);
|
|
162
185
|
// AC-01/RF3-01: hydrate the Composer from the unified snapshot — real
|
|
163
186
|
// model and thinking selection; `auto` displays the Pi actual effective
|
|
164
187
|
// level. Derived session/runtimeContext state ONLY comes from a snapshot
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { useSyncExternalStore } from "react";
|
|
13
13
|
import { confirmationToken } from "./format.js";
|
|
14
|
-
import { createRuntimeSnapshotStore, parseRuntimeSnapshotError, } from "./runtime-snapshot-store.js";
|
|
14
|
+
import { createRuntimeSnapshotStore, isLostChatSessionError, parseRuntimeSnapshotError, } from "./runtime-snapshot-store.js";
|
|
15
15
|
const store = createRuntimeSnapshotStore();
|
|
16
16
|
let controller;
|
|
17
17
|
function snapshotUrl(sessionId) {
|
|
@@ -49,10 +49,16 @@ async function fetchSnapshot(sessionId, origin) {
|
|
|
49
49
|
if (controller?.sessionId !== sessionId)
|
|
50
50
|
return; // stale session reply
|
|
51
51
|
if (!res.ok) {
|
|
52
|
+
const error = { ...parseRuntimeSnapshotError(body), status: res.status };
|
|
53
|
+
if (isLostChatSessionError(error)) {
|
|
54
|
+
store.dispatch({ type: "reset" });
|
|
55
|
+
controller.onUnrecoverableSession?.(sessionId);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
52
58
|
store.dispatch({
|
|
53
59
|
type: "load-failure",
|
|
54
60
|
sessionId,
|
|
55
|
-
error
|
|
61
|
+
error,
|
|
56
62
|
});
|
|
57
63
|
return;
|
|
58
64
|
}
|
|
@@ -1,19 +1,46 @@
|
|
|
1
|
+
export function isReplacementConsoleHealth(health, expectedConsoleInstanceId, previousConsoleInstanceId) {
|
|
2
|
+
if (!health || typeof health !== "object")
|
|
3
|
+
return false;
|
|
4
|
+
const instanceId = typeof health.consoleInstanceId ===
|
|
5
|
+
"string"
|
|
6
|
+
? health.consoleInstanceId.trim()
|
|
7
|
+
: "";
|
|
8
|
+
if (!instanceId)
|
|
9
|
+
return false;
|
|
10
|
+
const expected = expectedConsoleInstanceId?.trim();
|
|
11
|
+
if (expected)
|
|
12
|
+
return instanceId === expected;
|
|
13
|
+
return previousConsoleInstanceId !== null && instanceId !== previousConsoleInstanceId;
|
|
14
|
+
}
|
|
1
15
|
/**
|
|
2
|
-
* Poll /api/health until
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* port and reads as "the page is gone".
|
|
16
|
+
* Poll /api/health until the replacement Console answers, then reload. A
|
|
17
|
+
* handoff deliberately leaves the old process alive briefly, so HTTP 200 alone
|
|
18
|
+
* is insufficient: the response must carry the new instance identity.
|
|
6
19
|
*/
|
|
7
|
-
export async function reloadWhenConsoleReady(
|
|
20
|
+
export async function reloadWhenConsoleReady(options = {}) {
|
|
21
|
+
const timeoutMs = options.timeoutMs ?? 30_000;
|
|
22
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
23
|
+
const reload = options.reload ?? (() => window.location.reload());
|
|
8
24
|
const started = Date.now();
|
|
25
|
+
let previousConsoleInstanceId = null;
|
|
9
26
|
while (Date.now() - started < timeoutMs) {
|
|
10
27
|
try {
|
|
11
|
-
const res = await
|
|
28
|
+
const res = await fetchImpl(`/api/health?ts=${Date.now()}`, {
|
|
12
29
|
cache: "no-store",
|
|
13
30
|
});
|
|
14
31
|
if (res.ok) {
|
|
15
|
-
|
|
16
|
-
|
|
32
|
+
const health = await res.json().catch(() => null);
|
|
33
|
+
if (isReplacementConsoleHealth(health, options.expectedConsoleInstanceId, previousConsoleInstanceId)) {
|
|
34
|
+
reload();
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
const healthRecord = health;
|
|
38
|
+
const instanceId = typeof healthRecord?.consoleInstanceId === "string"
|
|
39
|
+
? healthRecord.consoleInstanceId.trim()
|
|
40
|
+
: "";
|
|
41
|
+
if (instanceId && previousConsoleInstanceId === null) {
|
|
42
|
+
previousConsoleInstanceId = instanceId;
|
|
43
|
+
}
|
|
17
44
|
}
|
|
18
45
|
}
|
|
19
46
|
catch {
|
|
@@ -21,5 +48,5 @@ export async function reloadWhenConsoleReady(timeoutMs = 30_000) {
|
|
|
21
48
|
}
|
|
22
49
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
23
50
|
}
|
|
24
|
-
|
|
51
|
+
return false;
|
|
25
52
|
}
|
|
@@ -170,6 +170,17 @@ export function withWorkspaceId(href, workspaceId) {
|
|
|
170
170
|
const qs = query.toString();
|
|
171
171
|
return `/#${path}${qs ? `?${qs}` : ""}`;
|
|
172
172
|
}
|
|
173
|
+
/** Preserve the selected scope for a deep link emitted inside an Operate page.
|
|
174
|
+
* Components should prefer this to hand-writing `/#/inspect/...`: it reads the
|
|
175
|
+
* canonical hash at click/render time and intentionally leaves bare Chat URLs
|
|
176
|
+
* unchanged because Chat selection is client state. */
|
|
177
|
+
export function withCurrentWorkspaceId(href, locationLike = typeof globalThis.location === "undefined" ? undefined : globalThis.location) {
|
|
178
|
+
const { query } = splitHash(locationLike?.hash ?? "");
|
|
179
|
+
const workspaceId = query.get("workspaceId")?.trim() ||
|
|
180
|
+
new URLSearchParams(locationLike?.search ?? "").get("workspaceId")?.trim() ||
|
|
181
|
+
undefined;
|
|
182
|
+
return withWorkspaceId(href, workspaceId);
|
|
183
|
+
}
|
|
173
184
|
export function buildCanonicalHref(input) {
|
|
174
185
|
if (input.workspace === "chat")
|
|
175
186
|
return buildChatHref(input);
|
|
@@ -19,9 +19,10 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import { randomBytes } from "node:crypto";
|
|
21
21
|
import { existsSync } from "node:fs";
|
|
22
|
+
import { rm } from "node:fs/promises";
|
|
22
23
|
import path from "node:path";
|
|
23
24
|
import { LoopAgentClient } from "../loop-agent/loop-agent-client.js";
|
|
24
|
-
import { openConsoleAppData, } from "../../infrastructure/console/app-data.js";
|
|
25
|
+
import { openConsoleAppData, sha256Json, writeSecureJson, } from "../../infrastructure/console/app-data.js";
|
|
25
26
|
import { DagConfirmationStore } from "./dag-confirmation.js";
|
|
26
27
|
import { DagExecutionReceiptStore } from "./dag-execution-receipt.js";
|
|
27
28
|
import { DraftStore } from "./draft-store.js";
|
|
@@ -31,6 +32,7 @@ import { ChatSessionStore } from "./chat/session-store.js";
|
|
|
31
32
|
import { createChatEventStore, createChatOperationLinker, } from "./chat/chat-event-store.js";
|
|
32
33
|
import { createChatTurnExecutionRegistry } from "./chat/turn-execution-registry.js";
|
|
33
34
|
import { ConsolePiRuntime } from "./chat/pi-runtime.js";
|
|
35
|
+
import { AimaxAuthReader, SddDataAlignmentService } from "./chat/sdd-data-alignment.js";
|
|
34
36
|
import { UserQuestionRegistry } from "./chat/user-questions.js";
|
|
35
37
|
import { TodoListRegistry } from "./chat/todos.js";
|
|
36
38
|
import { GoalRegistry } from "./chat/goals.js";
|
|
@@ -38,6 +40,9 @@ import { GoalRoundDriver } from "./chat/goal-round-driver.js";
|
|
|
38
40
|
import { driveGoalRound, recoverDeferredTurns } from "./chat/routes.js";
|
|
39
41
|
import { createOperationEventStore, } from "./operation-sse.js";
|
|
40
42
|
import { OperationStore } from "../../infrastructure/console/operation-store.js";
|
|
43
|
+
import { createFrontendSplitOperationAdapter, } from "./frontend-split-operation-adapter.js";
|
|
44
|
+
import { createFrontendHumanDecisionAdapter } from "./frontend-human-decision-adapter.js";
|
|
45
|
+
import { FrontendSplitTaskMaterializer, } from "../materialize/frontend-split-task-materializer.js";
|
|
41
46
|
import { reconcileContextExports } from "../../infrastructure/console/context-export-store.js";
|
|
42
47
|
import { reconcileArtifactRevisions } from "../../infrastructure/console/artifact-revision-store.js";
|
|
43
48
|
import { recordContextTransferEvent } from "./context-transfer-diagnostics.js";
|
|
@@ -76,6 +81,106 @@ export async function createWorkspaceContext(record, deps) {
|
|
|
76
81
|
const events = createOperationEventStore({
|
|
77
82
|
persistenceDir: path.join(appData.operations, "events"),
|
|
78
83
|
});
|
|
84
|
+
// Frontend split runtime is repo-scoped: materialization, durable schedule
|
|
85
|
+
// intents, and aggregate closeout must use this workspace's app-data and
|
|
86
|
+
// OperationStore rather than the process startup workspace.
|
|
87
|
+
const splitScheduleDir = path.join(appData.root, "frontend-split-schedule");
|
|
88
|
+
const splitAuditDir = path.join(appData.root, "frontend-split-audit");
|
|
89
|
+
const splitAggregateDir = path.join(appData.root, "frontend-split-aggregate");
|
|
90
|
+
const splitMaterializationBackend = {
|
|
91
|
+
async ensureTask({ taskId }) {
|
|
92
|
+
return { taskId, complete: true };
|
|
93
|
+
},
|
|
94
|
+
async ensureOperation({ taskId, operationId, payloadDigest, request }) {
|
|
95
|
+
const action = request.kind === "child"
|
|
96
|
+
? "frontendSplitChild"
|
|
97
|
+
: "frontendSplitIntegration";
|
|
98
|
+
const { operation } = await operations.ensureSplitMaterializedOperation({
|
|
99
|
+
operationId,
|
|
100
|
+
taskId,
|
|
101
|
+
action,
|
|
102
|
+
actionParams: request,
|
|
103
|
+
payloadHash: payloadDigest,
|
|
104
|
+
});
|
|
105
|
+
return {
|
|
106
|
+
taskId: operation.taskId ?? taskId,
|
|
107
|
+
operationId: operation.operationId,
|
|
108
|
+
complete: true,
|
|
109
|
+
};
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
const materializer = new FrontendSplitTaskMaterializer(splitMaterializationBackend);
|
|
113
|
+
const schedule = async (input) => {
|
|
114
|
+
const fact = {
|
|
115
|
+
schemaVersion: 1,
|
|
116
|
+
kind: "frontend-split-schedule-intent",
|
|
117
|
+
operationId: input.operationId,
|
|
118
|
+
generation: input.generation,
|
|
119
|
+
bindingDigest: input.bindingDigest,
|
|
120
|
+
recordedAt: new Date().toISOString(),
|
|
121
|
+
};
|
|
122
|
+
const name = `${sha256Json({
|
|
123
|
+
operationId: input.operationId,
|
|
124
|
+
generation: input.generation,
|
|
125
|
+
bindingDigest: input.bindingDigest,
|
|
126
|
+
})}.json`;
|
|
127
|
+
await writeSecureJson(path.join(splitScheduleDir, name), fact);
|
|
128
|
+
};
|
|
129
|
+
const auditStale = async (input) => {
|
|
130
|
+
const fact = {
|
|
131
|
+
schemaVersion: 1,
|
|
132
|
+
kind: "frontend-split-stale-audit",
|
|
133
|
+
operationId: input.operationId,
|
|
134
|
+
reason: input.reason,
|
|
135
|
+
fact: input.fact,
|
|
136
|
+
recordedAt: new Date().toISOString(),
|
|
137
|
+
};
|
|
138
|
+
await writeSecureJson(path.join(splitAuditDir, `${sha256Json(fact)}.json`), fact);
|
|
139
|
+
};
|
|
140
|
+
const prepareAggregate = async ({ operationId, aggregate, }) => {
|
|
141
|
+
const before = await operations.get(operationId);
|
|
142
|
+
if (!before) {
|
|
143
|
+
throw new Error(`cannot prepare aggregate: parent operation not found: ${operationId}`);
|
|
144
|
+
}
|
|
145
|
+
const aggregateFile = path.join(splitAggregateDir, `${aggregate.digest}.json`);
|
|
146
|
+
let parentRecordPath = "";
|
|
147
|
+
let wroteAggregate = false;
|
|
148
|
+
let wroteParent = false;
|
|
149
|
+
return {
|
|
150
|
+
committedRef: {
|
|
151
|
+
ref: aggregateFile,
|
|
152
|
+
digest: aggregate.digest,
|
|
153
|
+
commitState: "committed",
|
|
154
|
+
},
|
|
155
|
+
async commit(input) {
|
|
156
|
+
parentRecordPath = input.parentRecordPath;
|
|
157
|
+
await writeSecureJson(aggregateFile, {
|
|
158
|
+
schemaVersion: 1,
|
|
159
|
+
kind: "frontend-split-aggregate-closeout",
|
|
160
|
+
operationId,
|
|
161
|
+
aggregate,
|
|
162
|
+
});
|
|
163
|
+
wroteAggregate = true;
|
|
164
|
+
await writeSecureJson(input.parentRecordPath, input.parentRecord);
|
|
165
|
+
wroteParent = true;
|
|
166
|
+
},
|
|
167
|
+
async rollback() {
|
|
168
|
+
if (wroteAggregate)
|
|
169
|
+
await rm(aggregateFile, { force: true });
|
|
170
|
+
if (wroteParent && parentRecordPath) {
|
|
171
|
+
await writeSecureJson(parentRecordPath, before);
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
};
|
|
176
|
+
const splitOrchestrator = createFrontendSplitOperationAdapter({
|
|
177
|
+
store: operations,
|
|
178
|
+
materializer,
|
|
179
|
+
schedule,
|
|
180
|
+
auditStale,
|
|
181
|
+
prepareAggregate,
|
|
182
|
+
});
|
|
183
|
+
const humanDecisionAdapter = createFrontendHumanDecisionAdapter(operations);
|
|
79
184
|
const drafts = new DraftStore(appData);
|
|
80
185
|
const confirmations = new DagConfirmationStore(appData, deps.bootToken.confirmationToken);
|
|
81
186
|
const dagExecutionReceipts = new DagExecutionReceiptStore(appData);
|
|
@@ -123,6 +228,9 @@ export async function createWorkspaceContext(record, deps) {
|
|
|
123
228
|
client,
|
|
124
229
|
operations,
|
|
125
230
|
events,
|
|
231
|
+
splitOrchestrator,
|
|
232
|
+
humanDecisionAdapter,
|
|
233
|
+
materializer,
|
|
126
234
|
drafts,
|
|
127
235
|
confirmations,
|
|
128
236
|
dagExecutionReceipts,
|
|
@@ -146,6 +254,7 @@ export async function createWorkspaceContext(record, deps) {
|
|
|
146
254
|
}
|
|
147
255
|
},
|
|
148
256
|
});
|
|
257
|
+
const sddDataAlignment = new SddDataAlignmentService(chatStore, new AimaxAuthReader(), deps.fetchImpl);
|
|
149
258
|
const chatEventEpoch = randomBytes(12).toString("hex");
|
|
150
259
|
const chatEvents = createChatEventStore({
|
|
151
260
|
appData,
|
|
@@ -184,6 +293,7 @@ export async function createWorkspaceContext(record, deps) {
|
|
|
184
293
|
questionRegistry,
|
|
185
294
|
todoRegistry,
|
|
186
295
|
goalRegistry,
|
|
296
|
+
sddDataAlignment,
|
|
187
297
|
});
|
|
188
298
|
let chatRouteDeps;
|
|
189
299
|
const goalRoundDriver = new GoalRoundDriver((sessionId) => driveGoalRound(chatRouteDeps, sessionId));
|
|
@@ -195,6 +305,7 @@ export async function createWorkspaceContext(record, deps) {
|
|
|
195
305
|
questionRegistry,
|
|
196
306
|
todoRegistry,
|
|
197
307
|
goalRegistry,
|
|
308
|
+
sddDataAlignment,
|
|
198
309
|
goalRoundDriver,
|
|
199
310
|
operationLinker: chatOperationLinker,
|
|
200
311
|
appData,
|
|
@@ -218,6 +329,9 @@ export async function createWorkspaceContext(record, deps) {
|
|
|
218
329
|
client,
|
|
219
330
|
operations,
|
|
220
331
|
events,
|
|
332
|
+
splitOrchestrator,
|
|
333
|
+
humanDecisionAdapter,
|
|
334
|
+
materializer,
|
|
221
335
|
drafts,
|
|
222
336
|
confirmations,
|
|
223
337
|
dagExecutionReceipts,
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { computeFrontendSplitDigest } from "../../workflows/dag/frontend-split-orchestrator.js";
|
|
3
|
+
const idSchema = z.string().trim().min(1);
|
|
4
|
+
const digestSchema = z.string().regex(/^[a-f0-9]{64}$/);
|
|
5
|
+
const positiveIntegerSchema = z.number().int().positive();
|
|
6
|
+
const pathSchema = z.string().trim().min(1);
|
|
7
|
+
export const frontendSplitTaskMaterializationRequestSchema = z.object({
|
|
8
|
+
kind: z.enum(["child", "integration"]),
|
|
9
|
+
parentOperationId: idSchema,
|
|
10
|
+
manifestDigest: digestSchema,
|
|
11
|
+
generation: positiveIntegerSchema,
|
|
12
|
+
taskId: idSchema,
|
|
13
|
+
operationId: idSchema,
|
|
14
|
+
payloadDigest: digestSchema,
|
|
15
|
+
allowedPaths: z.array(pathSchema),
|
|
16
|
+
writeSet: z.array(pathSchema),
|
|
17
|
+
verifyRefs: z.array(idSchema),
|
|
18
|
+
}).strict();
|
|
19
|
+
export const frontendSplitTaskMaterializationResultSchema = z.object({
|
|
20
|
+
idempotencyKey: digestSchema,
|
|
21
|
+
requestDigest: digestSchema,
|
|
22
|
+
taskId: idSchema,
|
|
23
|
+
operationId: idSchema,
|
|
24
|
+
}).strict();
|
|
25
|
+
function identityPayload(request) {
|
|
26
|
+
return { kind: request.kind, parentOperationId: request.parentOperationId, manifestDigest: request.manifestDigest, generation: request.generation, taskId: request.taskId, operationId: request.operationId };
|
|
27
|
+
}
|
|
28
|
+
export class FrontendSplitTaskMaterializer {
|
|
29
|
+
backend;
|
|
30
|
+
completed = new Map();
|
|
31
|
+
inFlight = new Map();
|
|
32
|
+
constructor(backend) {
|
|
33
|
+
this.backend = backend;
|
|
34
|
+
}
|
|
35
|
+
async materialize(raw) {
|
|
36
|
+
const request = frontendSplitTaskMaterializationRequestSchema.parse(raw);
|
|
37
|
+
const idempotencyKey = computeFrontendSplitDigest(identityPayload(request));
|
|
38
|
+
const requestDigest = computeFrontendSplitDigest(request);
|
|
39
|
+
const existing = this.completed.get(idempotencyKey);
|
|
40
|
+
if (existing) {
|
|
41
|
+
if (existing.requestDigest !== requestDigest)
|
|
42
|
+
throw new Error("frontend split materialization payload drift");
|
|
43
|
+
return existing;
|
|
44
|
+
}
|
|
45
|
+
const pending = this.inFlight.get(idempotencyKey);
|
|
46
|
+
if (pending) {
|
|
47
|
+
const result = await pending;
|
|
48
|
+
if (result.requestDigest !== requestDigest)
|
|
49
|
+
throw new Error("frontend split materialization payload drift");
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
const promise = this.perform(request, idempotencyKey, requestDigest);
|
|
53
|
+
this.inFlight.set(idempotencyKey, promise);
|
|
54
|
+
try {
|
|
55
|
+
const result = await promise;
|
|
56
|
+
this.completed.set(idempotencyKey, result);
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
this.inFlight.delete(idempotencyKey);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async perform(request, idempotencyKey, requestDigest) {
|
|
64
|
+
const task = await this.backend.ensureTask({ idempotencyKey, taskId: request.taskId, payloadDigest: requestDigest, request });
|
|
65
|
+
if (!task.complete || task.taskId !== request.taskId)
|
|
66
|
+
throw new Error("frontend split task materialization incomplete or drifted");
|
|
67
|
+
const operation = await this.backend.ensureOperation({ idempotencyKey, taskId: request.taskId, operationId: request.operationId, payloadDigest: requestDigest, request });
|
|
68
|
+
if (!operation.complete || operation.taskId !== request.taskId || operation.operationId !== request.operationId)
|
|
69
|
+
throw new Error("frontend split operation materialization incomplete or drifted");
|
|
70
|
+
return frontendSplitTaskMaterializationResultSchema.parse({ idempotencyKey, requestDigest, taskId: request.taskId, operationId: request.operationId });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -278,6 +278,10 @@ export async function serveObserveStatic(req, res, staticDir, options = {}) {
|
|
|
278
278
|
"Content-Length": content.byteLength,
|
|
279
279
|
"X-Content-Type-Options": "nosniff",
|
|
280
280
|
"X-Frame-Options": "SAMEORIGIN",
|
|
281
|
+
// Filenames are unhashed and Console/observe builds change in place;
|
|
282
|
+
// heuristic caching would let a stale operator-chrome.js (old nav,
|
|
283
|
+
// old capabilities) survive a Console restart/update.
|
|
284
|
+
"Cache-Control": "no-store",
|
|
281
285
|
};
|
|
282
286
|
if (contentType.startsWith("text/html") && options.htmlEnhancer) {
|
|
283
287
|
const enhanced = options.htmlEnhancer(Buffer.from(content));
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
formatDuration,
|
|
7
7
|
formatAgo,
|
|
8
8
|
} from "./format.js";
|
|
9
|
-
import { navigate } from "./router.js";
|
|
9
|
+
import { inspectPathToCanonicalHref, navigate } from "./router.js";
|
|
10
10
|
|
|
11
11
|
export function isNodeActive(status) {
|
|
12
12
|
if (!status) return false;
|
|
@@ -151,7 +151,9 @@ export function frontendLintSummary(frontendLint) {
|
|
|
151
151
|
card.appendChild(meta);
|
|
152
152
|
|
|
153
153
|
const link = el("a", "active-dag-link", UI_TEXT.enterDetail);
|
|
154
|
-
link.href =
|
|
154
|
+
link.href = inspectPathToCanonicalHref(
|
|
155
|
+
`/dag/${encodeURIComponent(dag.dagRunId)}`,
|
|
156
|
+
);
|
|
155
157
|
link.addEventListener("click", (e) => {
|
|
156
158
|
e.preventDefault();
|
|
157
159
|
navigate(`/dag/${encodeURIComponent(dag.dagRunId)}`);
|
|
@@ -39,20 +39,25 @@ const NODE_PURPOSE_BY_ID = {
|
|
|
39
39
|
"closeout-summary-pi": "把验证与交付事实写成简短交接摘要。",
|
|
40
40
|
|
|
41
41
|
"frontend-contract-pi": "把前端需求整理成可追踪契约:页面、状态、验收与边界。",
|
|
42
|
+
"frontend-contract-import-shell": "导入并固化前端实现契约,供后续设计、写入与验证节点使用。",
|
|
42
43
|
"frontend-scout-pi": "只读侦察前端结构、组件与接口用法,不改文件。",
|
|
43
44
|
"frontend-plan-pi": "选定实现与 Mock/API 策略,输出可执行的前端实现契约补丁。",
|
|
45
|
+
"frontend-design-policy-shell": "确定前端设计与 Mock/API 策略,冻结后续实现必须遵守的规则。",
|
|
44
46
|
"frontend-design-review-pi": "审查前端计划是否可写:通过则沿用原计划,否则要求修订。",
|
|
45
47
|
"frontend-plan-revision-pi": "按设计审查意见修订前端契约,不扩大范围。",
|
|
46
48
|
"frontend-final-design-review-pi": "复核修订后的前端计划,只有通过才能授权写入。",
|
|
47
49
|
"frontend-prewrite-gate-shell": "写入前门禁:核对契约、覆盖与 Mock 策略,通过才允许改前端。",
|
|
50
|
+
"frontend-writer-admission-shell": "核对契约、设计策略与 writeSet,满足准入条件后才允许 writer 写入。",
|
|
48
51
|
"frontend-lint-baseline-shell": "记录写入前的 lint 基线,供实现后对比,不阻断写入。",
|
|
49
52
|
"frontend-implement-pi": "按已批准契约改前端代码、状态与交互,留在 writeSet 内。",
|
|
50
53
|
"frontend-verify-assess-shell": "跑静态/行为/Mock 验证并评估能否有界修复。",
|
|
54
|
+
"frontend-verify-shell": "执行冻结的前端验证命令,产出可复查的通过或失败证据。",
|
|
51
55
|
"frontend-repair-pi": "只修评估为可修复的前端失败,不重开需求。",
|
|
52
56
|
"frontend-reverify-shell": "修复后用同一套冻结命令复验。",
|
|
53
57
|
"frontend-review-context-shell": "打包真实 diff 与有效验证证据,供审查节点使用。",
|
|
54
58
|
"frontend-review-pi": "对照契约审查前端实现与证据,给出通过或需修订。",
|
|
55
59
|
"frontend-review-gate-shell": "前端审查门禁:结论为通过才进入收口。",
|
|
60
|
+
"frontend-closeout-shell": "汇总实现、验证与遗留事项,形成确定性的前端收口事实。",
|
|
56
61
|
"frontend-closeout-pi": "汇总前端交付状态(含真实联调是否仍待完成)。",
|
|
57
62
|
"frontend-mock-assess-pi": "评估项目是否具备授权的 Mock 验证能力。",
|
|
58
63
|
"frontend-mock-contract-gate-shell": "Mock 契约门禁:策略与验证命令不一致则阻断。",
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
parseRoute,
|
|
11
11
|
setNavigateHandler,
|
|
12
12
|
inspectPathToCanonicalHref,
|
|
13
|
+
workspaceIdFromLocation,
|
|
13
14
|
} from "./router.js";
|
|
14
15
|
import {
|
|
15
16
|
isPageVisible,
|
|
@@ -93,6 +94,25 @@ function onInspectRootClick(event) {
|
|
|
93
94
|
navigateInspectPath(href);
|
|
94
95
|
}
|
|
95
96
|
|
|
97
|
+
/** Static Inspect markup still contains a few compatibility anchors. Normalize
|
|
98
|
+
* them on mount/route change so opening a link in a new tab preserves the
|
|
99
|
+
* workspace as reliably as an ordinary in-app click. Chat remains deliberately
|
|
100
|
+
* bare: its selected workspace is client state, not a hash contract. */
|
|
101
|
+
function scopeInspectShellLinks(root, workspaceId) {
|
|
102
|
+
if (!root?.querySelectorAll) return;
|
|
103
|
+
for (const anchor of root.querySelectorAll("a[href]")) {
|
|
104
|
+
const href = anchor.getAttribute("href");
|
|
105
|
+
if (!href || href.startsWith("/#/chat") || href.startsWith("#/chat")) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (!isInspectShellHref(href)) continue;
|
|
109
|
+
anchor.setAttribute(
|
|
110
|
+
"href",
|
|
111
|
+
inspectPathToCanonicalHref(href, workspaceId),
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
96
116
|
function resumeInspectPolling() {
|
|
97
117
|
if (!isPageVisible() || !isWorkspaceActive()) return;
|
|
98
118
|
const r = lastInspectHash
|
|
@@ -187,6 +207,7 @@ export function startStandaloneInspect() {
|
|
|
187
207
|
if (typeof window === "undefined") return;
|
|
188
208
|
// Multi-workspace (AC-003): sync scope from the URL query before any fetch.
|
|
189
209
|
syncWorkspaceScopeFromLocation();
|
|
210
|
+
scopeInspectShellLinks(document, workspaceIdFromLocation());
|
|
190
211
|
window.addEventListener("hashchange", syncWorkspaceScopeFromLocation);
|
|
191
212
|
const chromeHost = document.getElementById("operator-chrome");
|
|
192
213
|
if (chromeHost) {
|
|
@@ -242,6 +263,7 @@ export function mountInspectWorkspace(root, options = {}) {
|
|
|
242
263
|
|
|
243
264
|
setNavigateHandler(onNavigate);
|
|
244
265
|
lastInspectHash = options.hash || (typeof location !== "undefined" ? location.hash : "");
|
|
266
|
+
scopeInspectShellLinks(root, workspaceIdFromHash(lastInspectHash));
|
|
245
267
|
setWorkspaceActive(options.active !== false);
|
|
246
268
|
|
|
247
269
|
const listenerAbort =
|
|
@@ -266,6 +288,7 @@ export function mountInspectWorkspace(root, options = {}) {
|
|
|
266
288
|
updateRoute(hashValue) {
|
|
267
289
|
if (host.destroyed) return;
|
|
268
290
|
lastInspectHash = hashValue || lastInspectHash;
|
|
291
|
+
scopeInspectShellLinks(root, workspaceIdFromHash(lastInspectHash));
|
|
269
292
|
if (!uiState.workspaceActive) return;
|
|
270
293
|
// AC-006: a hosted route update that carries a DIFFERENT workspaceId
|
|
271
294
|
// must not route under the old scope. Swap atomically:
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/** KPI cards, projection fault, artifact/inbox links. */
|
|
2
2
|
import { UI_TEXT } from "./constants.js";
|
|
3
3
|
import { el } from "./dom.js";
|
|
4
|
-
import { navigate } from "./router.js";
|
|
4
|
+
import { inspectPathToCanonicalHref, navigate } from "./router.js";
|
|
5
5
|
|
|
6
6
|
export function failuresInboxLink(count) {
|
|
7
7
|
const wrap = el("span", "kpi-value-with-link");
|
|
8
8
|
wrap.appendChild(el("span", "kpi-value-num", String(count)));
|
|
9
9
|
const a = el("a", "inbox-link", `${UI_TEXT.failureInbox} →`);
|
|
10
|
-
a.href = "
|
|
10
|
+
a.href = inspectPathToCanonicalHref("/failures");
|
|
11
11
|
a.addEventListener("click", (e) => {
|
|
12
12
|
e.preventDefault();
|
|
13
13
|
navigate("/failures");
|
|
@@ -62,10 +62,18 @@ export function parseInspectObjectView(
|
|
|
62
62
|
export function activeInspectTabHref(hashValue: string): string | null;
|
|
63
63
|
export function parseInspectContext(hashValue: string): InspectContext | null;
|
|
64
64
|
export function resolveOperateHrefFromInspectHash(hashValue: string): string;
|
|
65
|
-
export function buildOperateInterruptHref(
|
|
65
|
+
export function buildOperateInterruptHref(
|
|
66
|
+
dagRunId: string,
|
|
67
|
+
options?: { workspaceId?: string | null },
|
|
68
|
+
): string;
|
|
66
69
|
export function buildInspectDagHref(
|
|
67
70
|
dagRunId: string,
|
|
68
|
-
options?: { from?: string },
|
|
71
|
+
options?: { from?: string; workspaceId?: string | null },
|
|
72
|
+
): string;
|
|
73
|
+
export function parseInspectWorkspaceId(hashValue: string): string | null;
|
|
74
|
+
export function withWorkspaceQuery(
|
|
75
|
+
href: string,
|
|
76
|
+
workspaceId?: string | null,
|
|
69
77
|
): string;
|
|
70
78
|
export function clearInspectContextSource(hashValue: string): string;
|
|
71
79
|
export function repoBaseName(repoRoot: string): string;
|