@tea-agent/loop-agent 0.39.0-next.9 → 0.39.1
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 +269 -69
- 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 +16 -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/update/runtime-activity.js +1 -29
- 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 +555 -34
- 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 +27 -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 +70 -15
- package/dist/{cli → shared}/update/state.js +41 -13
- package/dist/task/config-types.js +28 -0
- package/dist/task/contract/apply.js +11 -1
- package/dist/task/contract/constants.js +3 -0
- package/dist/task/contract/diff.js +26 -0
- package/dist/task/contract/hash.js +3 -0
- package/dist/task/contract/observe.js +25 -4
- package/dist/task/contract/paths.js +2 -1
- package/dist/task/contract/project.js +20 -1
- package/dist/task/contract/schema.js +8 -1
- package/dist/task/contract/transaction.js +12 -0
- package/dist/task/source-prepare/build-draft.js +15 -0
- package/dist/task/source-prepare/completeness.js +188 -0
- package/dist/task/source-prepare/fragment-inventory.js +474 -0
- package/dist/task/source-prepare/index.js +3 -0
- package/dist/task/source-prepare/ledger-reconciliation.js +127 -0
- package/dist/task/source-prepare/ledger-review.js +214 -0
- package/dist/task/source-prepare/ledger.js +545 -0
- package/dist/task/source-prepare/parse-intent.js +24 -4
- package/dist/task/source-prepare/prepare.js +262 -1
- package/dist/task/source-prepare/semantic-intake.js +19 -3
- package/dist/task/source-prepare/source-fidelity-pi.js +384 -0
- package/dist/task/source-prepare/types.js +22 -0
- package/dist/worker/cli.js +18 -2
- 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/console-handoff.js +82 -0
- package/dist/worker/console/console-update-and-init.js +115 -0
- package/dist/worker/console/console-update-runtime.js +84 -0
- package/dist/worker/console/dag-execution-receipt.js +33 -0
- package/dist/worker/console/draft-store.js +49 -0
- package/dist/worker/console/inspect-split.js +18 -0
- package/dist/worker/console/operation-run-facts.js +19 -3
- package/dist/worker/console/operation-store.js +1 -1
- package/dist/worker/console/operator-actions.js +320 -4
- package/dist/worker/console/operator-surface-health.js +1 -0
- package/dist/worker/console/operator-user-error.js +2 -2
- package/dist/worker/console/pi-readiness.js +5 -4
- package/dist/worker/console/prd-intake-bridge.js +13 -0
- 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 +46 -2
- package/dist/worker/console/security.js +63 -9
- package/dist/worker/console/server.js +111 -7
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-BLXFe4qI.js → abnfDiagram-N423BO3Z-CTx1TrNa.js} +1 -1
- package/dist/worker/console/static/assets/{arc-7iOJeqho.js → arc-B4i9cVxX.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CYoyj5K7.js → architectureDiagram-T3A2C74G-BAXE8_Rl.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-fK01JNlR.js → blockDiagram-VBNYF7ZC-C5Xh59at.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-bzbw5D_Z.js → c4Diagram-5PPSVZJV-B_asOzAu.js} +1 -1
- package/dist/worker/console/static/assets/channel-Dj_4Moei.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-Cf6hJFFG.js → chunk-2GRJ4B5K-wRO_iRNc.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DSFXR-et.js → chunk-2Q5K7J3B-ByOaSh_G.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BagJechl.js → chunk-5RXB4S5H-DjLzudan.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-E7QNMFaW.js → chunk-5VM5RSS4-JCNGkXYT.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BG_eOBrC.js → chunk-6Q2QTUOP-BhrPXXMZ.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-Bo42dqol.js → chunk-GF5L2VYU-TJ5JuEBT.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-dibM-87v.js → chunk-JWPE2WC7-VVthn_mD.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-Bytrp3Oe.js → chunk-KBJHAD2P-D60-Mny4.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DBABILrr.js → chunk-RYQCIY6F-zXv7aVye.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-DCUk5Pf9.js → chunk-XXDRQBXY-By9VQoi1.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C0YYAyCa.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C0YYAyCa.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BlGE9v6E.js → cose-bilkent-JH36ORCC-C4zQLe-M.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-DGl5ewph.js → cynefin-VYW2F7L2-wh5N2ReN.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-DsJ_SVH_.js → cynefinDiagram-MW4NZA55-Dlu5wIqs.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BIhjFdzv.js → dagre-VZM6K2ZE-CrDgthc6.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Dmy1ctTb.js → diagram-7IWD3JNH-zN1clg27.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-DYCtWUF3.js → diagram-B4RE2ZJO-BN-o7J7-.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-BMZFHVd2.js → diagram-LBJQPF4R-C8SuPt0d.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-b_U8gfaO.js → diagram-Q27KOJAE-BoizuHbZ.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-Dzy_bn-I.js → diagram-UB23O5K3-BzUzXixG.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-DJCiMNEI.js → ebnfDiagram-BXEA7PRR-Ev3lA4Cf.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-28lD016B.js → erDiagram-JOGREHBK-GALUzRbs.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-DGyHJmIw.js → flowDiagram-UKHOOZJN-CZJN65Sb.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CzdwUjrO.js → ganttDiagram-PKOTCBZU-JLB9OESl.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-D9bGwuo8.js → gitGraphDiagram-DS77QQ5N-CcvCr4GD.js} +1 -1
- package/dist/worker/console/static/assets/index-CW88Fjbw.js +406 -0
- package/dist/worker/console/static/assets/index-DP0ZJl39.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-LJgJUQFC.js → infoDiagram-6WML65LV-HG5z7aVg.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-98LaB8zb.js → ishikawaDiagram-WSZJBQD7-BHEu9chR.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-D-ISzwlQ.js → journeyDiagram-NVQOT4AX-Cp_Up8-a.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-gfYOI-EL.js → kanban-definition-27J2QSJJ-DvVeY-em.js} +1 -1
- package/dist/worker/console/static/assets/{linear-CyntZgKV.js → linear-aGmcjgTn.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-BBOR7dg0.js → mermaid.core-Biyo-tqA.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-CQAgwPMk.js → mindmap-definition-FAOFIHXS-DyxY2pIb.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DkMiZFuV.js → pegDiagram-VL7TDLO6-CkdGuc50.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DLNXa_0J.js → pieDiagram-7S7Q4E2Y-NNQI9xVR.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-Dc7bdzjm.js → quadrantDiagram-CIZ2JOQS-PAJkuz_G.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BxMB0QqW.js → railroadDiagram-AXF67PYL-ZwrQod0O.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-FjumoKn4.js → requirementDiagram-LRYGKXZP-D3ZfrzM2.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-CkhKc_E3.js → sankeyDiagram-W5VNT64P-DDGFmWk5.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DoBWqTTH.js → sequenceDiagram-SI44F4Z6-DO6mmXTA.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-CQeh_YeE.js → sizeCapture-X5ZJPWSS-DMbCQiJt.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-LuHmFW-I.js → stateDiagram-OKZ733FA-Di_YUEfC.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DrdDvela.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CHwbDctY.js → swimlanes-SLNWSIFB-Bn2PphNJ.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DPkEAULl.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CoMngk0X.js → timeline-definition-Z64GVDOM-DPh8pRsp.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-CURYbtbO.js → vennDiagram-T6HMQDX7-GMfl26pW.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-BdqSECjU.js → wardleyDiagram-T6FBY63Y-DTlmVrmF.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-DbMUYuAg.js → xychartDiagram-ELKLHX3M-YGBjKAS5.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +40 -1
- package/dist/worker/console/static-src/app/useOperatorActions.js +6 -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/app/useTaskWizard.js +73 -1
- 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/init-runtime-activity.js +26 -0
- package/dist/worker/observability/read-model.js +22 -0
- package/dist/worker/observe/node-input.js +160 -3
- package/dist/worker/observe/node-process.js +377 -0
- package/dist/worker/observe/routes.js +224 -3
- package/dist/worker/observe/shell-handler-keys.js +34 -0
- package/dist/worker/observe/static/api.js +67 -1
- 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 +60 -0
- package/dist/worker/observe/static/index.html +5 -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/prompt-restart-candidates.d.ts +18 -0
- package/dist/worker/observe/static/prompt-restart-candidates.js +36 -0
- package/dist/worker/observe/static/state.js +13 -2
- package/dist/worker/observe/static/styles.css +85 -0
- package/dist/worker/observe/static/views/dag-inspector.js +912 -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/worker/observe/static/views/task.js +55 -0
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +856 -54
- 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 +343 -33
- package/dist/workflows/dag/backend-test-module-stem.js +5 -0
- package/dist/workflows/dag/backend-test-pytest-collection.js +454 -36
- 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 +110 -61
- 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 +465 -6
- package/dist/workflows/dag/frontend-plan-render.js +24 -0
- package/dist/workflows/dag/frontend-prewrite-gate.js +293 -84
- package/dist/workflows/dag/frontend-repair.js +14 -0
- package/dist/workflows/dag/frontend-review-context.js +92 -10
- 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 +1019 -209
- package/dist/workflows/dag/node-execution.js +228 -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 +185 -12
- package/dist/workflows/dag/validate.js +64 -20
- package/docs/README.md +1 -0
- package/docs/architecture/evolution.md +45 -0
- package/docs/init-surface.manifest.json +6 -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 +2 -2
- 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 +681 -24
- 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/loop-agent/references/hybrid-dag.md +7 -0
- 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
|
@@ -17,6 +17,7 @@ import { dispatchOperatorAction } from "../operator-actions.js";
|
|
|
17
17
|
import { authorizeOperatorChatTool } from "./tools.js";
|
|
18
18
|
import { projectOperationForChat } from "./chat-event-store.js";
|
|
19
19
|
import { scrubSecrets } from "./explore-tools.js";
|
|
20
|
+
import { hashDeferredTurnPayload, parseDeferredTurn, } from "./deferred-turn.js";
|
|
20
21
|
const CHAT_TITLE_MAX_CHARS = 80;
|
|
21
22
|
/** Safe, one-line bounded title used for placeholders and model output. */
|
|
22
23
|
export function cleanChatTitle(value) {
|
|
@@ -53,6 +54,7 @@ export class ChatSessionStore {
|
|
|
53
54
|
const raw = await readJsonIfExists(recordPath(this.appData, sessionId));
|
|
54
55
|
if (!raw || raw.sessionId !== sessionId)
|
|
55
56
|
return undefined;
|
|
57
|
+
const deferredTurn = parseDeferredTurn(raw.deferredTurn);
|
|
56
58
|
return {
|
|
57
59
|
schemaVersion: 2,
|
|
58
60
|
sessionId,
|
|
@@ -67,6 +69,7 @@ export class ChatSessionStore {
|
|
|
67
69
|
...(typeof raw.modelProvider === "string" ? { modelProvider: raw.modelProvider } : {}),
|
|
68
70
|
...(typeof raw.modelId === "string" ? { modelId: raw.modelId } : {}),
|
|
69
71
|
...(typeof raw.thinkingLevel === "string" ? { thinkingLevel: raw.thinkingLevel } : {}),
|
|
72
|
+
...(deferredTurn ? { deferredTurn } : {}),
|
|
70
73
|
messages: Array.isArray(raw.messages) ? raw.messages : [],
|
|
71
74
|
toolInvocations: Array.isArray(raw.toolInvocations) ? raw.toolInvocations : [],
|
|
72
75
|
};
|
|
@@ -147,6 +150,7 @@ export class ChatSessionStore {
|
|
|
147
150
|
...(input.targetSessionFile ? { sessionFile: input.targetSessionFile } : { sessionFile: undefined }),
|
|
148
151
|
createdAt: now,
|
|
149
152
|
updatedAt: now,
|
|
153
|
+
deferredTurn: undefined,
|
|
150
154
|
messages: structuredClone(source.messages),
|
|
151
155
|
toolInvocations: structuredClone(source.toolInvocations),
|
|
152
156
|
};
|
|
@@ -216,6 +220,146 @@ export class ChatSessionStore {
|
|
|
216
220
|
return true;
|
|
217
221
|
});
|
|
218
222
|
}
|
|
223
|
+
async putDeferredTurn(sessionId, input) {
|
|
224
|
+
return this.serialize(sessionId, async () => {
|
|
225
|
+
const record = await this.get(sessionId);
|
|
226
|
+
if (!record)
|
|
227
|
+
return { ok: false, code: "NOT_FOUND" };
|
|
228
|
+
const clientRequestId = input.clientRequestId.trim();
|
|
229
|
+
const blockedByTurnId = input.blockedByTurnId.trim();
|
|
230
|
+
const text = input.text.trim() || (input.images.length > 0 ? "请查看附件图片。" : "");
|
|
231
|
+
if (!clientRequestId || !blockedByTurnId || !text) {
|
|
232
|
+
return { ok: false, code: "INVALID_INPUT" };
|
|
233
|
+
}
|
|
234
|
+
const payloadHash = hashDeferredTurnPayload({ text, images: input.images });
|
|
235
|
+
const existing = record.deferredTurn;
|
|
236
|
+
if (existing) {
|
|
237
|
+
if (existing.clientRequestId === clientRequestId) {
|
|
238
|
+
if (existing.payloadHash !== payloadHash) {
|
|
239
|
+
return {
|
|
240
|
+
ok: false,
|
|
241
|
+
code: "REQUEST_ID_REUSE_CONFLICT",
|
|
242
|
+
deferredTurn: existing,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
return { ok: true, deferredTurn: existing, idempotent: true };
|
|
246
|
+
}
|
|
247
|
+
if (existing.state !== "queued") {
|
|
248
|
+
return {
|
|
249
|
+
ok: false,
|
|
250
|
+
code: "NOT_REPLACEABLE",
|
|
251
|
+
deferredTurn: existing,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
if (input.expectedRevision !== existing.revision) {
|
|
255
|
+
return {
|
|
256
|
+
ok: false,
|
|
257
|
+
code: "REVISION_CONFLICT",
|
|
258
|
+
deferredTurn: existing,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
else if (input.expectedRevision !== undefined &&
|
|
263
|
+
input.expectedRevision !== 0) {
|
|
264
|
+
return { ok: false, code: "REVISION_CONFLICT" };
|
|
265
|
+
}
|
|
266
|
+
const now = new Date().toISOString();
|
|
267
|
+
const deferredTurn = {
|
|
268
|
+
schemaVersion: 1,
|
|
269
|
+
revision: (existing?.revision ?? 0) + 1,
|
|
270
|
+
clientRequestId,
|
|
271
|
+
payloadHash,
|
|
272
|
+
blockedByTurnId,
|
|
273
|
+
text,
|
|
274
|
+
images: structuredClone(input.images),
|
|
275
|
+
state: "queued",
|
|
276
|
+
createdAt: existing?.createdAt ?? now,
|
|
277
|
+
updatedAt: now,
|
|
278
|
+
};
|
|
279
|
+
record.deferredTurn = deferredTurn;
|
|
280
|
+
record.updatedAt = now;
|
|
281
|
+
await writeSecureJson(recordPath(this.appData, sessionId), record);
|
|
282
|
+
return { ok: true, deferredTurn, idempotent: false };
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
async claimDeferredTurn(sessionId, expectedRevision) {
|
|
286
|
+
return this.serialize(sessionId, async () => {
|
|
287
|
+
const record = await this.get(sessionId);
|
|
288
|
+
const existing = record?.deferredTurn;
|
|
289
|
+
if (!record || !existing)
|
|
290
|
+
return { ok: false, code: "NOT_FOUND" };
|
|
291
|
+
if (existing.revision !== expectedRevision) {
|
|
292
|
+
return { ok: false, code: "REVISION_CONFLICT", deferredTurn: existing };
|
|
293
|
+
}
|
|
294
|
+
if (existing.state !== "queued") {
|
|
295
|
+
return { ok: false, code: "NOT_CLAIMABLE", deferredTurn: existing };
|
|
296
|
+
}
|
|
297
|
+
const next = {
|
|
298
|
+
...existing,
|
|
299
|
+
revision: existing.revision + 1,
|
|
300
|
+
state: "dispatching",
|
|
301
|
+
updatedAt: new Date().toISOString(),
|
|
302
|
+
};
|
|
303
|
+
record.deferredTurn = next;
|
|
304
|
+
record.updatedAt = next.updatedAt;
|
|
305
|
+
await writeSecureJson(recordPath(this.appData, sessionId), record);
|
|
306
|
+
return { ok: true, deferredTurn: next };
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
async transitionDeferredTurn(sessionId, input) {
|
|
310
|
+
return this.serialize(sessionId, async () => {
|
|
311
|
+
const record = await this.get(sessionId);
|
|
312
|
+
const existing = record?.deferredTurn;
|
|
313
|
+
if (!record || !existing || existing.revision !== input.expectedRevision)
|
|
314
|
+
return undefined;
|
|
315
|
+
const next = {
|
|
316
|
+
...existing,
|
|
317
|
+
revision: existing.revision + 1,
|
|
318
|
+
state: input.state,
|
|
319
|
+
updatedAt: new Date().toISOString(),
|
|
320
|
+
...(input.acceptedTurnId ? { acceptedTurnId: input.acceptedTurnId } : {}),
|
|
321
|
+
...(input.lastError ? { lastError: input.lastError } : {}),
|
|
322
|
+
};
|
|
323
|
+
if (!input.lastError)
|
|
324
|
+
delete next.lastError;
|
|
325
|
+
record.deferredTurn = next;
|
|
326
|
+
record.updatedAt = next.updatedAt;
|
|
327
|
+
await writeSecureJson(recordPath(this.appData, sessionId), record);
|
|
328
|
+
return next;
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
async deleteDeferredTurn(sessionId, input) {
|
|
332
|
+
return this.serialize(sessionId, async () => {
|
|
333
|
+
const record = await this.get(sessionId);
|
|
334
|
+
const existing = record?.deferredTurn;
|
|
335
|
+
if (!record || !existing || existing.clientRequestId !== input.clientRequestId) {
|
|
336
|
+
return { ok: false, code: "NOT_FOUND" };
|
|
337
|
+
}
|
|
338
|
+
if (existing.state !== "queued" && existing.state !== "failed") {
|
|
339
|
+
return { ok: false, code: "NOT_CANCELLABLE", deferredTurn: existing };
|
|
340
|
+
}
|
|
341
|
+
if (existing.revision !== input.expectedRevision) {
|
|
342
|
+
return { ok: false, code: "REVISION_CONFLICT", deferredTurn: existing };
|
|
343
|
+
}
|
|
344
|
+
delete record.deferredTurn;
|
|
345
|
+
record.updatedAt = new Date().toISOString();
|
|
346
|
+
await writeSecureJson(recordPath(this.appData, sessionId), record);
|
|
347
|
+
return { ok: true, deferredTurn: existing };
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
async clearDeferredTurnForRequest(sessionId, clientRequestId) {
|
|
351
|
+
return this.serialize(sessionId, async () => {
|
|
352
|
+
const record = await this.get(sessionId);
|
|
353
|
+
const existing = record?.deferredTurn;
|
|
354
|
+
if (!record || !existing || existing.clientRequestId !== clientRequestId) {
|
|
355
|
+
return undefined;
|
|
356
|
+
}
|
|
357
|
+
delete record.deferredTurn;
|
|
358
|
+
record.updatedAt = new Date().toISOString();
|
|
359
|
+
await writeSecureJson(recordPath(this.appData, sessionId), record);
|
|
360
|
+
return { deferredTurn: existing, clearedRevision: existing.revision + 1 };
|
|
361
|
+
});
|
|
362
|
+
}
|
|
219
363
|
async appendMessage(sessionId, message) {
|
|
220
364
|
return this.serialize(sessionId, async () => {
|
|
221
365
|
const record = await this.get(sessionId);
|
|
@@ -340,10 +484,10 @@ export async function runChatTurn(options) {
|
|
|
340
484
|
},
|
|
341
485
|
};
|
|
342
486
|
}
|
|
343
|
-
await store.appendMessage(sessionId, { role: "user", text });
|
|
487
|
+
const userMessage = await store.appendMessage(sessionId, { role: "user", text });
|
|
344
488
|
const afterUserMessage = await store.get(sessionId);
|
|
345
489
|
if (afterUserMessage)
|
|
346
|
-
await options.onUserMessagePersisted?.(afterUserMessage);
|
|
490
|
+
await options.onUserMessagePersisted?.(afterUserMessage, userMessage);
|
|
347
491
|
let turnError;
|
|
348
492
|
// Collect persistence promises so runChatTurn does not resolve before the
|
|
349
493
|
// chat history / audit trail is durably written (also makes the outcome
|
|
@@ -216,9 +216,6 @@ export function buildTurnProcessGroups(input) {
|
|
|
216
216
|
current = startGroup(`assistant-${message.id}`);
|
|
217
217
|
current.assistants.push(message);
|
|
218
218
|
}
|
|
219
|
-
if (groups.length === 0 && input.toolCalls.length > 0) {
|
|
220
|
-
groups.push(emptyGroup("orphan-tools"));
|
|
221
|
-
}
|
|
222
219
|
const assigned = new Set();
|
|
223
220
|
const assistantToGroup = new Map();
|
|
224
221
|
for (const group of groups) {
|
|
@@ -265,17 +262,12 @@ export function buildTurnProcessGroups(input) {
|
|
|
265
262
|
const end = next?.user?.createdAt ?? Number.POSITIVE_INFINITY;
|
|
266
263
|
return tool.createdAt >= start && tool.createdAt < end;
|
|
267
264
|
});
|
|
265
|
+
// Time window only. Never dump unmatched tools onto the latest group:
|
|
266
|
+
// that is how a previous turn's bash cards jump under a new prompt.
|
|
268
267
|
if (windowGroup) {
|
|
269
268
|
windowGroup.tools.push(tool);
|
|
270
269
|
assigned.add(tool.id);
|
|
271
270
|
}
|
|
272
|
-
else {
|
|
273
|
-
const lastGroup = groups.at(-1);
|
|
274
|
-
if (lastGroup) {
|
|
275
|
-
lastGroup.tools.push(tool);
|
|
276
|
-
assigned.add(tool.id);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
271
|
}
|
|
280
272
|
for (const group of groups) {
|
|
281
273
|
group.tools.sort((a, b) => {
|
|
@@ -292,6 +284,14 @@ export function buildTurnProcessGroups(input) {
|
|
|
292
284
|
}
|
|
293
285
|
return groups;
|
|
294
286
|
}
|
|
287
|
+
/** Keep the most recent complete turns. Slice groups, never raw messages. */
|
|
288
|
+
export function selectRecentTurnGroups(groups, limit) {
|
|
289
|
+
if (limit <= 0)
|
|
290
|
+
return [];
|
|
291
|
+
if (groups.length <= limit)
|
|
292
|
+
return [...groups];
|
|
293
|
+
return groups.slice(-limit);
|
|
294
|
+
}
|
|
295
295
|
export function processGroupSummary(tools, options) {
|
|
296
296
|
const count = tools.length;
|
|
297
297
|
const thinkingCount = options?.thinkingCount ?? 0;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
+
const HANDOFF_ENV = "LOOP_CONSOLE_HANDOFF_V1";
|
|
3
|
+
const HANDOFF_HASH_ENV = "LOOP_CONSOLE_HANDOFF_SHA256";
|
|
4
|
+
function canonicalJson(value) {
|
|
5
|
+
return JSON.stringify(value);
|
|
6
|
+
}
|
|
7
|
+
function digest(value) {
|
|
8
|
+
return createHash("sha256").update(value, "utf8").digest("hex");
|
|
9
|
+
}
|
|
10
|
+
export function createConsoleHandoff(input) {
|
|
11
|
+
const handoff = {
|
|
12
|
+
schemaVersion: 1,
|
|
13
|
+
consoleInstanceId: `console_${randomBytes(16).toString("hex")}`,
|
|
14
|
+
...input,
|
|
15
|
+
};
|
|
16
|
+
const encoded = canonicalJson(handoff);
|
|
17
|
+
return {
|
|
18
|
+
handoff,
|
|
19
|
+
env: {
|
|
20
|
+
[HANDOFF_ENV]: Buffer.from(encoded, "utf8").toString("base64url"),
|
|
21
|
+
[HANDOFF_HASH_ENV]: digest(encoded),
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** Parse an exact, hash-bound handoff contract inherited by the replacement process. */
|
|
26
|
+
export function readConsoleHandoff(env) {
|
|
27
|
+
const encoded = env[HANDOFF_ENV];
|
|
28
|
+
const expectedDigest = env[HANDOFF_HASH_ENV];
|
|
29
|
+
if (!encoded && !expectedDigest)
|
|
30
|
+
return undefined;
|
|
31
|
+
if (!encoded || !expectedDigest)
|
|
32
|
+
throw new Error("console handoff contract is incomplete");
|
|
33
|
+
let raw;
|
|
34
|
+
try {
|
|
35
|
+
raw = Buffer.from(encoded, "base64url").toString("utf8");
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
throw new Error("console handoff payload is invalid");
|
|
39
|
+
}
|
|
40
|
+
if (digest(raw) !== expectedDigest)
|
|
41
|
+
throw new Error("console handoff hash mismatch");
|
|
42
|
+
let parsed;
|
|
43
|
+
try {
|
|
44
|
+
parsed = JSON.parse(raw);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
throw new Error("console handoff payload is not JSON");
|
|
48
|
+
}
|
|
49
|
+
const value = parsed;
|
|
50
|
+
if (value.schemaVersion !== 1 ||
|
|
51
|
+
!Number.isInteger(value.oldPid) ||
|
|
52
|
+
value.oldPid <= 0 ||
|
|
53
|
+
typeof value.repoRoot !== "string" ||
|
|
54
|
+
typeof value.host !== "string" ||
|
|
55
|
+
!Number.isInteger(value.port) ||
|
|
56
|
+
value.port < 0 ||
|
|
57
|
+
typeof value.targetVersion !== "string" ||
|
|
58
|
+
typeof value.consoleInstanceId !== "string") {
|
|
59
|
+
throw new Error("console handoff payload has invalid fields");
|
|
60
|
+
}
|
|
61
|
+
return value;
|
|
62
|
+
}
|
|
63
|
+
export async function waitForOldConsoleExit(oldPid, options = {}) {
|
|
64
|
+
const isAlive = options.isAlive ?? ((pid) => {
|
|
65
|
+
try {
|
|
66
|
+
process.kill(pid, 0);
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
const sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
74
|
+
const timeoutMs = options.timeoutMs ?? 15_000;
|
|
75
|
+
const started = Date.now();
|
|
76
|
+
while (isAlive(oldPid)) {
|
|
77
|
+
if (Date.now() - started >= timeoutMs) {
|
|
78
|
+
throw new Error(`old Console PID ${oldPid} did not exit before handoff timeout`);
|
|
79
|
+
}
|
|
80
|
+
await sleep(50);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import semver from "semver";
|
|
2
|
+
function notRunMaintenance(channel) {
|
|
3
|
+
return {
|
|
4
|
+
package: { status: "failed", channel },
|
|
5
|
+
initialization: { status: "not-run", pendingPaths: [] },
|
|
6
|
+
handoff: { status: "not-needed" },
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
function failed(code, message, maintenance = notRunMaintenance("latest")) {
|
|
10
|
+
return {
|
|
11
|
+
state: "failed",
|
|
12
|
+
maintenance,
|
|
13
|
+
errorCode: code,
|
|
14
|
+
errorMessage: message,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Deterministic core for the Console-only maintenance operation. It contains
|
|
19
|
+
* no HTTP, model, or process-global calls, which makes the safety ordering
|
|
20
|
+
* independently testable and keeps the caller responsible for privileged
|
|
21
|
+
* handoff mechanics.
|
|
22
|
+
*/
|
|
23
|
+
export async function performConsoleUpdateAndInit(deps, channel = "latest") {
|
|
24
|
+
const preflight = await deps.preflight();
|
|
25
|
+
if (!preflight.ok) {
|
|
26
|
+
return failed(preflight.code, preflight.message, notRunMaintenance(channel));
|
|
27
|
+
}
|
|
28
|
+
const target = await deps.resolveVersion(channel);
|
|
29
|
+
if (!target.ok) {
|
|
30
|
+
return failed("REGISTRY_UNAVAILABLE", `could not verify npm ${channel} (${target.reason})`, notRunMaintenance(channel));
|
|
31
|
+
}
|
|
32
|
+
if (!semver.valid(deps.currentVersion) || !semver.valid(target.version)) {
|
|
33
|
+
return failed("INVALID_VERSION", "installed or selected channel version is invalid", notRunMaintenance(channel));
|
|
34
|
+
}
|
|
35
|
+
if (channel === "latest" && semver.lt(target.version, deps.currentVersion)) {
|
|
36
|
+
return failed("REGISTRY_VERSION_REGRESSION", `npm latest ${target.version} is older than installed ${deps.currentVersion}`, notRunMaintenance(channel));
|
|
37
|
+
}
|
|
38
|
+
const release = await deps.acquireLock();
|
|
39
|
+
if (!release) {
|
|
40
|
+
return failed("UPDATE_LOCKED", "another loop-agent update is already in progress for this user", notRunMaintenance(channel));
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const packageChanged = target.version !== deps.currentVersion;
|
|
44
|
+
const maintenance = {
|
|
45
|
+
package: {
|
|
46
|
+
status: packageChanged ? "updated" : "current",
|
|
47
|
+
channel,
|
|
48
|
+
targetVersion: target.version,
|
|
49
|
+
},
|
|
50
|
+
initialization: { status: "not-run", pendingPaths: [] },
|
|
51
|
+
handoff: { status: "not-needed" },
|
|
52
|
+
};
|
|
53
|
+
if (packageChanged) {
|
|
54
|
+
const proof = await deps.canAutoInstall();
|
|
55
|
+
if (!proof.ok) {
|
|
56
|
+
maintenance.package = { status: "failed", channel, reason: proof.reason };
|
|
57
|
+
return failed("GLOBAL_INSTALL_UNPROVEN", proof.reason, maintenance);
|
|
58
|
+
}
|
|
59
|
+
const installed = await deps.installExact(target.version, channel);
|
|
60
|
+
if (!installed.ok) {
|
|
61
|
+
maintenance.package = { status: "failed", channel, reason: installed.reason };
|
|
62
|
+
return failed("INSTALL_FAILED", installed.reason, maintenance);
|
|
63
|
+
}
|
|
64
|
+
const verified = await deps.verifyInstalled(target.version);
|
|
65
|
+
if (!verified.ok) {
|
|
66
|
+
maintenance.package = { status: "failed", channel, reason: verified.reason };
|
|
67
|
+
return failed("INSTALL_VERIFICATION_FAILED", verified.reason, maintenance);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const initResult = await deps.applySafeInit();
|
|
71
|
+
maintenance.initialization = initResult.ok
|
|
72
|
+
? { status: "clean", pendingPaths: initResult.pendingPaths }
|
|
73
|
+
: {
|
|
74
|
+
status: "needs-reconcile",
|
|
75
|
+
pendingPaths: initResult.pendingPaths,
|
|
76
|
+
message: initResult.message,
|
|
77
|
+
};
|
|
78
|
+
if (!packageChanged) {
|
|
79
|
+
return {
|
|
80
|
+
state: initResult.ok ? "succeeded" : "needs-reconcile",
|
|
81
|
+
maintenance,
|
|
82
|
+
...(initResult.ok
|
|
83
|
+
? {}
|
|
84
|
+
: {
|
|
85
|
+
errorCode: "INIT_NEEDS_RECONCILE",
|
|
86
|
+
errorMessage: initResult.message,
|
|
87
|
+
}),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const handoff = await deps.requestHandoff({
|
|
91
|
+
targetVersion: target.version,
|
|
92
|
+
initResult,
|
|
93
|
+
});
|
|
94
|
+
if (!handoff.ok) {
|
|
95
|
+
maintenance.handoff = {
|
|
96
|
+
status: "needs-reconcile",
|
|
97
|
+
message: handoff.message,
|
|
98
|
+
};
|
|
99
|
+
return {
|
|
100
|
+
state: "needs-reconcile",
|
|
101
|
+
maintenance,
|
|
102
|
+
errorCode: "HANDOFF_FAILED",
|
|
103
|
+
errorMessage: handoff.message,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
maintenance.handoff = {
|
|
107
|
+
status: "requested",
|
|
108
|
+
consoleInstanceId: handoff.consoleInstanceId,
|
|
109
|
+
};
|
|
110
|
+
return { state: "restarting", maintenance };
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
await release().catch(() => undefined);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import { readPackageVersion } from "../../shared/package-metadata.js";
|
|
3
|
+
import { createNpmUpdateClient } from "../../shared/update/npm-client.js";
|
|
4
|
+
import { acquireUpdateLock, updateLockPath } from "../../shared/update/state.js";
|
|
5
|
+
import { readInitRuntimeActivity } from "../observability/init-runtime-activity.js";
|
|
6
|
+
import { performConsoleUpdateAndInit, } from "./console-update-and-init.js";
|
|
7
|
+
import { TERMINAL_OPERATION_STATES } from "./operation-store.js";
|
|
8
|
+
export function createConsoleUpdateExecutor(input) {
|
|
9
|
+
return async (channel) => {
|
|
10
|
+
const currentVersion = readPackageVersion(input.packageRoot);
|
|
11
|
+
if (!currentVersion) {
|
|
12
|
+
return {
|
|
13
|
+
state: "failed",
|
|
14
|
+
errorCode: "PACKAGE_IDENTITY_UNAVAILABLE",
|
|
15
|
+
errorMessage: "Console package version cannot be proven",
|
|
16
|
+
maintenance: {
|
|
17
|
+
package: { status: "failed", channel },
|
|
18
|
+
initialization: { status: "not-run", pendingPaths: [] },
|
|
19
|
+
handoff: { status: "not-needed" },
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
const npm = createNpmUpdateClient({
|
|
24
|
+
currentVersion,
|
|
25
|
+
packageRoot: input.packageRoot,
|
|
26
|
+
cwd: input.repoRoot,
|
|
27
|
+
});
|
|
28
|
+
return performConsoleUpdateAndInit({
|
|
29
|
+
currentVersion,
|
|
30
|
+
preflight: async () => {
|
|
31
|
+
const [activity, operations] = await Promise.all([
|
|
32
|
+
readInitRuntimeActivity(input.repoRoot),
|
|
33
|
+
input.operations.list(),
|
|
34
|
+
]);
|
|
35
|
+
if (activity.workerProjectionError ||
|
|
36
|
+
activity.activeDagRunIds.length > 0 ||
|
|
37
|
+
activity.activeWorkerTasks > 0 ||
|
|
38
|
+
activity.activeWorkerBatches > 0) {
|
|
39
|
+
return {
|
|
40
|
+
ok: false,
|
|
41
|
+
code: "ACTIVE_RUNTIME",
|
|
42
|
+
message: "active DAG/Worker runtime exists or cannot be proven inactive",
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (input.activeChatTurnCount() > 0) {
|
|
46
|
+
return {
|
|
47
|
+
ok: false,
|
|
48
|
+
code: "CHAT_TURN_ACTIVE",
|
|
49
|
+
message: "an Operator Chat turn is active",
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const liveOtherOperation = operations.some((operation) => operation.action !== "consoleUpdateAndInit" &&
|
|
53
|
+
!TERMINAL_OPERATION_STATES.has(operation.state));
|
|
54
|
+
return liveOtherOperation
|
|
55
|
+
? {
|
|
56
|
+
ok: false,
|
|
57
|
+
code: "OPERATION_ACTIVE",
|
|
58
|
+
message: "another Console operation is active",
|
|
59
|
+
}
|
|
60
|
+
: { ok: true };
|
|
61
|
+
},
|
|
62
|
+
acquireLock: () => acquireUpdateLock(updateLockPath(os.homedir())),
|
|
63
|
+
resolveVersion: (selectedChannel) => npm.distTagVersionCheck(selectedChannel),
|
|
64
|
+
canAutoInstall: () => npm.canAutoInstall(),
|
|
65
|
+
installExact: (version, selectedChannel) => npm.installExact(version, selectedChannel),
|
|
66
|
+
verifyInstalled: (version) => npm.verifyInstalled(version),
|
|
67
|
+
applySafeInit: async () => {
|
|
68
|
+
const result = await input.client.runExternal("loop-agent", ["init", "update", "--apply-safe", "--repo-root", input.repoRoot], {
|
|
69
|
+
cwd: input.repoRoot,
|
|
70
|
+
artifactName: "console-update-safe-init",
|
|
71
|
+
timeoutMs: 120_000,
|
|
72
|
+
});
|
|
73
|
+
return result.ok
|
|
74
|
+
? { ok: true, pendingPaths: [] }
|
|
75
|
+
: {
|
|
76
|
+
ok: false,
|
|
77
|
+
message: result.stderr || result.stdout || "safe initialization failed",
|
|
78
|
+
pendingPaths: [],
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
requestHandoff: async ({ targetVersion }) => input.requestHandoff(targetVersion),
|
|
82
|
+
}, channel);
|
|
83
|
+
};
|
|
84
|
+
}
|
|
@@ -35,6 +35,32 @@ import { hashDagBytes, } from "./dag-confirmation.js";
|
|
|
35
35
|
* framework invocations that don't embed the word "verify".
|
|
36
36
|
*/
|
|
37
37
|
export const STRUCTURED_VERIFICATION_COMMAND_RE = /\b(vitest|jest|playwright|cypress|pytest|py-test|go\s+test|cargo\s+test|npm\s+run\s+(lint|typecheck|test|verify)|npm\s+(test|run\s+test)|check-repo\.sh|loop-agent-standard-verify)\b/i;
|
|
38
|
+
/**
|
|
39
|
+
* Deterministic shell preset keys whose executors are themselves structured
|
|
40
|
+
* verification/gate materializers: their shell.commands[] is empty and the
|
|
41
|
+
* preset alone carries the verification authority (hash-bound artifact
|
|
42
|
+
* gates, evidence validators, report finalize). Mirrors the named-shell
|
|
43
|
+
* handler registry in shell-executor.ts so preset-only workflows
|
|
44
|
+
* (frontend-test finalize/reports, backend-test pipeline, verify-evidence
|
|
45
|
+
* gates) pass the G2 autonomous execution gate without depending on node-id
|
|
46
|
+
* naming conventions.
|
|
47
|
+
*/
|
|
48
|
+
export const STRUCTURED_VERIFICATION_SHELL_PRESETS = new Set([
|
|
49
|
+
"frontendTestCaseChecklist",
|
|
50
|
+
"frontendTestCaseManifest",
|
|
51
|
+
"frontendTestEvidenceValidation",
|
|
52
|
+
"frontendTestResultFinalize",
|
|
53
|
+
"frontendTestL5Report",
|
|
54
|
+
"frontendTestHtmlReport",
|
|
55
|
+
"frontendTestReports",
|
|
56
|
+
"backendTestPipeline",
|
|
57
|
+
"verifyEvidence",
|
|
58
|
+
"repairArtifactGate",
|
|
59
|
+
"jacocoCoverage",
|
|
60
|
+
"requirementCoverageGate",
|
|
61
|
+
"jsonArtifactGate",
|
|
62
|
+
"verdictGate",
|
|
63
|
+
]);
|
|
38
64
|
export const DEFAULT_EXECUTION_RECEIPT_TTL_MS = 30 * 60 * 1000;
|
|
39
65
|
function normalizePathEntry(entry) {
|
|
40
66
|
return entry.trim().replace(/\\/g, "/").replace(/^\.\//, "");
|
|
@@ -157,6 +183,13 @@ export function deriveEligibilityFactsFromDagSpec(input) {
|
|
|
157
183
|
return false;
|
|
158
184
|
if (/verify|verification/i.test(task.id ?? ""))
|
|
159
185
|
return true;
|
|
186
|
+
// Preset-only verification shells (empty commands[]): the preset key is
|
|
187
|
+
// the verification authority (frontend-test finalize, backend-test
|
|
188
|
+
// pipeline, evidence/artifact gates).
|
|
189
|
+
const shellRecord = task.shell;
|
|
190
|
+
const hasVerificationPreset = Object.keys(shellRecord).some((key) => STRUCTURED_VERIFICATION_SHELL_PRESETS.has(key));
|
|
191
|
+
if (hasVerificationPreset)
|
|
192
|
+
return true;
|
|
160
193
|
const commands = stringList(task.shell.commands);
|
|
161
194
|
return commands.some((command) => STRUCTURED_VERIFICATION_COMMAND_RE.test(command));
|
|
162
195
|
});
|
|
@@ -1,5 +1,51 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { readJsonIfExists, sha256Json, writeSecureJson, } from "./app-data.js";
|
|
3
|
+
/**
|
|
4
|
+
* 从任意上游载荷安全投影 ledger 摘要(未知形状丢弃,绝不抛错、绝不信任字段外的数据)。
|
|
5
|
+
* 供 DraftStore / prd-intake-bridge 把 prepare/advance 结果中的 ledger 摘要带入草稿。
|
|
6
|
+
*/
|
|
7
|
+
export function summarizeLedgerPayload(value) {
|
|
8
|
+
if (!value || typeof value !== "object")
|
|
9
|
+
return undefined;
|
|
10
|
+
const record = value;
|
|
11
|
+
if (record.present !== true)
|
|
12
|
+
return undefined;
|
|
13
|
+
const num = (key) => {
|
|
14
|
+
const raw = record[key];
|
|
15
|
+
return typeof raw === "number" && Number.isFinite(raw) ? raw : 0;
|
|
16
|
+
};
|
|
17
|
+
const gateRaw = record.gateStatus;
|
|
18
|
+
const gateStatus = gateRaw === "pass" || gateRaw === "fail" || gateRaw === "unknown"
|
|
19
|
+
? gateRaw
|
|
20
|
+
: "unknown";
|
|
21
|
+
const failureCode = typeof record.failureCode === "string" && record.failureCode
|
|
22
|
+
? record.failureCode
|
|
23
|
+
: null;
|
|
24
|
+
const rawExclusions = Array.isArray(record.exclusions) ? record.exclusions : [];
|
|
25
|
+
const exclusions = [];
|
|
26
|
+
for (const item of rawExclusions) {
|
|
27
|
+
if (!item || typeof item !== "object")
|
|
28
|
+
continue;
|
|
29
|
+
const entry = item;
|
|
30
|
+
const fragmentId = typeof entry.fragmentId === "string" ? entry.fragmentId : "";
|
|
31
|
+
const reason = typeof entry.reason === "string" ? entry.reason : "";
|
|
32
|
+
if (!fragmentId)
|
|
33
|
+
continue;
|
|
34
|
+
exclusions.push({ fragmentId, reason });
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
present: true,
|
|
38
|
+
fragmentCount: num("fragmentCount"),
|
|
39
|
+
canonicalRequirementCount: num("canonicalRequirementCount"),
|
|
40
|
+
contextOnlyCount: num("contextOnlyCount"),
|
|
41
|
+
duplicateCount: num("duplicateCount"),
|
|
42
|
+
unresolvedCount: num("unresolvedCount"),
|
|
43
|
+
conflictCount: num("conflictCount"),
|
|
44
|
+
gateStatus,
|
|
45
|
+
failureCode,
|
|
46
|
+
exclusions,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
3
49
|
function draftPath(appData, taskId) {
|
|
4
50
|
const safe = taskId.replace(/[^a-zA-Z0-9._-]+/g, "_");
|
|
5
51
|
return path.join(appData.drafts, `${safe}.json`);
|
|
@@ -86,6 +132,9 @@ export function toCanonicalDraftForCli(draft) {
|
|
|
86
132
|
if (draft.interviewAssessmentRef) {
|
|
87
133
|
canonical.interviewAssessmentRef = draft.interviewAssessmentRef;
|
|
88
134
|
}
|
|
135
|
+
// AC-BLOCK-002:canonical draft JSON 绝不携带 ledgerSummary 字段(strict
|
|
136
|
+
// taskContractDraftSchema 拒绝未知键)。摘要仅留在 prd-intake-bridge 响应与
|
|
137
|
+
// console-types 的 LedgerSummaryView(UI 视图模型),不进入持久化 draft。
|
|
89
138
|
return canonical;
|
|
90
139
|
}
|
|
91
140
|
export class DraftStore {
|
|
@@ -80,3 +80,21 @@ export function pickInspectNode(nodes, preferredNodeId) {
|
|
|
80
80
|
});
|
|
81
81
|
return running ?? nodes[0];
|
|
82
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Terminal DagRun effective/status values (mirrors observe read-model
|
|
85
|
+
* TERMINAL_RUN_STATUSES). A run that reached one of these states no longer
|
|
86
|
+
* needs live polling in the console split panel.
|
|
87
|
+
*/
|
|
88
|
+
export const TERMINAL_DAG_RUN_STATUSES = new Set([
|
|
89
|
+
"finished",
|
|
90
|
+
"failed",
|
|
91
|
+
"partial_failed",
|
|
92
|
+
"superseded",
|
|
93
|
+
"abandoned",
|
|
94
|
+
]);
|
|
95
|
+
export function isTerminalDagRun(dag) {
|
|
96
|
+
if (!dag)
|
|
97
|
+
return false;
|
|
98
|
+
const status = String(dag.effectiveStatus ?? dag.status ?? "").toLowerCase();
|
|
99
|
+
return TERMINAL_DAG_RUN_STATUSES.has(status);
|
|
100
|
+
}
|