@tea-agent/loop-agent 0.39.0-next.8 → 0.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +4 -1
- package/CHANGELOG.md +264 -67
- package/README.md +11 -5
- package/bin/loop-agent.js +7 -3
- package/dist/application/dag/generate-task-dag.js +9 -0
- package/dist/application/task-lifecycle/advance.js +4 -0
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +16 -5
- package/dist/cli/program.js +16 -2
- package/dist/cli/update/notifier.js +2 -2
- package/dist/cli.js +2 -1
- package/dist/commands/dag-rerun.js +55 -1
- package/dist/commands/init-model-catalog.js +464 -0
- package/dist/commands/init-upgrade.js +265 -97
- package/dist/commands/init.js +428 -27
- package/dist/commands/inspect-next.js +8 -0
- package/dist/commands/task-advance.js +31 -0
- package/dist/executors/dag-pi-executor.js +124 -18
- package/dist/executors/pi-event-serializer.js +42 -1
- package/dist/executors/pi-executor.js +51 -0
- package/dist/executors/pi-extension-resolver.js +233 -0
- package/dist/executors/pi-playwright-cli-tool.js +74 -28
- package/dist/executors/pi-sdk-executor.js +187 -59
- package/dist/executors/shell-executor.js +554 -33
- package/dist/executors/shell-write-guard.js +7 -0
- package/dist/shared/dag-failure-category.js +138 -0
- package/dist/shared/dag-prompt-override.js +28 -0
- package/dist/shared/operator/capabilities.js +181 -27
- package/dist/shared/playwright-cli-command-policy.js +15 -0
- package/dist/shared/update/console-notifier.js +100 -0
- package/dist/{cli → shared}/update/npm-client.js +41 -8
- package/dist/{cli → shared}/update/state.js +41 -13
- package/dist/task/config-types.js +28 -0
- package/dist/task/contract/constants.js +1 -0
- package/dist/task/contract/diff.js +26 -0
- package/dist/task/contract/project.js +5 -0
- package/dist/task/contract/schema.js +6 -1
- package/dist/task/source-prepare/build-draft.js +15 -0
- package/dist/task/source-prepare/parse-intent.js +24 -4
- package/dist/worker/cli.js +31 -1
- package/dist/worker/console/chat/deferred-turn.js +91 -0
- package/dist/worker/console/chat/mcp-inventory.js +221 -0
- package/dist/worker/console/chat/operation-card.js +8 -1
- package/dist/worker/console/chat/pi-console-config.js +81 -10
- package/dist/worker/console/chat/pi-runtime.js +165 -83
- package/dist/worker/console/chat/repo-walk.js +10 -1
- package/dist/worker/console/chat/routes.js +480 -55
- package/dist/worker/console/chat/semantic-activity.js +20 -8
- package/dist/worker/console/chat/session-store.js +146 -2
- package/dist/worker/console/chat/turn-process.js +10 -10
- package/dist/worker/console/dag-execution-receipt.js +33 -0
- package/dist/worker/console/inspect-split.js +18 -0
- package/dist/worker/console/operation-run-facts.js +19 -3
- package/dist/worker/console/operator-actions.js +195 -4
- package/dist/worker/console/operator-user-error.js +2 -2
- package/dist/worker/console/pi-readiness.js +5 -4
- package/dist/worker/console/recovery-cta.js +4 -4
- package/dist/worker/console/recovery-error-copy.js +4 -4
- package/dist/worker/console/routes.js +43 -2
- package/dist/worker/console/security.js +63 -9
- package/dist/worker/console/server.js +42 -6
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-BLXFe4qI.js → abnfDiagram-N423BO3Z-Br92EGb7.js} +1 -1
- package/dist/worker/console/static/assets/{arc-7iOJeqho.js → arc-oulImtQq.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CYoyj5K7.js → architectureDiagram-T3A2C74G-tyHc6aqO.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-fK01JNlR.js → blockDiagram-VBNYF7ZC-CY86BSpT.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-bzbw5D_Z.js → c4Diagram-5PPSVZJV-BKKsDmNs.js} +1 -1
- package/dist/worker/console/static/assets/channel-TY36zt-i.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-Cf6hJFFG.js → chunk-2GRJ4B5K-CXZdjl3B.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DSFXR-et.js → chunk-2Q5K7J3B-B-aIFDQ5.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BagJechl.js → chunk-5RXB4S5H-BDgAW_B2.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-E7QNMFaW.js → chunk-5VM5RSS4-DABa4UPR.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BG_eOBrC.js → chunk-6Q2QTUOP-aGbysVOx.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-Bo42dqol.js → chunk-GF5L2VYU-B8MSZDVs.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-dibM-87v.js → chunk-JWPE2WC7-CpelV73g.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-Bytrp3Oe.js → chunk-KBJHAD2P-BE04ty3z.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DBABILrr.js → chunk-RYQCIY6F-BUxTkaaR.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-DCUk5Pf9.js → chunk-XXDRQBXY-CYYBVKfX.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Dk7QCChM.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Dk7QCChM.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BlGE9v6E.js → cose-bilkent-JH36ORCC-CEQF_xmQ.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-DGl5ewph.js → cynefin-VYW2F7L2-CqpxN5IT.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-DsJ_SVH_.js → cynefinDiagram-MW4NZA55-B3PzanVi.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BIhjFdzv.js → dagre-VZM6K2ZE-BSUGCNx1.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Dmy1ctTb.js → diagram-7IWD3JNH-CdLevU5T.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-DYCtWUF3.js → diagram-B4RE2ZJO-BjpF_Kok.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-BMZFHVd2.js → diagram-LBJQPF4R-C4I6NJny.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-b_U8gfaO.js → diagram-Q27KOJAE-C8hecCr5.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-Dzy_bn-I.js → diagram-UB23O5K3-Daod6tOS.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-DJCiMNEI.js → ebnfDiagram-BXEA7PRR-BvvmazGv.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-28lD016B.js → erDiagram-JOGREHBK-DeN_vfV4.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-DGyHJmIw.js → flowDiagram-UKHOOZJN-D6dq1t9z.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CzdwUjrO.js → ganttDiagram-PKOTCBZU-DziIdNY4.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-D9bGwuo8.js → gitGraphDiagram-DS77QQ5N-Dy4s5CXr.js} +1 -1
- package/dist/worker/console/static/assets/index-C3GgHg-g.css +1 -0
- package/dist/worker/console/static/assets/index-Ci8ksvBm.js +406 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-LJgJUQFC.js → infoDiagram-6WML65LV-Czv5eYx2.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-98LaB8zb.js → ishikawaDiagram-WSZJBQD7-Bsk3nFRT.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-D-ISzwlQ.js → journeyDiagram-NVQOT4AX-DxGmNj5K.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-gfYOI-EL.js → kanban-definition-27J2QSJJ-CYCFqkO_.js} +1 -1
- package/dist/worker/console/static/assets/{linear-CyntZgKV.js → linear-B0YzRwrd.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-BBOR7dg0.js → mermaid.core-t3EZtIGh.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-CQAgwPMk.js → mindmap-definition-FAOFIHXS-BTXpTv0T.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DkMiZFuV.js → pegDiagram-VL7TDLO6-DU8UkKqA.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DLNXa_0J.js → pieDiagram-7S7Q4E2Y-DP4y8ohp.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-Dc7bdzjm.js → quadrantDiagram-CIZ2JOQS-B07vNaYR.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BxMB0QqW.js → railroadDiagram-AXF67PYL-xDWktb2i.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-FjumoKn4.js → requirementDiagram-LRYGKXZP-BLNMKqeS.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-CkhKc_E3.js → sankeyDiagram-W5VNT64P-n0adalxH.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DoBWqTTH.js → sequenceDiagram-SI44F4Z6-BbnS-2DL.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-CQeh_YeE.js → sizeCapture-X5ZJPWSS-B4uPpCLu.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-LuHmFW-I.js → stateDiagram-OKZ733FA--MMbB0fX.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BqOigMQ4.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CHwbDctY.js → swimlanes-SLNWSIFB-BXUgp2QY.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Caxz9UA2.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CoMngk0X.js → timeline-definition-Z64GVDOM-CsaMxki7.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-CURYbtbO.js → vennDiagram-T6HMQDX7-C2IuG9iH.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-BdqSECjU.js → wardleyDiagram-T6FBY63Y-CmEWOXCW.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-DbMUYuAg.js → xychartDiagram-ELKLHX3M-D_9LE0MG.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/useOperatorActions.js +1 -1
- package/dist/worker/console/static-src/app/useRecoveryActions.js +2 -2
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +38 -2
- package/dist/worker/console/static-src/app/useRunProgress.js +46 -0
- package/dist/worker/console/static-src/operator-chat/cards/failure-category-advice.js +25 -0
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +74 -26
- package/dist/worker/console/static-src/operator-chat/input-history.js +76 -0
- package/dist/worker/console/static-src/operator-chat/mutation-gate.js +1 -1
- package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +328 -0
- package/dist/worker/console/static-src/operator-chat/resource-diagnostics.js +204 -0
- package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +8 -1
- package/dist/worker/console/static-src/operator-chat/turn-submission.js +54 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +3 -0
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +237 -11
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +50 -22
- package/dist/worker/console/static-src/operator-chat/useComposer.js +32 -0
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +26 -1
- package/dist/worker/console/static-src/pages/tasks/run-id-resolution.js +79 -0
- package/dist/worker/console/static-src/pages/tasks/run-ownership-verify.js +23 -0
- package/dist/worker/console/static-src/pages/tasks/run-panel-progress.js +110 -0
- package/dist/worker/loop-agent/loop-agent-client.js +7 -3
- package/dist/worker/materialize/harness-task-lineage.js +5 -2
- package/dist/worker/observability/read-model.js +22 -0
- package/dist/worker/observe/node-input.js +74 -3
- package/dist/worker/observe/node-process.js +377 -0
- package/dist/worker/observe/routes.js +36 -3
- package/dist/worker/observe/shell-handler-keys.js +34 -0
- package/dist/worker/observe/static/api.js +66 -0
- package/dist/worker/observe/static/constants.js +8 -1
- package/dist/worker/observe/static/dag-history-labels.js +4 -0
- package/dist/worker/observe/static/format.js +23 -0
- package/dist/worker/observe/static/markdown-render.js +20 -1
- package/dist/worker/observe/static/operator-chrome.js +4 -0
- package/dist/worker/observe/static/state.js +13 -2
- package/dist/worker/observe/static/styles.css +73 -0
- package/dist/worker/observe/static/views/dag-inspector.js +612 -8
- package/dist/worker/observe/static/views/dag.js +8 -1
- package/dist/worker/observe/static/views/failures.js +5 -2
- package/dist/worker/observe/static/views/session-timeline.js +78 -9
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +827 -53
- package/dist/workflows/dag/backend-test-case-manifest.js +4 -0
- package/dist/workflows/dag/backend-test-execution-contract.js +18 -9
- package/dist/workflows/dag/backend-test-gap-fill.js +205 -0
- package/dist/workflows/dag/backend-test-intake-context.js +32 -0
- package/dist/workflows/dag/backend-test-layout.js +133 -0
- package/dist/workflows/dag/backend-test-markdown-workflow.js +167 -32
- package/dist/workflows/dag/backend-test-module-stem.js +5 -0
- package/dist/workflows/dag/backend-test-pytest-collection.js +368 -33
- package/dist/workflows/dag/backend-test-result-contract.js +1 -1
- package/dist/workflows/dag/backend-test-scenario-param.js +884 -140
- package/dist/workflows/dag/backend-test-scenario-partitions.js +321 -0
- package/dist/workflows/dag/backend-test-writer-completeness.js +100 -56
- package/dist/workflows/dag/contract-output-registry.js +15 -0
- package/dist/workflows/dag/contract-validator-registrations.js +3 -1
- package/dist/workflows/dag/convergence/controller.js +141 -0
- package/dist/workflows/dag/dynamic-runtime/loop-until.js +1 -1
- package/dist/workflows/dag/dynamic-runtime/map.js +25 -9
- package/dist/workflows/dag/dynamic-runtime/shared.js +1 -1
- package/dist/workflows/dag/failure-category.js +7 -116
- package/dist/workflows/dag/frontend-implementation-contract.js +377 -4
- package/dist/workflows/dag/frontend-plan-render.js +1 -1
- package/dist/workflows/dag/frontend-prewrite-gate.js +96 -84
- package/dist/workflows/dag/frontend-repair.js +14 -0
- package/dist/workflows/dag/frontend-risk.js +15 -2
- package/dist/workflows/dag/frontend-test-case-checklist.js +30 -4
- package/dist/workflows/dag/frontend-test-case-manifest.js +11 -4
- package/dist/workflows/dag/frontend-test-case-quality.js +11 -16
- package/dist/workflows/dag/frontend-test-environment-probe.js +230 -0
- package/dist/workflows/dag/frontend-test-html-report.js +3 -1
- package/dist/workflows/dag/frontend-test-l5-report.js +3 -1
- package/dist/workflows/dag/frontend-test-layout.js +159 -0
- package/dist/workflows/dag/frontend-test-markdown.js +61 -0
- package/dist/workflows/dag/frontend-test-result-contract.js +188 -42
- package/dist/workflows/dag/frontend-test-standard-scenarios.js +70 -0
- package/dist/workflows/dag/frontend-verification-trace.js +29 -4
- package/dist/workflows/dag/init-hybrid.js +894 -205
- package/dist/workflows/dag/node-execution.js +183 -15
- package/dist/workflows/dag/prompt.js +60 -1
- package/dist/workflows/dag/rerun-feedback.js +212 -0
- package/dist/workflows/dag/rerun-plan.js +224 -27
- package/dist/workflows/dag/rerun-run.js +61 -6
- package/dist/workflows/dag/rerun-task.js +61 -2
- package/dist/workflows/dag/retry-policy.js +109 -0
- package/dist/workflows/dag/structured-output-repair.js +712 -0
- package/dist/workflows/dag/types.js +143 -1
- package/dist/workflows/dag/validate.js +64 -20
- package/docs/init-surface.manifest.json +5 -0
- package/docs/operations/README.md +1 -1
- package/docs/operations/local-development-environment.md +1 -5
- package/docs/skills/vetted-skill-registry.md +14 -0
- package/docs/templates/README.md +1 -1
- package/docs/templates/agent-dag.schema.json +44 -0
- package/docs/templates/backend-test-dag.json +60 -35
- package/docs/templates/frontend-implementation-contract.schema.json +1 -1
- package/docs/templates/frontend-test-case-checklist.md +2 -2
- package/docs/templates/frontend-test-dag.json +8 -10
- package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
- package/docs/templates/init-managed-agents.md +13 -3
- package/harness.json +6 -4
- package/package.json +12 -4
- package/scripts/next-info.mjs +356 -0
- package/scripts/next-publish-gate.mjs +238 -0
- package/scripts/release-source-binding.mjs +251 -0
- package/skills/analyze-product-requirements/SKILL.md +8 -5
- package/skills/analyze-product-requirements/references/acceptance-criteria.md +1 -1
- package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +10 -6
- package/skills/analyze-product-requirements/references/example.md +50 -0
- package/skills/analyze-product-requirements/references/forward-test-cases.md +11 -7
- package/skills/analyze-product-requirements/references/kb-integration.md +5 -5
- package/skills/analyze-product-requirements/references/product-analysis-schema.md +8 -4
- package/skills/analyze-product-requirements/references/product-requirement-schema.md +2 -2
- package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +1 -1
- package/skills/analyze-product-requirements/scripts/test-validators.mjs +11 -1
- package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +20 -1
- package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +9 -0
- package/skills/codebase-scout/SKILL.md +1 -1
- package/skills/fe-test-ui-scout/SKILL.md +65 -0
- package/skills/fe-test-ui-scout/references/ledger-schema.md +62 -0
- package/skills/fe-test-ui-scout/references/recon-protocol.md +54 -0
- package/skills/frontend-implementation/references/node-contracts.md +2 -2
- package/skills/improve-codebase-architecture/SKILL.md +81 -0
- package/skills/improve-codebase-architecture/deepening.md +37 -0
- package/skills/improve-codebase-architecture/html-report.md +123 -0
- package/skills/improve-codebase-architecture/interface-design.md +44 -0
- package/skills/improve-codebase-architecture/language.md +53 -0
- package/skills/loop-agent/references/command-reference.md +11 -4
- package/skills/playwright-cli/SKILL.md +23 -1
- package/skills/playwright-cli-case-generator/SKILL.md +29 -8
- package/dist/worker/console/static/assets/channel-BBOTJHvD.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Cm8Yf8tD.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Cm8Yf8tD.js +0 -1
- package/dist/worker/console/static/assets/index-B-qWGThd.js +0 -325
- package/dist/worker/console/static/assets/index-C-xRgY_0.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DZihfdKc.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CKnXCjHX.js +0 -8
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { createInterface } from "node:readline/promises";
|
|
2
|
+
import semver from "semver";
|
|
3
|
+
import { acquireUpdateLock, readUpdateState, updateLockPath, updateStatePath, writeUpdateState, } from "./state.js";
|
|
4
|
+
export async function runConsoleDailyUpdateCheck(options) {
|
|
5
|
+
void options.writeStdout;
|
|
6
|
+
void options.packageRoot;
|
|
7
|
+
void options.cwd;
|
|
8
|
+
const writeStderr = options.writeStderr ?? ((chunk) => process.stderr.write(chunk));
|
|
9
|
+
const now = options.now ?? (() => new Date());
|
|
10
|
+
try {
|
|
11
|
+
if (!canPromptInteractively(options))
|
|
12
|
+
return;
|
|
13
|
+
const statePath = updateStatePath(options.homeDir);
|
|
14
|
+
const today = formatLocalDate(now());
|
|
15
|
+
const existing = await readUpdateState(statePath);
|
|
16
|
+
if (existing.lastPromptedLocalDate === today)
|
|
17
|
+
return;
|
|
18
|
+
const release = await acquireUpdateLock(updateLockPath(options.homeDir), {
|
|
19
|
+
now,
|
|
20
|
+
});
|
|
21
|
+
if (!release)
|
|
22
|
+
return;
|
|
23
|
+
try {
|
|
24
|
+
const state = await readUpdateState(statePath);
|
|
25
|
+
if (state.lastPromptedLocalDate === today)
|
|
26
|
+
return;
|
|
27
|
+
const check = await options.client.latestVersionCheck();
|
|
28
|
+
if (!check.ok)
|
|
29
|
+
return;
|
|
30
|
+
if (!semver.gt(check.version, options.currentVersion))
|
|
31
|
+
return;
|
|
32
|
+
const parsedLatest = semver.parse(check.version);
|
|
33
|
+
if (!parsedLatest || parsedLatest.prerelease.length > 0)
|
|
34
|
+
return;
|
|
35
|
+
await markPromptedToday(statePath, state, today);
|
|
36
|
+
const answer = await ask(options, `loop-agent 有新版本:${options.currentVersion} → ${check.version}\n现在更新到 ${check.version}?[y/N] `);
|
|
37
|
+
if (!isYes(answer))
|
|
38
|
+
return;
|
|
39
|
+
const proof = await options.client.canAutoInstall();
|
|
40
|
+
if (!proof.ok) {
|
|
41
|
+
writeStderr(`当前 loop-agent 不是由此 npm 的正式全局安装管理,无法自动升级。\n请运行:npm install -g @tea-agent/loop-agent@${check.version}\n`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const install = await options.client.installExact(check.version);
|
|
45
|
+
if (!install.ok) {
|
|
46
|
+
writeStderr(`loop-agent 更新失败:${install.reason}\n`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const verified = await options.client.verifyInstalled(check.version);
|
|
50
|
+
if (!verified.ok) {
|
|
51
|
+
writeStderr(`loop-agent 已尝试安装,但验证失败:${verified.reason}\n`);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
writeStderr(`loop-agent 已更新到 ${check.version};新版本将在下一次命令调用时完全生效。\n`);
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
await release().catch(() => undefined);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
writeStderr(`loop-agent 更新检查已跳过:${error instanceof Error ? error.message : String(error)}\n`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function canPromptInteractively(options) {
|
|
65
|
+
if (!options.stdinIsTTY || !options.stderrIsTTY)
|
|
66
|
+
return false;
|
|
67
|
+
if (options.env.CI)
|
|
68
|
+
return false;
|
|
69
|
+
if (options.env.LOOP_AGENT_DISABLE_UPDATE_CHECK === "1")
|
|
70
|
+
return false;
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
function formatLocalDate(date) {
|
|
74
|
+
const year = date.getFullYear();
|
|
75
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
76
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
77
|
+
return `${year}-${month}-${day}`;
|
|
78
|
+
}
|
|
79
|
+
async function markPromptedToday(statePath, state, today) {
|
|
80
|
+
await writeUpdateState(statePath, {
|
|
81
|
+
...state,
|
|
82
|
+
lastPromptedLocalDate: today,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
async function ask(options, prompt) {
|
|
86
|
+
if (options.readLine) {
|
|
87
|
+
options.writeStderr?.(prompt);
|
|
88
|
+
return options.readLine(prompt);
|
|
89
|
+
}
|
|
90
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
91
|
+
try {
|
|
92
|
+
return await rl.question(prompt);
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
rl.close();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function isYes(answer) {
|
|
99
|
+
return /^(y|yes)$/i.test(answer.trim());
|
|
100
|
+
}
|
|
@@ -3,25 +3,48 @@ import { lstat, readFile, realpath } from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import semver from "semver";
|
|
5
5
|
const PACKAGE_NAME = "@tea-agent/loop-agent";
|
|
6
|
+
export const DEFAULT_NPM_QUERY_TIMEOUT_MS = 15_000;
|
|
6
7
|
export function createNpmUpdateClient(options) {
|
|
7
8
|
const npmCommand = options.npmCommand ?? "npm";
|
|
8
9
|
const loopAgentCommand = options.loopAgentCommand ?? "loop-agent";
|
|
9
10
|
const runner = options.runner ?? runCommand;
|
|
10
|
-
const timeoutMs = options.timeoutMs ??
|
|
11
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_NPM_QUERY_TIMEOUT_MS;
|
|
12
|
+
const cwd = options.cwd;
|
|
11
13
|
const env = options.env;
|
|
14
|
+
const latestVersionCheck = async () => {
|
|
15
|
+
let result;
|
|
16
|
+
try {
|
|
17
|
+
result = await runner(npmCommand, ["view", PACKAGE_NAME, "dist-tags.latest", "--json"], { timeoutMs, cwd, env });
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return { ok: false, reason: "spawn-failure" };
|
|
21
|
+
}
|
|
22
|
+
if (result.timedOut)
|
|
23
|
+
return { ok: false, reason: "timeout" };
|
|
24
|
+
if (result.exitCode === null) {
|
|
25
|
+
return { ok: false, reason: "spawn-failure" };
|
|
26
|
+
}
|
|
27
|
+
if (result.exitCode !== 0) {
|
|
28
|
+
return { ok: false, reason: "command-failed" };
|
|
29
|
+
}
|
|
30
|
+
const version = parseNpmViewLatest(result.stdout);
|
|
31
|
+
return version
|
|
32
|
+
? { ok: true, version }
|
|
33
|
+
: { ok: false, reason: "invalid-response" };
|
|
34
|
+
};
|
|
12
35
|
return {
|
|
13
36
|
async latestVersion() {
|
|
14
|
-
const result = await
|
|
15
|
-
|
|
16
|
-
return undefined;
|
|
17
|
-
return parseNpmViewLatest(result.stdout);
|
|
37
|
+
const result = await latestVersionCheck();
|
|
38
|
+
return result.ok ? result.version : undefined;
|
|
18
39
|
},
|
|
40
|
+
latestVersionCheck,
|
|
19
41
|
async canAutoInstall() {
|
|
20
42
|
return proveGlobalNpmInstall({
|
|
21
43
|
currentVersion: options.currentVersion,
|
|
22
44
|
packageRoot: options.packageRoot,
|
|
23
45
|
npmCommand,
|
|
24
46
|
runner,
|
|
47
|
+
cwd,
|
|
25
48
|
env,
|
|
26
49
|
timeoutMs,
|
|
27
50
|
});
|
|
@@ -29,17 +52,25 @@ export function createNpmUpdateClient(options) {
|
|
|
29
52
|
async installExact(version) {
|
|
30
53
|
if (!isStrictRelease(version))
|
|
31
54
|
return { ok: false, reason: "invalid-version" };
|
|
32
|
-
const result = await runner(npmCommand, buildExactInstallArgs(version), {
|
|
55
|
+
const result = await runner(npmCommand, buildExactInstallArgs(version), {
|
|
56
|
+
timeoutMs: Math.max(timeoutMs, 120000),
|
|
57
|
+
cwd,
|
|
58
|
+
env,
|
|
59
|
+
});
|
|
33
60
|
if (result.exitCode !== 0 || result.timedOut)
|
|
34
61
|
return { ok: false, reason: "install-failed" };
|
|
35
62
|
return { ok: true };
|
|
36
63
|
},
|
|
37
64
|
async verifyInstalled(version) {
|
|
38
|
-
const list = await runner(npmCommand, ["list", "-g", PACKAGE_NAME, "--depth=0", "--json"], { timeoutMs, env });
|
|
65
|
+
const list = await runner(npmCommand, ["list", "-g", PACKAGE_NAME, "--depth=0", "--json"], { timeoutMs, cwd, env });
|
|
39
66
|
if (list.exitCode !== 0 || !verifyNpmListVersion(list.stdout, version)) {
|
|
40
67
|
return { ok: false, reason: "npm-list-version-mismatch" };
|
|
41
68
|
}
|
|
42
|
-
const pathVersion = await runner(loopAgentCommand, ["--version"], {
|
|
69
|
+
const pathVersion = await runner(loopAgentCommand, ["--version"], {
|
|
70
|
+
timeoutMs,
|
|
71
|
+
cwd,
|
|
72
|
+
env,
|
|
73
|
+
});
|
|
43
74
|
if (pathVersion.exitCode !== 0 || !verifyPathVersion(pathVersion.stdout, version)) {
|
|
44
75
|
return { ok: false, reason: "path-version-mismatch" };
|
|
45
76
|
}
|
|
@@ -80,6 +111,7 @@ export function verifyPathVersion(stdout, expectedVersion) {
|
|
|
80
111
|
async function proveGlobalNpmInstall(options) {
|
|
81
112
|
const root = await options.runner(options.npmCommand, ["root", "-g"], {
|
|
82
113
|
timeoutMs: options.timeoutMs,
|
|
114
|
+
cwd: options.cwd,
|
|
83
115
|
env: options.env,
|
|
84
116
|
});
|
|
85
117
|
if (root.exitCode !== 0 || root.timedOut)
|
|
@@ -113,6 +145,7 @@ function isStrictRelease(version) {
|
|
|
113
145
|
async function runCommand(command, args, options) {
|
|
114
146
|
return new Promise((resolve) => {
|
|
115
147
|
const child = spawn(command, [...args], {
|
|
148
|
+
cwd: options.cwd,
|
|
116
149
|
env: options.env ? { ...process.env, ...options.env } : process.env,
|
|
117
150
|
stdio: ["ignore", "pipe", "pipe"],
|
|
118
151
|
shell: false,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
1
|
+
import { mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
export function updateStatePath(homeDir) {
|
|
4
4
|
return path.join(homeDir, ".loop-agent", "update-state.json");
|
|
@@ -18,6 +18,7 @@ export async function readUpdateState(statePath) {
|
|
|
18
18
|
latestVersion: stringOrUndefined(parsed.latestVersion),
|
|
19
19
|
dismissedVersion: stringOrUndefined(parsed.dismissedVersion),
|
|
20
20
|
retryAfter: stringOrUndefined(parsed.retryAfter),
|
|
21
|
+
lastPromptedLocalDate: stringOrUndefined(parsed.lastPromptedLocalDate),
|
|
21
22
|
};
|
|
22
23
|
}
|
|
23
24
|
catch {
|
|
@@ -36,28 +37,55 @@ export async function acquireUpdateLock(lockPath, options = {}) {
|
|
|
36
37
|
await mkdir(path.dirname(lockPath), { recursive: true });
|
|
37
38
|
try {
|
|
38
39
|
await mkdir(lockPath);
|
|
40
|
+
return await claimLock(lockPath, now);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
if (await isFreshLock(lockPath, now, staleMs))
|
|
44
|
+
return undefined;
|
|
45
|
+
try {
|
|
46
|
+
await rm(lockPath, { recursive: true, force: true });
|
|
47
|
+
await mkdir(lockPath);
|
|
48
|
+
return await claimLock(lockPath, now);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function claimLock(lockPath, now) {
|
|
56
|
+
try {
|
|
39
57
|
await writeFile(path.join(lockPath, "owner.json"), JSON.stringify({ pid: process.pid, at: now().toISOString() }), "utf-8");
|
|
40
58
|
return async () => {
|
|
41
59
|
await rm(lockPath, { recursive: true, force: true });
|
|
42
60
|
};
|
|
43
61
|
}
|
|
62
|
+
catch {
|
|
63
|
+
await rm(lockPath, { recursive: true, force: true }).catch(() => undefined);
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async function isFreshLock(lockPath, now, staleMs) {
|
|
68
|
+
try {
|
|
69
|
+
const ownerRaw = await readFile(path.join(lockPath, "owner.json"), "utf-8");
|
|
70
|
+
const owner = JSON.parse(ownerRaw);
|
|
71
|
+
if (typeof owner.at !== "string")
|
|
72
|
+
return false;
|
|
73
|
+
const at = Date.parse(owner.at);
|
|
74
|
+
if (!Number.isFinite(at))
|
|
75
|
+
return false;
|
|
76
|
+
return now().getTime() - at <= staleMs;
|
|
77
|
+
}
|
|
44
78
|
catch {
|
|
45
79
|
try {
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
await writeFile(path.join(lockPath, "owner.json"), JSON.stringify({ pid: process.pid, at: now().toISOString() }), "utf-8");
|
|
52
|
-
return async () => {
|
|
53
|
-
await rm(lockPath, { recursive: true, force: true });
|
|
54
|
-
};
|
|
55
|
-
}
|
|
80
|
+
const info = await stat(lockPath);
|
|
81
|
+
const mtime = Number.isFinite(info.mtimeMs)
|
|
82
|
+
? info.mtimeMs
|
|
83
|
+
: info.mtime.getTime();
|
|
84
|
+
return now().getTime() - mtime <= staleMs;
|
|
56
85
|
}
|
|
57
86
|
catch {
|
|
58
|
-
return
|
|
87
|
+
return false;
|
|
59
88
|
}
|
|
60
|
-
return undefined;
|
|
61
89
|
}
|
|
62
90
|
}
|
|
63
91
|
function emptyState() {
|
|
@@ -87,6 +87,12 @@ export const frontendOpenspecConfigSchema = z.object({
|
|
|
87
87
|
/** Batch limits and gate profile for the browser-driven frontend test DAG.
|
|
88
88
|
* Token fields are post-case stop thresholds, not model-provider hard caps. */
|
|
89
89
|
export const frontendTestConfigSchema = z.object({
|
|
90
|
+
/**
|
|
91
|
+
* Repository-relative frontend-test artifact root.
|
|
92
|
+
* Default "testcase/frontend". Semantic validation lives in
|
|
93
|
+
* resolveFrontendTestLayout (fail-closed), not in this schema.
|
|
94
|
+
*/
|
|
95
|
+
testRoot: z.string().min(1).optional(),
|
|
90
96
|
maxCasesPerBatch: z.number().int().min(1).max(50).optional(),
|
|
91
97
|
maxTokensPerCase: z.number().int().positive().optional(),
|
|
92
98
|
maxTotalTokens: z.number().int().positive().optional(),
|
|
@@ -124,6 +130,26 @@ export const frontendTestConfigSchema = z.object({
|
|
|
124
130
|
.strict()
|
|
125
131
|
.optional(),
|
|
126
132
|
});
|
|
133
|
+
/** Backend-test artifact layout and pipeline mode configuration (plan A/B/D). */
|
|
134
|
+
export const backendTestConfigSchema = z
|
|
135
|
+
.object({
|
|
136
|
+
/** Repository-relative test root. Default "testcase". */
|
|
137
|
+
testRoot: z.string().min(1).optional(),
|
|
138
|
+
/** Markdown subdirectory relative to testRoot. Default "md". */
|
|
139
|
+
markdownRel: z.string().min(1).optional(),
|
|
140
|
+
/** Pytest script subdirectory relative to testRoot. Default "." (testRoot itself). */
|
|
141
|
+
scriptRel: z.string().min(1).optional(),
|
|
142
|
+
/** User-provided shared setup document (plan B). Read-only bound source. */
|
|
143
|
+
sharedSetupDoc: z.string().min(1).optional(),
|
|
144
|
+
/** Pipeline mode: full 16-node chain (default) or gap-fill incremental DAG (plan D). */
|
|
145
|
+
mode: z.enum(["full", "gap-fill"]).optional(),
|
|
146
|
+
/** Required for mode=gap-fill: user missing-scenario document path. */
|
|
147
|
+
gapDoc: z.string().min(1).optional(),
|
|
148
|
+
/** gap-fill pytest execution scope: only affected items (default) or all eligible. */
|
|
149
|
+
executeScope: z.enum(["affected", "all"]).optional(),
|
|
150
|
+
})
|
|
151
|
+
.strict()
|
|
152
|
+
.optional();
|
|
127
153
|
export const convergenceConfigSchema = z.object({
|
|
128
154
|
enabled: z.boolean().optional().default(false),
|
|
129
155
|
maxPasses: z.number().int().positive().optional().default(3),
|
|
@@ -196,6 +222,8 @@ const taskConfigObjectSchema = z.object({
|
|
|
196
222
|
frontendTest: frontendTestConfigSchema.optional(),
|
|
197
223
|
/** Frontend OpenSpec gate policy and contract-declared required read paths. */
|
|
198
224
|
frontendOpenspec: frontendOpenspecConfigSchema.optional(),
|
|
225
|
+
/** Backend-test layout / shared-setup / gap-fill configuration. */
|
|
226
|
+
backendTest: backendTestConfigSchema,
|
|
199
227
|
notes: z.string().optional().default(""),
|
|
200
228
|
});
|
|
201
229
|
export const taskConfigSchema = z.preprocess((raw) => {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { canonicalizeJsonValue } from "./canonicalize.js";
|
|
1
2
|
import { MANAGED_TASK_CONFIG_FIELDS } from "./constants.js";
|
|
2
3
|
function maxRisk(a, b) {
|
|
3
4
|
const order = ["routine", "elevated", "blocking"];
|
|
@@ -139,6 +140,31 @@ export function diffDraftAgainstCurrent(input) {
|
|
|
139
140
|
path: "task.json#featureId",
|
|
140
141
|
});
|
|
141
142
|
}
|
|
143
|
+
// backendTest is managed whole-object: an absent draft value means
|
|
144
|
+
// "keep existing" and never diffs; declared values compare canonically.
|
|
145
|
+
if (input.draft.backendTest !== undefined) {
|
|
146
|
+
const before = cfg.backendTest ?? {};
|
|
147
|
+
const after = input.draft.backendTest;
|
|
148
|
+
if (canonicalizeJsonValue(before) !== canonicalizeJsonValue(after)) {
|
|
149
|
+
changedManagedFields.push("backendTest");
|
|
150
|
+
risks.push({
|
|
151
|
+
code: "BACKEND_TEST_CONFIG_CHANGED",
|
|
152
|
+
level: "routine",
|
|
153
|
+
message: "backendTest configuration changed",
|
|
154
|
+
path: "task.json#backendTest",
|
|
155
|
+
});
|
|
156
|
+
const afterKeys = new Set(Object.keys(after));
|
|
157
|
+
const droppedKeys = Object.keys(before).filter((key) => !afterKeys.has(key));
|
|
158
|
+
if (droppedKeys.length > 0) {
|
|
159
|
+
risks.push({
|
|
160
|
+
code: "BACKEND_TEST_CONFIG_WEAKENED",
|
|
161
|
+
level: "elevated",
|
|
162
|
+
message: `drop backendTest keys: ${droppedKeys.sort().join(", ")}`,
|
|
163
|
+
path: "task.json#backendTest",
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
142
168
|
}
|
|
143
169
|
else {
|
|
144
170
|
for (const field of MANAGED_TASK_CONFIG_FIELDS) {
|
|
@@ -174,6 +174,11 @@ export function mergeManagedTaskConfigFields(existing, draft) {
|
|
|
174
174
|
if (draft.featureId !== undefined) {
|
|
175
175
|
next.featureId = draft.featureId;
|
|
176
176
|
}
|
|
177
|
+
// backendTest is whole-object replace when the draft declares it (`{}` is a
|
|
178
|
+
// legal explicit clear back to the default layout); absent keeps existing.
|
|
179
|
+
if (draft.backendTest !== undefined) {
|
|
180
|
+
next.backendTest = draft.backendTest;
|
|
181
|
+
}
|
|
177
182
|
return next;
|
|
178
183
|
}
|
|
179
184
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { taskKindSchema, taskVerifyCommandSchema } from "../config-types.js";
|
|
2
|
+
import { backendTestConfigSchema, taskKindSchema, taskVerifyCommandSchema, } from "../config-types.js";
|
|
3
3
|
import { TASK_CONFIG_SCHEMA_VERSION, TASK_CONTRACT_CANONICALIZER_VERSION, TASK_CONTRACT_DRAFT_SCHEMA_VERSION, TASK_CONTRACT_PROJECTION_VERSION, TASK_CONTRACT_REF_SCHEMA_VERSION, } from "./constants.js";
|
|
4
4
|
const sha256Schema = z
|
|
5
5
|
.string()
|
|
@@ -62,6 +62,11 @@ export const taskContractDraftSchema = z
|
|
|
62
62
|
requirement: taskContractDraftRequirementSchema,
|
|
63
63
|
constraints: taskContractDraftConstraintsSchema,
|
|
64
64
|
verification: taskContractDraftVerificationSchema,
|
|
65
|
+
/**
|
|
66
|
+
* Managed backend-test layout / pipeline configuration projected into
|
|
67
|
+
* task.json#backendTest. Optional: absent means "keep existing".
|
|
68
|
+
*/
|
|
69
|
+
backendTest: backendTestConfigSchema,
|
|
65
70
|
references: z.array(taskContractDraftReferenceSchema).optional(),
|
|
66
71
|
sourceResolutions: z
|
|
67
72
|
.array(taskContractDraftSourceResolutionSchema)
|
|
@@ -22,6 +22,18 @@ function resolveTaskKindExplicit(flags, baseDraft) {
|
|
|
22
22
|
return flags.taskKindExplicit;
|
|
23
23
|
return baseDraft?.taskKindExplicit ?? false;
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Resolve managed backend-test layout config.
|
|
27
|
+
* Precedence: explicit CLI flags > --from-draft base > existing task.json
|
|
28
|
+
* (the last tier self-heals tasks whose backendTest was hand-written).
|
|
29
|
+
*/
|
|
30
|
+
function resolveBackendTest(flags, baseDraft, existing) {
|
|
31
|
+
if (flags.backendTest !== undefined)
|
|
32
|
+
return flags.backendTest;
|
|
33
|
+
if (baseDraft?.backendTest !== undefined)
|
|
34
|
+
return baseDraft.backendTest;
|
|
35
|
+
return existing?.backendTest ?? undefined;
|
|
36
|
+
}
|
|
25
37
|
function resolveTitle(taskId, flags, facts, baseDraft, existing) {
|
|
26
38
|
return (trimNonEmpty(flags.title) ??
|
|
27
39
|
trimNonEmpty(baseDraft?.title) ??
|
|
@@ -205,6 +217,9 @@ export function buildPrepareDraft(input) {
|
|
|
205
217
|
};
|
|
206
218
|
if (featureId)
|
|
207
219
|
draft.featureId = featureId;
|
|
220
|
+
const backendTest = resolveBackendTest(flags, baseDraft, existingTaskConfig);
|
|
221
|
+
if (backendTest !== undefined)
|
|
222
|
+
draft.backendTest = backendTest;
|
|
208
223
|
if (references && references.length > 0) {
|
|
209
224
|
draft.references = references.map((ref) => ({
|
|
210
225
|
role: ref.role,
|
|
@@ -35,8 +35,15 @@ const HEADING_ALIASES = {
|
|
|
35
35
|
assumptions: new Set(["假设", "assumptions"]),
|
|
36
36
|
};
|
|
37
37
|
const OBJECTIVE_MAX_CHARS = 1200;
|
|
38
|
-
/**
|
|
39
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Product Requirement V3/V4 style embedded AC heading, e.g. AC-FE-001 /
|
|
40
|
+
* AC-BE-12 / AC-001 — and catalog-qualified feature segments such as
|
|
41
|
+
* AC-FE-INS-001 / AC-FE-CHAT-005 (uppercase letter/digit segments).
|
|
42
|
+
* Catalog ids embedded in bold (“**AC-FE-INS-001**:…”) must bind as-is;
|
|
43
|
+
* falling through to a minted AC-00N historically produced dual ids and
|
|
44
|
+
* acceptanceCoverage.missing noise (2026-08-23 fe-test loop finding 5).
|
|
45
|
+
*/
|
|
46
|
+
const EMBEDDED_AC_HEADING = /^(AC(?:[-_](?:FE|BE|UI|API))?[-_]?[A-Z0-9]+(?:[-_][A-Z0-9]+)*)\s*[::]?\s*(.*)$/u;
|
|
40
47
|
function stripFencedAndComments(text) {
|
|
41
48
|
// Remove fenced code blocks and HTML comments so headings inside them are ignored.
|
|
42
49
|
return text
|
|
@@ -52,12 +59,25 @@ function normalizeAcceptanceId(raw) {
|
|
|
52
59
|
.replace(/^(AC)(FE|BE|UI|API)(?=-|\d)/u, "$1-$2");
|
|
53
60
|
}
|
|
54
61
|
function parseEmbeddedAcHeading(headingText) {
|
|
55
|
-
const normalized = headingText
|
|
62
|
+
const normalized = headingText
|
|
63
|
+
.trim()
|
|
64
|
+
// Strip leading section numbers ("2.1、" style) and bold/emphasis
|
|
65
|
+
// wrappers ("**AC-FE-INS-001**") so embedded catalog ids bind.
|
|
66
|
+
.replace(/^(?:\d+(?:\.\d+)*[..、]?\s*)+/u, "")
|
|
67
|
+
.replace(/^\*\*|\*\*$/g, "")
|
|
68
|
+
.replace(/^__|__$/g, "")
|
|
69
|
+
.trim();
|
|
56
70
|
const match = EMBEDDED_AC_HEADING.exec(normalized);
|
|
57
71
|
if (!match)
|
|
58
72
|
return null;
|
|
59
73
|
const id = normalizeAcceptanceId(match[1]);
|
|
60
|
-
|
|
74
|
+
// Bold-wrapped ids leave their closing marker inside the captured text
|
|
75
|
+
// ("**AC-FE-INS-001**:xxx" captures "**:xxx"); strip emphasis remnants
|
|
76
|
+
// and leading separators so the bound text is clean prose.
|
|
77
|
+
const text = (match[2] ?? "")
|
|
78
|
+
.replace(/^[*_]+\s*/, "")
|
|
79
|
+
.replace(/^\s*[::]\s*/, "")
|
|
80
|
+
.trim() || id;
|
|
61
81
|
return { id, text };
|
|
62
82
|
}
|
|
63
83
|
function normalizeHeadingText(raw) {
|
package/dist/worker/cli.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import { fileURLToPath } from "node:url";
|
|
5
6
|
import { Command } from "commander";
|
|
7
|
+
import { createNpmUpdateClient } from "../shared/update/npm-client.js";
|
|
8
|
+
import { runConsoleDailyUpdateCheck } from "../shared/update/console-notifier.js";
|
|
6
9
|
import { readPackageVersion } from "../shared/package-metadata.js";
|
|
7
10
|
import YAML from "yaml";
|
|
8
11
|
import { LoopAgentClient } from "./loop-agent/loop-agent-client.js";
|
|
@@ -838,10 +841,33 @@ function parseOptionalTimeoutMs(raw) {
|
|
|
838
841
|
return value;
|
|
839
842
|
}
|
|
840
843
|
/** Console HTTP entry for bare `agent-worker console` (listen first, then optional browser open). */
|
|
841
|
-
async function runConsoleServe(options) {
|
|
844
|
+
export async function runConsoleServe(options) {
|
|
842
845
|
const { createConsoleServer } = await import("./console/server.js");
|
|
843
846
|
const { maybeOpenConsoleBrowser } = await import("./console/open-browser.js");
|
|
844
847
|
try {
|
|
848
|
+
try {
|
|
849
|
+
const packageRoot = path.dirname(path.dirname(path.dirname(fileURLToPath(import.meta.url))));
|
|
850
|
+
const currentVersion = readPackageVersion(packageRoot) ?? "0.0.0";
|
|
851
|
+
await runConsoleDailyUpdateCheck({
|
|
852
|
+
currentVersion,
|
|
853
|
+
packageRoot,
|
|
854
|
+
homeDir: os.homedir(),
|
|
855
|
+
cwd: options.repoRoot,
|
|
856
|
+
env: process.env,
|
|
857
|
+
stdinIsTTY: process.stdin.isTTY === true,
|
|
858
|
+
stderrIsTTY: process.stderr.isTTY === true,
|
|
859
|
+
client: createNpmUpdateClient({
|
|
860
|
+
currentVersion,
|
|
861
|
+
packageRoot,
|
|
862
|
+
cwd: options.repoRoot,
|
|
863
|
+
env: process.env,
|
|
864
|
+
}),
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
catch (error) {
|
|
868
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
869
|
+
process.stderr.write(`loop-agent 更新检查已跳过:${detail}\n`);
|
|
870
|
+
}
|
|
845
871
|
const server = await createConsoleServer({
|
|
846
872
|
repoRoot: options.repoRoot,
|
|
847
873
|
host: options.host,
|
|
@@ -857,6 +883,10 @@ async function runConsoleServe(options) {
|
|
|
857
883
|
noOpen: options.noOpen === true,
|
|
858
884
|
serverUrl: server.url,
|
|
859
885
|
});
|
|
886
|
+
if (options.holdUntilSignal === false) {
|
|
887
|
+
await server.close();
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
860
890
|
await new Promise((resolve) => {
|
|
861
891
|
const shutdown = () => resolve();
|
|
862
892
|
process.once("SIGINT", shutdown);
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
const IMAGE_MIME_TYPES = new Set([
|
|
3
|
+
"image/png",
|
|
4
|
+
"image/jpeg",
|
|
5
|
+
"image/gif",
|
|
6
|
+
"image/webp",
|
|
7
|
+
]);
|
|
8
|
+
export function hashDeferredTurnPayload(input) {
|
|
9
|
+
return createHash("sha256")
|
|
10
|
+
.update(JSON.stringify({
|
|
11
|
+
text: input.text,
|
|
12
|
+
images: input.images.map(({ type, mimeType, data }) => ({
|
|
13
|
+
type,
|
|
14
|
+
mimeType,
|
|
15
|
+
data,
|
|
16
|
+
})),
|
|
17
|
+
}))
|
|
18
|
+
.digest("hex");
|
|
19
|
+
}
|
|
20
|
+
export function parseDeferredTurn(value) {
|
|
21
|
+
if (!value || typeof value !== "object")
|
|
22
|
+
return undefined;
|
|
23
|
+
const item = value;
|
|
24
|
+
if (item.schemaVersion !== 1 ||
|
|
25
|
+
!Number.isSafeInteger(item.revision) ||
|
|
26
|
+
(item.revision ?? 0) < 1 ||
|
|
27
|
+
typeof item.clientRequestId !== "string" ||
|
|
28
|
+
!item.clientRequestId.trim() ||
|
|
29
|
+
typeof item.payloadHash !== "string" ||
|
|
30
|
+
!/^[a-f0-9]{64}$/.test(item.payloadHash) ||
|
|
31
|
+
typeof item.blockedByTurnId !== "string" ||
|
|
32
|
+
!item.blockedByTurnId.trim() ||
|
|
33
|
+
typeof item.text !== "string" ||
|
|
34
|
+
!Array.isArray(item.images) ||
|
|
35
|
+
!["queued", "dispatching", "accepted", "failed"].includes(item.state) ||
|
|
36
|
+
typeof item.createdAt !== "string" ||
|
|
37
|
+
typeof item.updatedAt !== "string") {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
const images = [];
|
|
41
|
+
for (const image of item.images) {
|
|
42
|
+
if (!image || typeof image !== "object")
|
|
43
|
+
return undefined;
|
|
44
|
+
const candidate = image;
|
|
45
|
+
if (typeof candidate.name !== "string" ||
|
|
46
|
+
candidate.type !== "image" ||
|
|
47
|
+
typeof candidate.mimeType !== "string" ||
|
|
48
|
+
!IMAGE_MIME_TYPES.has(candidate.mimeType) ||
|
|
49
|
+
typeof candidate.data !== "string") {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
images.push(candidate);
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
schemaVersion: 1,
|
|
56
|
+
revision: item.revision,
|
|
57
|
+
clientRequestId: item.clientRequestId.trim(),
|
|
58
|
+
payloadHash: item.payloadHash,
|
|
59
|
+
blockedByTurnId: item.blockedByTurnId.trim(),
|
|
60
|
+
text: item.text,
|
|
61
|
+
images,
|
|
62
|
+
state: item.state,
|
|
63
|
+
createdAt: item.createdAt,
|
|
64
|
+
updatedAt: item.updatedAt,
|
|
65
|
+
...(typeof item.acceptedTurnId === "string" && item.acceptedTurnId
|
|
66
|
+
? { acceptedTurnId: item.acceptedTurnId }
|
|
67
|
+
: {}),
|
|
68
|
+
...(item.lastError &&
|
|
69
|
+
typeof item.lastError.code === "string" &&
|
|
70
|
+
typeof item.lastError.message === "string"
|
|
71
|
+
? { lastError: { code: item.lastError.code, message: item.lastError.message } }
|
|
72
|
+
: {}),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export function projectDeferredTurnSummary(deferredTurn) {
|
|
76
|
+
if (!deferredTurn)
|
|
77
|
+
return null;
|
|
78
|
+
return {
|
|
79
|
+
revision: deferredTurn.revision,
|
|
80
|
+
clientRequestId: deferredTurn.clientRequestId,
|
|
81
|
+
blockedByTurnId: deferredTurn.blockedByTurnId,
|
|
82
|
+
state: deferredTurn.state,
|
|
83
|
+
updatedAt: deferredTurn.updatedAt,
|
|
84
|
+
textPreview: deferredTurn.text.replace(/\s+/g, " ").trim().slice(0, 120),
|
|
85
|
+
imageCount: deferredTurn.images.length,
|
|
86
|
+
...(deferredTurn.acceptedTurnId
|
|
87
|
+
? { acceptedTurnId: deferredTurn.acceptedTurnId }
|
|
88
|
+
: {}),
|
|
89
|
+
...(deferredTurn.lastError ? { lastError: deferredTurn.lastError } : {}),
|
|
90
|
+
};
|
|
91
|
+
}
|