@tea-agent/loop-agent 0.39.0-beta.1 → 0.39.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +4 -1
- package/CHANGELOG.md +248 -95
- package/README.md +9 -5
- package/bin/loop-agent.js +7 -3
- package/dist/application/task-lifecycle/advance.js +12 -0
- package/dist/application/task-lifecycle/recommendations.js +13 -2
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +23 -5
- package/dist/cli/program.js +21 -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/client-recovery.js +3 -0
- package/dist/commands/dag-follow-up.js +138 -0
- 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 +455 -28
- package/dist/commands/inspect-next.js +8 -0
- package/dist/commands/task-advance.js +19 -0
- package/dist/executors/dag-pi-executor.js +2306 -61
- package/dist/executors/pi-executor.js +7 -0
- package/dist/executors/pi-extension-resolver.js +233 -0
- package/dist/executors/pi-playwright-cli-tool.js +74 -28
- package/dist/executors/pi-read-budget-policy.js +239 -0
- package/dist/executors/pi-sdk-executor.js +180 -33
- package/dist/executors/shell-executor.js +1249 -224
- package/dist/executors/shell-write-guard.js +14 -0
- package/dist/executors/workspace-write-snapshot.js +68 -0
- package/dist/infrastructure/harness/atomic-write.js +23 -0
- package/dist/shared/dag-failure-category.js +150 -0
- package/dist/shared/dag-prompt-override.js +27 -0
- package/dist/shared/openspec-spec.js +70 -4
- package/dist/shared/operator/capabilities.js +39 -0
- 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 +50 -5
- package/dist/task/contract/apply.js +11 -1
- package/dist/task/contract/constants.js +2 -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 +18 -1
- package/dist/task/contract/schema.js +4 -1
- package/dist/task/contract/transaction.js +12 -0
- package/dist/task/frontend-project-capability.js +199 -18
- package/dist/task/source-prepare/completeness.js +188 -0
- package/dist/task/source-prepare/fragment-inventory.js +477 -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 +298 -1
- package/dist/task/source-prepare/semantic-intake.js +25 -5
- package/dist/task/source-prepare/source-fidelity-pi.js +384 -0
- package/dist/task/source-prepare/types.js +22 -0
- package/dist/task/source-references.js +22 -1
- package/dist/worker/cli.js +18 -2
- package/dist/worker/console/chat/chat-event-store.js +62 -4
- package/dist/worker/console/chat/context-panel.js +7 -0
- package/dist/worker/console/chat/deferred-turn.js +12 -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 +264 -16
- package/dist/worker/console/chat/provider-error.js +98 -0
- package/dist/worker/console/chat/routes.js +298 -32
- package/dist/worker/console/chat/session-stats.js +179 -0
- package/dist/worker/console/chat/session-store.js +93 -63
- package/dist/worker/console/chat/todos.js +115 -0
- package/dist/worker/console/chat/tool-preview.js +42 -0
- package/dist/worker/console/chat/turn-process.js +20 -55
- package/dist/worker/console/chat/user-questions.js +266 -0
- package/dist/worker/console/chat/workspace-landing.js +14 -0
- 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/frontend-human-decision-adapter.js +19 -0
- package/dist/worker/console/frontend-split-operation-adapter.js +20 -0
- package/dist/worker/console/index.js +3 -0
- package/dist/worker/console/inspect-split.js +18 -0
- package/dist/worker/console/native-directory-picker.js +13 -0
- package/dist/worker/console/operation-run-facts.js +19 -3
- package/dist/worker/console/operation-store.js +227 -7
- package/dist/worker/console/operator-actions.js +388 -0
- package/dist/worker/console/operator-surface-health.js +1 -0
- package/dist/worker/console/operator-user-error.js +2 -2
- package/dist/worker/console/prd-intake-bridge.js +13 -0
- package/dist/worker/console/routes.js +355 -3
- package/dist/worker/console/security.js +67 -13
- package/dist/worker/console/server.js +609 -164
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4kVYweA.js → abnfDiagram-N423BO3Z-BAsjg7ew.js} +1 -1
- package/dist/worker/console/static/assets/{arc-Bj2M8iO5.js → arc-CyIlrwZw.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CLlXe4-6.js → architectureDiagram-T3A2C74G-Bo4tobhH.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-psEp5xH0.js → blockDiagram-VBNYF7ZC-Dfpow9gu.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DeKcOCGJ.js → c4Diagram-5PPSVZJV-DPmzG_B4.js} +1 -1
- package/dist/worker/console/static/assets/channel-DSrJAn0-.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-K0C744p1.js → chunk-2GRJ4B5K-pKQpB1UW.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-xbNw4J9-.js → chunk-2Q5K7J3B-5n9YYquZ.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DjLaSDUO.js → chunk-5RXB4S5H-DP3CtA2a.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CtiPlKel.js → chunk-5VM5RSS4-DYxMJPUy.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DZtolir7.js → chunk-6Q2QTUOP-CS3D50Io.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CPcdNeew.js → chunk-GF5L2VYU-gSdDfo5_.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-NgHc6V37.js → chunk-JWPE2WC7-A48q0ZNt.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DQ_T-hg8.js → chunk-KBJHAD2P-ow8Mm3DY.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DgLsxcVP.js → chunk-RYQCIY6F-CMKfLWYE.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-UrVoM9z1.js → chunk-XXDRQBXY-BiTBdQPX.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DeMmPl0_.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DeMmPl0_.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CLkYvTb3.js → cose-bilkent-JH36ORCC-Cm7exRz_.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BwT_xKrE.js → cynefin-VYW2F7L2-D9O5qGOu.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Bi9tuzif.js → cynefinDiagram-MW4NZA55-CkvQnako.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BCIqkWBV.js → dagre-VZM6K2ZE-B1rzp7CB.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Dip6l9_Z.js → diagram-7IWD3JNH-BP07Ipcc.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B-xkh_wK.js → diagram-B4RE2ZJO-BlPzweQb.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DZO0kTt_.js → diagram-LBJQPF4R-CxPa7n0S.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-D6ZplNbZ.js → diagram-Q27KOJAE-CMTZs53A.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BPekbhoS.js → diagram-UB23O5K3-CxG_X52r.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BtmaJpK5.js → ebnfDiagram-BXEA7PRR-D8axtJM0.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-6DS0Xc44.js → erDiagram-JOGREHBK-DIsGzn7b.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CihnROSm.js → flowDiagram-UKHOOZJN-CU9zkDG0.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CZC4zOE2.js → ganttDiagram-PKOTCBZU-DCjTmz5P.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Dq1fhzGN.js → gitGraphDiagram-DS77QQ5N-BksdmFzg.js} +1 -1
- package/dist/worker/console/static/assets/index-B-O5ChYq.css +1 -0
- package/dist/worker/console/static/assets/index-CfnY8AQx.js +407 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DlSl4BGx.js → infoDiagram-6WML65LV-CGzzzJtL.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-snnzFl_U.js → ishikawaDiagram-WSZJBQD7-BdseB80N.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-BDE7qpMx.js → journeyDiagram-NVQOT4AX-DM9gsgha.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CD2Ci04G.js → kanban-definition-27J2QSJJ-3q-_9R2b.js} +1 -1
- package/dist/worker/console/static/assets/{linear-DdnapKIH.js → linear-B8N-Mxsd.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-BVjAT9b8.js → mermaid.core-EPIHUUsu.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-D0yJk-zA.js → mindmap-definition-FAOFIHXS-CxwO12aa.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BVo9tFP-.js → pegDiagram-VL7TDLO6-bPqbVX6Y.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DnOV-mZ_.js → pieDiagram-7S7Q4E2Y-KzqIvzeX.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CzUJo58i.js → quadrantDiagram-CIZ2JOQS-CyofpWiV.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Bvikrolh.js → railroadDiagram-AXF67PYL-cTWJvLTP.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DtaSVCap.js → requirementDiagram-LRYGKXZP-BK--q27B.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-C2c9A0wW.js → sankeyDiagram-W5VNT64P-C0SDOHXK.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DmXJcU7r.js → sequenceDiagram-SI44F4Z6-Cong6YIo.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-B4GUFW92.js → sizeCapture-X5ZJPWSS-DUVkgZx8.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Dsad2MXf.js → stateDiagram-OKZ733FA-C7XgxuZg.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Dzbnd5oX.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DGq48Fbi.js → swimlanes-SLNWSIFB-D9Ayvwcy.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BY0hHk6G.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CSSi6OFf.js → timeline-definition-Z64GVDOM-Byb000cH.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BqyzPrWv.js → vennDiagram-T6HMQDX7-DMLegPt4.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DlznVb6S.js → wardleyDiagram-T6FBY63Y-ChMMeOsP.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CfBcgJ_K.js → xychartDiagram-ELKLHX3M-bEMEPisW.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/active-run-badge.js +17 -0
- package/dist/worker/console/static-src/app/console-types.js +68 -4
- package/dist/worker/console/static-src/app/useConsoleShell.js +30 -12
- package/dist/worker/console/static-src/app/useOperatorActions.js +41 -6
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +45 -10
- package/dist/worker/console/static-src/app/useRunProgress.js +47 -0
- package/dist/worker/console/static-src/app/useTaskWizard.js +74 -2
- package/dist/worker/console/static-src/night/useNightBoard.js +8 -5
- 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 +140 -8
- package/dist/worker/console/static-src/operator-chat/details-dag-actions.js +122 -0
- 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/refs.js +3 -0
- package/dist/worker/console/static-src/operator-chat/runtime-selection-labels.js +27 -0
- package/dist/worker/console/static-src/operator-chat/turn-submission.js +16 -3
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +168 -72
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +102 -21
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +184 -4
- package/dist/worker/console/static-src/operator-chat/useComposer.js +32 -0
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +63 -9
- package/dist/worker/console/static-src/operator-chat/useWorkspaceBrowserGate.js +4 -0
- package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +3 -3
- 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/console/static-src/shell/useWorkspaces.js +299 -0
- package/dist/worker/console/static-src/shell/workspace-route.js +337 -0
- package/dist/worker/console/workspace-context.js +344 -0
- package/dist/worker/console/workspace-registry.js +214 -0
- package/dist/worker/loop-agent/loop-agent-client.js +7 -3
- package/dist/worker/materialize/frontend-split-task-materializer.js +72 -0
- 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/server.js +4 -0
- package/dist/worker/observe/shell-handler-keys.js +34 -0
- package/dist/worker/observe/static/api.js +90 -4
- package/dist/worker/observe/static/app.js +18 -70
- 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/dag-node-purpose.d.ts +6 -0
- package/dist/worker/observe/static/dag-node-purpose.js +208 -0
- package/dist/worker/observe/static/format.js +60 -0
- package/dist/worker/observe/static/index.html +6 -1
- package/dist/worker/observe/static/inspect-workspace.js +307 -0
- package/dist/worker/observe/static/markdown-render.js +20 -1
- package/dist/worker/observe/static/operator-chrome.css +145 -20
- package/dist/worker/observe/static/operator-chrome.d.ts +20 -3
- package/dist/worker/observe/static/operator-chrome.js +330 -96
- 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/router.d.ts +46 -0
- package/dist/worker/observe/static/router.js +25 -1
- package/dist/worker/observe/static/state.d.ts +50 -0
- package/dist/worker/observe/static/state.js +184 -1
- package/dist/worker/observe/static/styles.css +395 -0
- package/dist/worker/observe/static/views/dag-graph.js +4 -2
- package/dist/worker/observe/static/views/dag-inspector.js +1067 -9
- package/dist/worker/observe/static/views/dag.d.ts +13 -0
- package/dist/worker/observe/static/views/dag.js +14 -5
- package/dist/worker/observe/static/views/dashboard.js +6 -4
- package/dist/worker/observe/static/views/failures.js +5 -2
- package/dist/worker/observe/static/views/night.js +2 -2
- package/dist/worker/observe/static/views/pool.js +1 -0
- package/dist/worker/observe/static/views/run.js +3 -1
- package/dist/worker/observe/static/views/session-timeline.js +78 -9
- package/dist/worker/observe/static/views/task.js +56 -1
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +49 -3
- package/dist/workflows/dag/backend-test-markdown-workflow.js +176 -1
- package/dist/workflows/dag/backend-test-pytest-collection.js +98 -14
- package/dist/workflows/dag/backend-test-writer-completeness.js +10 -5
- package/dist/workflows/dag/contract-output-registry.js +15 -0
- package/dist/workflows/dag/contract-validator-registrations.js +1 -2
- package/dist/workflows/dag/dynamic-runtime/loop-until.js +1 -1
- package/dist/workflows/dag/dynamic-runtime/map.js +1 -1
- package/dist/workflows/dag/failure-category.js +7 -116
- package/dist/workflows/dag/frontend-closeout.js +221 -0
- package/dist/workflows/dag/frontend-design-policy.js +400 -0
- package/dist/workflows/dag/frontend-human-decision.js +182 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +538 -166
- package/dist/workflows/dag/frontend-plan-render.js +24 -0
- package/dist/workflows/dag/frontend-prewrite-gate.js +388 -392
- package/dist/workflows/dag/frontend-provider-capability-matrix.js +159 -0
- package/dist/workflows/dag/frontend-recovery-capsule.js +455 -0
- package/dist/workflows/dag/frontend-recovery-controller.js +202 -0
- package/dist/workflows/dag/frontend-recovery-lineage.js +178 -0
- package/dist/workflows/dag/frontend-recovery-plan.js +17 -10
- package/dist/workflows/dag/frontend-recovery-run.js +33 -20
- package/dist/workflows/dag/frontend-repair.js +7 -424
- package/dist/workflows/dag/frontend-review-context.js +288 -14
- package/dist/workflows/dag/frontend-review-findings.js +270 -0
- package/dist/workflows/dag/frontend-risk.js +15 -2
- package/dist/workflows/dag/frontend-shadow-dual-write.js +814 -0
- package/dist/workflows/dag/frontend-shape-capsule-store.js +191 -0
- package/dist/workflows/dag/frontend-shape-facts.js +409 -0
- package/dist/workflows/dag/frontend-shape.js +427 -0
- package/dist/workflows/dag/frontend-source-fidelity-ledger.js +108 -0
- package/dist/workflows/dag/frontend-split-application-service.js +203 -0
- package/dist/workflows/dag/frontend-split-orchestrator.js +899 -0
- package/dist/workflows/dag/frontend-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-typed-event-store.js +452 -0
- package/dist/workflows/dag/frontend-typed-event-transaction.js +180 -0
- package/dist/workflows/dag/frontend-verification-trace.js +52 -17
- package/dist/workflows/dag/frontend-worktree-diff.js +250 -17
- package/dist/workflows/dag/frontend-writer-admission.js +272 -0
- package/dist/workflows/dag/frontend-writer-status.js +244 -0
- package/dist/workflows/dag/init-hybrid.js +984 -613
- package/dist/workflows/dag/interrupt-request.js +10 -3
- package/dist/workflows/dag/lifecycle.js +3 -3
- package/dist/workflows/dag/node-execution.js +468 -58
- package/dist/workflows/dag/prompt.js +44 -19
- package/dist/workflows/dag/recovery-lease.js +80 -0
- package/dist/workflows/dag/report.js +37 -1
- package/dist/workflows/dag/rerun-feedback.js +124 -1
- package/dist/workflows/dag/rerun-plan.js +144 -5
- package/dist/workflows/dag/rerun-run.js +81 -6
- package/dist/workflows/dag/rerun-task.js +29 -0
- package/dist/workflows/dag/retry-policy.js +318 -8
- package/dist/workflows/dag/runner.js +429 -122
- package/dist/workflows/dag/scheduler.js +114 -16
- package/dist/workflows/dag/structured-output-repair.js +712 -0
- package/dist/workflows/dag/types.js +321 -31
- package/dist/workflows/dag/upstream-artifacts.js +0 -4
- package/dist/workflows/dag/validate.js +92 -32
- package/docs/README.md +2 -0
- package/docs/architecture/evolution.md +45 -0
- package/docs/init-surface.manifest.json +9 -0
- package/docs/operations/README.md +1 -1
- package/docs/operations/local-development-environment.md +21 -0
- package/docs/templates/README.md +2 -1
- package/docs/templates/agent-dag-report.schema.json +8 -2
- package/docs/templates/agent-dag.schema.json +45 -1
- package/docs/templates/frontend-implementation-contract.schema.json +684 -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/docs/templates/spec-registry.schema.json +45 -0
- package/harness.json +3 -3
- package/package.json +7 -1
- 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/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-bounded-implement/SKILL.md +8 -14
- package/skills/frontend-design-review/SKILL.md +20 -41
- package/skills/frontend-implementation/SKILL.md +3 -4
- package/skills/frontend-implementation/references/design-spec.md +15 -10
- package/skills/frontend-implementation/references/node-contracts.md +23 -13
- package/skills/frontend-review/SKILL.md +22 -14
- package/skills/frontend-review/references/review-findings.md +6 -7
- package/skills/loop-agent/references/command-reference.md +12 -4
- package/skills/loop-agent/references/hybrid-dag.md +8 -1
- 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-CPF4N7pf.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C2DwA_Y1.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C2DwA_Y1.js +0 -1
- package/dist/worker/console/static/assets/index-DTZOKgAn.js +0 -404
- package/dist/worker/console/static/assets/index-Ya5FE7cD.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8FB_cNk.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CrZYaWfx.js +0 -8
package/dist/commands/init.js
CHANGED
|
@@ -7,12 +7,26 @@ import { fileURLToPath } from "node:url";
|
|
|
7
7
|
import { isInitRuntimeActive, } from "../shared/runtime-activity.js";
|
|
8
8
|
import { loadHarnessManifest } from "../governance/harness.js";
|
|
9
9
|
import { OPENCODE_CONTEXT_OVERFLOW_COMPACT_PLUGIN_PATH, OPENCODE_TRANSIENT_RETRY_PLUGIN_PATH, PI_CONTEXT_OVERFLOW_EXTENSION_PATH, PI_PROJECT_SETTINGS_PATH, applyPiRetryMerge, applyProjectPiSettingsMerge, buildOpenCodeContextOverflowCompactPluginSource, buildProjectPiSettingsSource, buildOpenCodeTransientRetryPluginSource, buildPiContextOverflowExtensionSource, inspectProjectPiSettings, parseClientRecoveryMode, runClientRecovery, } from "./client-recovery.js";
|
|
10
|
+
import { assessInitPiModelReadiness, discoverInitModelCatalog, validateInitModelMatrixAgainstCatalog, } from "./init-model-catalog.js";
|
|
10
11
|
const MANAGED_BLOCK_START = "<!-- LOOP_AGENT_INIT_START -->";
|
|
11
12
|
const MANAGED_BLOCK_END = "<!-- LOOP_AGENT_INIT_END -->";
|
|
12
13
|
const GITIGNORE_BLOCK_START = "# LOOP_AGENT_INIT_START";
|
|
13
14
|
const GITIGNORE_BLOCK_END = "# LOOP_AGENT_INIT_END";
|
|
14
15
|
const INIT_SURFACE_STATE_PATH = ".harness/init-surface.json";
|
|
15
16
|
const DEFAULT_GOVERNANCE_ROOT = "ai_workspace/loop-agent";
|
|
17
|
+
function buildSpecRegistry() {
|
|
18
|
+
return `${JSON.stringify({
|
|
19
|
+
version: 1,
|
|
20
|
+
roots: [],
|
|
21
|
+
discovery: {
|
|
22
|
+
enabled: true,
|
|
23
|
+
maxDepth: 3,
|
|
24
|
+
maxFiles: 100,
|
|
25
|
+
maxBytes: 1048576,
|
|
26
|
+
timeoutMs: 2000,
|
|
27
|
+
},
|
|
28
|
+
}, null, 2)}\n`;
|
|
29
|
+
}
|
|
16
30
|
const CORE_DOC_FILES = [
|
|
17
31
|
{ source: "README.md", target: "README.md" },
|
|
18
32
|
{
|
|
@@ -609,7 +623,7 @@ if (
|
|
|
609
623
|
(assessment.status === "passed" && assessment.currentExitCode !== 0) ||
|
|
610
624
|
(assessment.status === "baseline-debt" && assessment.currentExitCode === 0)
|
|
611
625
|
) fail();
|
|
612
|
-
if (!assessment.baselineRef || assessment.baselineRef.nodeId !== "frontend-
|
|
626
|
+
if (!assessment.baselineRef || assessment.baselineRef.nodeId !== "frontend-writer-admission-shell") fail();
|
|
613
627
|
const baselineRaw = readContained(assessment.baselineRef.path);
|
|
614
628
|
if (sha256(baselineRaw) !== assessment.baselineRef.sha256) fail();
|
|
615
629
|
let baseline;
|
|
@@ -889,13 +903,13 @@ function buildHarness(input) {
|
|
|
889
903
|
}
|
|
890
904
|
// Init always writes a Pi tier matrix. `defaultModel` remains runtime
|
|
891
905
|
// compatibility for old projects only and is never fresh-init output.
|
|
892
|
-
if (input.model) {
|
|
906
|
+
if (input.model || input.modelMatrix) {
|
|
893
907
|
const piExisting = isRecord(mergedExecutors.pi) ? mergedExecutors.pi : {};
|
|
894
908
|
mergedExecutors.pi = {
|
|
895
909
|
...piExisting,
|
|
896
|
-
LOW: input.model,
|
|
897
|
-
MED: input.model,
|
|
898
|
-
HIGH: input.model,
|
|
910
|
+
LOW: input.modelMatrix?.LOW ?? input.model,
|
|
911
|
+
MED: input.modelMatrix?.MED ?? input.model,
|
|
912
|
+
HIGH: input.modelMatrix?.HIGH ?? input.model,
|
|
899
913
|
};
|
|
900
914
|
}
|
|
901
915
|
if (isRecord(mergedExecutors.pi)) {
|
|
@@ -1128,6 +1142,28 @@ export function normalizeInitModelReference(input) {
|
|
|
1128
1142
|
}
|
|
1129
1143
|
return model;
|
|
1130
1144
|
}
|
|
1145
|
+
export function normalizeInitModelMatrix(input) {
|
|
1146
|
+
const tierInputs = [input.lowModel, input.medModel, input.highModel];
|
|
1147
|
+
if (tierInputs.every((value) => value === undefined))
|
|
1148
|
+
return undefined;
|
|
1149
|
+
if (input.provider !== undefined || input.model !== undefined) {
|
|
1150
|
+
throw new Error("init --low-model/--med-model/--high-model cannot be combined with --provider or --model");
|
|
1151
|
+
}
|
|
1152
|
+
if (tierInputs.some((value) => value === undefined)) {
|
|
1153
|
+
throw new Error("init model matrix requires --low-model, --med-model, and --high-model together");
|
|
1154
|
+
}
|
|
1155
|
+
const lowModel = normalizeInitModelReference({ model: input.lowModel });
|
|
1156
|
+
const medModel = normalizeInitModelReference({ model: input.medModel });
|
|
1157
|
+
const highModel = normalizeInitModelReference({ model: input.highModel });
|
|
1158
|
+
if (!lowModel || !medModel || !highModel) {
|
|
1159
|
+
throw new Error("init model matrix contains an empty model reference");
|
|
1160
|
+
}
|
|
1161
|
+
return {
|
|
1162
|
+
LOW: lowModel,
|
|
1163
|
+
MED: medModel,
|
|
1164
|
+
HIGH: highModel,
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1131
1167
|
function normalizeRelativePath(relativePath) {
|
|
1132
1168
|
return relativePath.split(path.sep).join("/");
|
|
1133
1169
|
}
|
|
@@ -1289,6 +1325,9 @@ async function buildDesiredSurfaceContent(input) {
|
|
|
1289
1325
|
}), null, 2)}\n`,
|
|
1290
1326
|
};
|
|
1291
1327
|
}
|
|
1328
|
+
if (manifestPath === "docs/spec-registry.json") {
|
|
1329
|
+
return { content: buildSpecRegistry() };
|
|
1330
|
+
}
|
|
1292
1331
|
if (manifestPath === OPENCODE_TRANSIENT_RETRY_PLUGIN_PATH) {
|
|
1293
1332
|
return { content: buildOpenCodeTransientRetryPluginSource() };
|
|
1294
1333
|
}
|
|
@@ -1363,6 +1402,7 @@ async function readExistingSurfaceState(repoRoot) {
|
|
|
1363
1402
|
/** The IDE schema reference remains a structural update signal. */
|
|
1364
1403
|
const HARNESS_STRUCTURAL_VALUE_KEYS = new Set(["$schema"]);
|
|
1365
1404
|
const PI_MODEL_ROUTING_KEYS = new Set(["LOW", "MED", "HIGH", "defaultModel"]);
|
|
1405
|
+
const HARNESS_OPEN_MAP_PATHS = new Set(["entrypoints", "artifacts", "scripts"]);
|
|
1366
1406
|
function isPiModelRoutingPath(path) {
|
|
1367
1407
|
return path.length === 2 && path[0] === "executors" && path[1] === "pi";
|
|
1368
1408
|
}
|
|
@@ -1400,6 +1440,48 @@ function harnessJsonShapeMatches(current, desired, path = []) {
|
|
|
1400
1440
|
}
|
|
1401
1441
|
return true;
|
|
1402
1442
|
}
|
|
1443
|
+
/**
|
|
1444
|
+
* Align harness.json object properties with the current generated contract while
|
|
1445
|
+
* preserving project-owned scalar/array values. Pi tier routing is deliberately
|
|
1446
|
+
* opaque: existing LOW/MED/HIGH values survive byte-for-value, and missing tiers
|
|
1447
|
+
* stay missing until model readiness selects them from the Pi catalog.
|
|
1448
|
+
*/
|
|
1449
|
+
function reconcileHarnessStructure(current, desired, path = []) {
|
|
1450
|
+
if (!isRecord(desired)) {
|
|
1451
|
+
if (HARNESS_STRUCTURAL_VALUE_KEYS.has(path.at(-1) ?? ""))
|
|
1452
|
+
return desired;
|
|
1453
|
+
if (Array.isArray(desired) &&
|
|
1454
|
+
Array.isArray(current)) {
|
|
1455
|
+
return current;
|
|
1456
|
+
}
|
|
1457
|
+
return current !== undefined && typeof current === typeof desired
|
|
1458
|
+
? current
|
|
1459
|
+
: desired;
|
|
1460
|
+
}
|
|
1461
|
+
const currentRecord = isRecord(current) ? current : {};
|
|
1462
|
+
const next = {};
|
|
1463
|
+
for (const [key, desiredValue] of Object.entries(desired)) {
|
|
1464
|
+
if (isPiModelRoutingPath(path) && PI_MODEL_ROUTING_KEYS.has(key)) {
|
|
1465
|
+
if (key in currentRecord)
|
|
1466
|
+
next[key] = currentRecord[key];
|
|
1467
|
+
continue;
|
|
1468
|
+
}
|
|
1469
|
+
next[key] = reconcileHarnessStructure(currentRecord[key], desiredValue, [...path, key]);
|
|
1470
|
+
}
|
|
1471
|
+
if (isPiModelRoutingPath(path)) {
|
|
1472
|
+
for (const key of PI_MODEL_ROUTING_KEYS) {
|
|
1473
|
+
if (key in currentRecord && !(key in next))
|
|
1474
|
+
next[key] = currentRecord[key];
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
if (path.length === 1 && HARNESS_OPEN_MAP_PATHS.has(path[0])) {
|
|
1478
|
+
for (const [key, value] of Object.entries(currentRecord)) {
|
|
1479
|
+
if (!(key in next))
|
|
1480
|
+
next[key] = value;
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
return next;
|
|
1484
|
+
}
|
|
1403
1485
|
/**
|
|
1404
1486
|
* Stable package/source anchor for generated desired content: sha256 of the
|
|
1405
1487
|
* desired content re-rendered through buildDesiredSurfaceContent with fixed
|
|
@@ -1574,6 +1656,13 @@ async function writeInitSurfaceState(input) {
|
|
|
1574
1656
|
file.acceptedMerge = { ...priorFile.acceptedMerge };
|
|
1575
1657
|
}
|
|
1576
1658
|
}
|
|
1659
|
+
for (const [pathName, priorFile] of Object.entries(input.preserveOwnershipFrom.files)) {
|
|
1660
|
+
if (state.files[pathName] || priorFile.mode === "state")
|
|
1661
|
+
continue;
|
|
1662
|
+
if (await exists(path.join(input.repoRoot, pathName))) {
|
|
1663
|
+
state.files[pathName] = { ...priorFile };
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1577
1666
|
}
|
|
1578
1667
|
if (input.acceptCurrentHarnessAsMerge) {
|
|
1579
1668
|
const harness = state.files["harness.json"];
|
|
@@ -1636,6 +1725,8 @@ async function listRelativeFiles(rootPath) {
|
|
|
1636
1725
|
async function expectedLegacyInitFileSha(input) {
|
|
1637
1726
|
if (input.legacyPath.startsWith("docs/")) {
|
|
1638
1727
|
const doc = input.legacyPath.slice("docs/".length);
|
|
1728
|
+
if (doc === "spec-registry.json")
|
|
1729
|
+
return sha256Text(buildSpecRegistry());
|
|
1639
1730
|
const generated = buildGeneratedCoreDoc({
|
|
1640
1731
|
doc,
|
|
1641
1732
|
projectName: input.projectName,
|
|
@@ -1725,7 +1816,8 @@ function assessHarnessModelMigration(harness) {
|
|
|
1725
1816
|
}
|
|
1726
1817
|
const pi = isRecord(executorRecord.pi) ? executorRecord.pi : {};
|
|
1727
1818
|
const hasDefaultModel = "defaultModel" in pi;
|
|
1728
|
-
const
|
|
1819
|
+
const tierCount = PI_MODEL_TIERS.filter((tier) => tier in pi).length;
|
|
1820
|
+
const hasTier = tierCount > 0;
|
|
1729
1821
|
if (["requiresApiKey", "provider", "model"].some((field) => field in pi) ||
|
|
1730
1822
|
Object.keys(pi).some((field) => !["description", "enabled", "defaultModel", ...PI_MODEL_TIERS].includes(field))) {
|
|
1731
1823
|
return {
|
|
@@ -1739,8 +1831,16 @@ function assessHarnessModelMigration(harness) {
|
|
|
1739
1831
|
reason: "legacy top-level model or workflow fields cannot be proven equivalent",
|
|
1740
1832
|
};
|
|
1741
1833
|
}
|
|
1834
|
+
if (tierCount > 0 && tierCount < PI_MODEL_TIERS.length) {
|
|
1835
|
+
return {
|
|
1836
|
+
kind: "ambiguous",
|
|
1837
|
+
reason: "executors.pi contains an incomplete LOW/MED/HIGH model matrix",
|
|
1838
|
+
};
|
|
1839
|
+
}
|
|
1742
1840
|
if (!hasDefaultModel)
|
|
1743
1841
|
return { kind: "none" };
|
|
1842
|
+
if (tierCount === PI_MODEL_TIERS.length)
|
|
1843
|
+
return { kind: "none" };
|
|
1744
1844
|
if (hasTier) {
|
|
1745
1845
|
return {
|
|
1746
1846
|
kind: "ambiguous",
|
|
@@ -2428,7 +2528,9 @@ export function buildInitInstructions(input) {
|
|
|
2428
2528
|
"",
|
|
2429
2529
|
`- Project name: default \`${projectName}\`.`,
|
|
2430
2530
|
`- Governance root: default \`${governanceRoot}\`.`,
|
|
2431
|
-
"-
|
|
2531
|
+
"- Discover a credential-safe Pi/OpenCode model catalog with `loop-agent init model-catalog --repo-root <target> --json`. The command exposes only allowlisted model metadata, Pi setup status, and credential-readiness booleans, never raw config or credentials.",
|
|
2532
|
+
"- Preserve every existing LOW/MED/HIGH reference that still resolves in Pi. Select only missing or removed tiers from `eligibleForHarness=true`, then apply the complete matrix with `--low-model`, `--med-model`, and `--high-model`; update code keeps valid tiers unchanged.",
|
|
2533
|
+
"- If `piSetup.status` is `model-definition-required`, ask only for the missing provider/model id, API/base URL, context window, maximum output, inputs/reasoning, cost constraints, and credential method. Help merge Pi models.json without exposing credential values, rerun the catalog, and only then initialize. If definitions exist but credentials are missing, configure auth instead of rewriting harness tiers.",
|
|
2432
2534
|
"- Default: merge existing AGENTS.md, harness.json, and loop-agent governance docs instead of overwriting user content.",
|
|
2433
2535
|
"",
|
|
2434
2536
|
"## Initialization Upgrade Routing",
|
|
@@ -2463,12 +2565,14 @@ export function buildInitInstructions(input) {
|
|
|
2463
2565
|
"",
|
|
2464
2566
|
"When a model/agent is asked to initialize a target project, treat initialization as one automated workflow. Do not run deterministic init and then wait for another user turn to fill project-specific content.",
|
|
2465
2567
|
"",
|
|
2466
|
-
"1.
|
|
2467
|
-
"2.
|
|
2468
|
-
"3.
|
|
2469
|
-
"4.
|
|
2568
|
+
"1. Run `init model-catalog` and inspect `piSetup`. When Pi has no usable model setup, collect only the missing provider/model metadata or credential method, help configure Pi, and rerun the catalog before writing harness.json.",
|
|
2569
|
+
"2. Inspect the target repository. Preserve existing tiers that still resolve in Pi and select only missing/removed tiers from credential-ready Pi-compatible entries. OpenCode-only entries are context, not proof that the Pi executor can use them.",
|
|
2570
|
+
"3. Resolve with the user only inputs that remain genuinely unsafe to assume: material credential/cost/compliance choices, non-default governance root, overwrite policy, or deployment side effects.",
|
|
2571
|
+
"4. Run deterministic init with all three tier flags and merge defaults.",
|
|
2572
|
+
"5. Continue immediately into project inspection and adaptation.",
|
|
2573
|
+
"6. Finish by running the init health checks and quick verification; `init doctor` must confirm the Pi model matrix before any DAG is started.",
|
|
2470
2574
|
"",
|
|
2471
|
-
"
|
|
2575
|
+
"A model reference that remains in Pi is project-owned and must not be reset merely because init templates changed. If its credentials are missing, preserve the tier and repair authentication. If a reference has left Pi, replace only that tier. Ask the user only when required model metadata, credentials, cost, compliance, or availability cannot be inferred. An ambiguous old model configuration stays in a bounded harness.json model merge/human decision; never silently reinterpret it.",
|
|
2472
2576
|
"",
|
|
2473
2577
|
"## Required Model Adaptation",
|
|
2474
2578
|
"",
|
|
@@ -2502,6 +2606,7 @@ export async function initializeLoopAgentProject(options) {
|
|
|
2502
2606
|
const profile = options.profile ?? "full";
|
|
2503
2607
|
const merge = options.merge ?? true;
|
|
2504
2608
|
const model = normalizeInitModelReference(options);
|
|
2609
|
+
const modelMatrix = options.modelMatrix;
|
|
2505
2610
|
const written = [];
|
|
2506
2611
|
const skipped = [];
|
|
2507
2612
|
const assetRoot = await findPackageRoot();
|
|
@@ -2538,6 +2643,7 @@ export async function initializeLoopAgentProject(options) {
|
|
|
2538
2643
|
projectName,
|
|
2539
2644
|
governanceRoot,
|
|
2540
2645
|
model,
|
|
2646
|
+
modelMatrix,
|
|
2541
2647
|
});
|
|
2542
2648
|
await writeText({
|
|
2543
2649
|
repoRoot,
|
|
@@ -2619,6 +2725,14 @@ export async function initializeLoopAgentProject(options) {
|
|
|
2619
2725
|
targetRelativePath: path.join(governanceRoot, "templates"),
|
|
2620
2726
|
written,
|
|
2621
2727
|
});
|
|
2728
|
+
await writeTextIfMissing({
|
|
2729
|
+
repoRoot,
|
|
2730
|
+
relativePath: path.join(governanceRoot, "spec-registry.json"),
|
|
2731
|
+
content: buildSpecRegistry(),
|
|
2732
|
+
merge,
|
|
2733
|
+
written,
|
|
2734
|
+
skipped,
|
|
2735
|
+
});
|
|
2622
2736
|
if (profile === "full") {
|
|
2623
2737
|
// Project repo-local skills live only under `.agents/skills/`.
|
|
2624
2738
|
// The package still ships `skills/` as the built-in fallback source.
|
|
@@ -2680,7 +2794,7 @@ export async function initializeLoopAgentProject(options) {
|
|
|
2680
2794
|
projectName,
|
|
2681
2795
|
governanceRoot,
|
|
2682
2796
|
stateKind: "recorded",
|
|
2683
|
-
acceptCurrentHarnessAsMerge: model !== undefined,
|
|
2797
|
+
acceptCurrentHarnessAsMerge: model !== undefined || modelMatrix !== undefined,
|
|
2684
2798
|
});
|
|
2685
2799
|
written.push(INIT_SURFACE_STATE_PATH);
|
|
2686
2800
|
return {
|
|
@@ -2743,11 +2857,7 @@ function modelMergeTaskFor(pathName, state, allPaths, recordedState) {
|
|
|
2743
2857
|
"Keep writes inside allowedPaths only.",
|
|
2744
2858
|
],
|
|
2745
2859
|
verification: [
|
|
2746
|
-
|
|
2747
|
-
"loop-agent init doctor --repo-root .",
|
|
2748
|
-
"loop-agent inspect --repo-root .",
|
|
2749
|
-
"loop-agent docs audit --repo-root .",
|
|
2750
|
-
"bash scripts/check-repo.sh",
|
|
2860
|
+
`git diff --check -- ${pathName}`,
|
|
2751
2861
|
],
|
|
2752
2862
|
evidence: {
|
|
2753
2863
|
baseSha256: recordedState?.files[pathName]?.currentSha256,
|
|
@@ -2900,15 +3010,52 @@ export async function checkInitUpdate(input) {
|
|
|
2900
3010
|
(state.mode === "copied" || state.mode === "generated") &&
|
|
2901
3011
|
state.currentSha256 !== undefined &&
|
|
2902
3012
|
recordedState.files[pathName]?.currentSha256 === state.currentSha256) {
|
|
3013
|
+
if (pathName === "harness.json") {
|
|
3014
|
+
// harness.json always contains project-owned routing and configuration.
|
|
3015
|
+
// Even when its bytes still match the recorded baseline, package evolution
|
|
3016
|
+
// must use a semantic merge instead of replacing the whole file.
|
|
3017
|
+
modelMergeTasks.push(modelMergeTaskFor(pathName, state, allPaths, recordedState));
|
|
3018
|
+
}
|
|
3019
|
+
else {
|
|
3020
|
+
deterministicActions.push({
|
|
3021
|
+
type: "refresh-owned-file",
|
|
3022
|
+
path: pathName,
|
|
3023
|
+
mode: state.mode,
|
|
3024
|
+
reason: "refresh an unchanged recorded init file to the current generated or bundled content",
|
|
3025
|
+
});
|
|
3026
|
+
}
|
|
3027
|
+
}
|
|
3028
|
+
else {
|
|
3029
|
+
modelMergeTasks.push(modelMergeTaskFor(pathName, state, allPaths, recordedState));
|
|
3030
|
+
}
|
|
3031
|
+
}
|
|
3032
|
+
}
|
|
3033
|
+
const retiredActionPaths = new Set(retiredLayoutActions.map((action) => action.path));
|
|
3034
|
+
if (recordedState) {
|
|
3035
|
+
for (const [pathName, recordedFile] of Object.entries(recordedState.files)) {
|
|
3036
|
+
if (currentState.files[pathName] ||
|
|
3037
|
+
retiredActionPaths.has(pathName) ||
|
|
3038
|
+
recordedFile.mode === "state" ||
|
|
3039
|
+
recordedFile.mode === "directory") {
|
|
3040
|
+
continue;
|
|
3041
|
+
}
|
|
3042
|
+
const currentSha256 = await fileSha256IfExists(path.join(repoRoot, pathName));
|
|
3043
|
+
if (!currentSha256)
|
|
3044
|
+
continue;
|
|
3045
|
+
if (recordedFile.currentSha256 &&
|
|
3046
|
+
currentSha256 === recordedFile.currentSha256) {
|
|
2903
3047
|
deterministicActions.push({
|
|
2904
|
-
type: "
|
|
3048
|
+
type: "remove-owned-file",
|
|
2905
3049
|
path: pathName,
|
|
2906
|
-
|
|
2907
|
-
reason: "
|
|
3050
|
+
expectedSha256: currentSha256,
|
|
3051
|
+
reason: "remove an unchanged recorded init file that is no longer part of the current initialization surface",
|
|
2908
3052
|
});
|
|
2909
3053
|
}
|
|
2910
|
-
else {
|
|
2911
|
-
|
|
3054
|
+
else if (!humanDecisions.some((decision) => decision.path === pathName)) {
|
|
3055
|
+
humanDecisions.push({
|
|
3056
|
+
path: pathName,
|
|
3057
|
+
reason: "this recorded init file was removed from the current surface but has local changes; review whether to preserve or delete it",
|
|
3058
|
+
});
|
|
2912
3059
|
}
|
|
2913
3060
|
}
|
|
2914
3061
|
}
|
|
@@ -2916,9 +3063,12 @@ export async function checkInitUpdate(input) {
|
|
|
2916
3063
|
// Safe harness hygiene: strip obsolete pi.requiresApiKey without full harness rewrite.
|
|
2917
3064
|
// Cursor requiresApiKey is intentionally kept when present.
|
|
2918
3065
|
const harnessPath = path.join(repoRoot, "harness.json");
|
|
3066
|
+
let currentHarness;
|
|
2919
3067
|
if (await exists(harnessPath)) {
|
|
2920
3068
|
try {
|
|
2921
3069
|
const harness = JSON.parse(await readFile(harnessPath, "utf-8"));
|
|
3070
|
+
if (isRecord(harness))
|
|
3071
|
+
currentHarness = harness;
|
|
2922
3072
|
if (recordedState &&
|
|
2923
3073
|
isRecord(harness) &&
|
|
2924
3074
|
harness.$schema !== expectedHarnessSchemaRef(governanceRoot)) {
|
|
@@ -2942,10 +3092,11 @@ export async function checkInitUpdate(input) {
|
|
|
2942
3092
|
// A harness that already matches the current generated surface except for
|
|
2943
3093
|
// opaque Pi routing fields is modern, not a legacy-routing candidate.
|
|
2944
3094
|
// Keep the explicit migration assessment for every other harness shape.
|
|
3095
|
+
let modelMigration = { kind: "none" };
|
|
2945
3096
|
if (isRecord(harness) &&
|
|
2946
3097
|
currentState.files["harness.json"]?.relationship !==
|
|
2947
3098
|
"matches-current-generated") {
|
|
2948
|
-
|
|
3099
|
+
modelMigration = assessHarnessModelMigration(harness);
|
|
2949
3100
|
if (modelMigration.kind === "safe") {
|
|
2950
3101
|
deterministicActions.push({
|
|
2951
3102
|
type: "migrate-harness-model-fields",
|
|
@@ -2975,11 +3126,83 @@ export async function checkInitUpdate(input) {
|
|
|
2975
3126
|
modelMergeTasks.push(mergeTask);
|
|
2976
3127
|
}
|
|
2977
3128
|
}
|
|
3129
|
+
if (recordedState &&
|
|
3130
|
+
isRecord(harness) &&
|
|
3131
|
+
modelMigration.kind !== "ambiguous") {
|
|
3132
|
+
const desired = await buildDesiredSurfaceContent({
|
|
3133
|
+
assetRoot,
|
|
3134
|
+
repoRoot,
|
|
3135
|
+
projectName,
|
|
3136
|
+
governanceRoot,
|
|
3137
|
+
entry: { path: "harness.json", mode: "generated" },
|
|
3138
|
+
});
|
|
3139
|
+
if (desired.content !== undefined) {
|
|
3140
|
+
const desiredHarness = JSON.parse(desired.content);
|
|
3141
|
+
const reconciled = reconcileHarnessStructure(harness, desiredHarness);
|
|
3142
|
+
if (!isDeepStrictEqual(reconciled, harness)) {
|
|
3143
|
+
deterministicActions.push({
|
|
3144
|
+
type: "reconcile-harness-structure",
|
|
3145
|
+
path: "harness.json",
|
|
3146
|
+
reason: "add current harness properties, remove retired properties, and preserve project-owned values",
|
|
3147
|
+
});
|
|
3148
|
+
const existingTask = modelMergeTasks.findIndex((task) => task.path === "harness.json");
|
|
3149
|
+
if (existingTask >= 0)
|
|
3150
|
+
modelMergeTasks.splice(existingTask, 1);
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
}
|
|
2978
3154
|
}
|
|
2979
3155
|
catch {
|
|
2980
3156
|
// leave malformed harness for model merge / doctor
|
|
2981
3157
|
}
|
|
2982
3158
|
}
|
|
3159
|
+
const modelReadiness = input.modelCatalog
|
|
3160
|
+
? assessInitPiModelReadiness(isRecord(currentHarness?.executors) &&
|
|
3161
|
+
isRecord(currentHarness.executors.pi)
|
|
3162
|
+
? currentHarness.executors.pi
|
|
3163
|
+
: undefined, input.modelCatalog)
|
|
3164
|
+
: undefined;
|
|
3165
|
+
if (modelReadiness?.status === "selection-required" ||
|
|
3166
|
+
modelReadiness?.status === "reselection-required") {
|
|
3167
|
+
const state = currentState.files["harness.json"];
|
|
3168
|
+
const task = modelMergeTaskFor("harness.json", state, allPaths, recordedState);
|
|
3169
|
+
task.reason =
|
|
3170
|
+
modelReadiness.status === "selection-required"
|
|
3171
|
+
? "harness.json has no complete Pi model matrix that resolves against the current Pi model catalog"
|
|
3172
|
+
: `only unavailable Pi tiers need reselection: ${modelReadiness.reselectTiers.join(", ")}`;
|
|
3173
|
+
task.mergeRules = [
|
|
3174
|
+
`Preserve these currently resolvable tiers byte-for-byte: ${modelReadiness.preserveTiers.join(", ") || "none"}.`,
|
|
3175
|
+
`Select replacements only for: ${modelReadiness.reselectTiers.join(", ")}.`,
|
|
3176
|
+
"Choose replacements from eligibleReferences using target-project needs, capability, context window, latency, and cost; tiers may intentionally share a model.",
|
|
3177
|
+
"Apply the complete matrix through init update --apply-safe with all three tier flags so code can revalidate it; valid existing tiers must keep their current effective reference.",
|
|
3178
|
+
"Do not add credentials or raw Pi configuration to harness.json.",
|
|
3179
|
+
];
|
|
3180
|
+
task.verification = [
|
|
3181
|
+
"loop-agent init model-catalog --repo-root . --json",
|
|
3182
|
+
"loop-agent init check-update --repo-root . --json",
|
|
3183
|
+
"loop-agent init doctor --repo-root .",
|
|
3184
|
+
"bash scripts/check-repo.sh",
|
|
3185
|
+
];
|
|
3186
|
+
const existingTask = modelMergeTasks.findIndex((candidate) => candidate.path === "harness.json");
|
|
3187
|
+
if (existingTask >= 0)
|
|
3188
|
+
modelMergeTasks[existingTask] = task;
|
|
3189
|
+
else
|
|
3190
|
+
modelMergeTasks.push(task);
|
|
3191
|
+
}
|
|
3192
|
+
else if (modelReadiness &&
|
|
3193
|
+
modelReadiness.status !== "ready") {
|
|
3194
|
+
const setup = input.modelCatalog?.piSetup;
|
|
3195
|
+
const reason = setup?.status === "model-definition-required"
|
|
3196
|
+
? `${setup.reason} Ask for: ${setup.requiredUserInput.join("; ")}.`
|
|
3197
|
+
: setup?.status === "credentials-required" ||
|
|
3198
|
+
setup?.status === "discovery-failed"
|
|
3199
|
+
? setup.reason
|
|
3200
|
+
: "One or more configured Pi tiers are known but not credential-ready; enable their provider without changing the tier selection.";
|
|
3201
|
+
humanDecisions.push({
|
|
3202
|
+
path: "~/.pi/agent/models.json",
|
|
3203
|
+
reason,
|
|
3204
|
+
});
|
|
3205
|
+
}
|
|
2983
3206
|
const partial = {
|
|
2984
3207
|
repoRoot,
|
|
2985
3208
|
controllerVersion: currentState.controllerVersion,
|
|
@@ -2987,6 +3210,7 @@ export async function checkInitUpdate(input) {
|
|
|
2987
3210
|
deterministicActions,
|
|
2988
3211
|
modelMergeTasks,
|
|
2989
3212
|
humanDecisions,
|
|
3213
|
+
...(modelReadiness ? { modelReadiness } : {}),
|
|
2990
3214
|
summary: {
|
|
2991
3215
|
missing: deterministicActions.filter((action) => action.type !== "bootstrap-surface").length,
|
|
2992
3216
|
modelMerge: modelMergeTasks.length,
|
|
@@ -2994,6 +3218,9 @@ export async function checkInitUpdate(input) {
|
|
|
2994
3218
|
},
|
|
2995
3219
|
};
|
|
2996
3220
|
const recommendedNext = recommendedNextFor(partial);
|
|
3221
|
+
if (modelReadiness && modelReadiness.status !== "ready") {
|
|
3222
|
+
recommendedNext.unshift("loop-agent init model-catalog --repo-root <target> --json");
|
|
3223
|
+
}
|
|
2997
3224
|
// Project settings are part of the surface. Do not inspect user home by default.
|
|
2998
3225
|
const piInspection = await inspectProjectPiSettings({ repoRoot });
|
|
2999
3226
|
return {
|
|
@@ -3105,9 +3332,36 @@ async function applySafeAction(input) {
|
|
|
3105
3332
|
const target = path.join(input.repoRoot, input.action.path);
|
|
3106
3333
|
if (!(await exists(target)))
|
|
3107
3334
|
return false;
|
|
3335
|
+
if (input.action.expectedSha256 &&
|
|
3336
|
+
(await fileSha256IfExists(target)) !== input.action.expectedSha256) {
|
|
3337
|
+
return false;
|
|
3338
|
+
}
|
|
3108
3339
|
await rm(target, { force: true });
|
|
3109
3340
|
return true;
|
|
3110
3341
|
}
|
|
3342
|
+
if (input.action.type === "reconcile-harness-structure") {
|
|
3343
|
+
const target = path.join(input.repoRoot, "harness.json");
|
|
3344
|
+
if (!(await exists(target)))
|
|
3345
|
+
return false;
|
|
3346
|
+
const current = JSON.parse(await readFile(target, "utf-8"));
|
|
3347
|
+
if (!isRecord(current))
|
|
3348
|
+
return false;
|
|
3349
|
+
const assetRoot = await findPackageRoot();
|
|
3350
|
+
const desired = await buildDesiredSurfaceContent({
|
|
3351
|
+
assetRoot,
|
|
3352
|
+
repoRoot: input.repoRoot,
|
|
3353
|
+
projectName: input.projectName,
|
|
3354
|
+
governanceRoot: input.governanceRoot,
|
|
3355
|
+
entry: { path: "harness.json", mode: "generated" },
|
|
3356
|
+
});
|
|
3357
|
+
if (desired.content === undefined)
|
|
3358
|
+
return false;
|
|
3359
|
+
const next = reconcileHarnessStructure(current, JSON.parse(desired.content));
|
|
3360
|
+
if (isDeepStrictEqual(next, current))
|
|
3361
|
+
return false;
|
|
3362
|
+
await writeFile(target, `${JSON.stringify(next, null, 2)}\n`, "utf-8");
|
|
3363
|
+
return true;
|
|
3364
|
+
}
|
|
3111
3365
|
if (input.action.type === "remove-empty-directory") {
|
|
3112
3366
|
const target = path.join(input.repoRoot, input.action.path);
|
|
3113
3367
|
if (!(await isDirectoryEmpty(target)))
|
|
@@ -3162,6 +3416,8 @@ async function applySafeAction(input) {
|
|
|
3162
3416
|
return true;
|
|
3163
3417
|
}
|
|
3164
3418
|
if (input.action.type === "refresh-owned-file") {
|
|
3419
|
+
if (input.action.path === "harness.json")
|
|
3420
|
+
return false;
|
|
3165
3421
|
await writeFile(target, desired.content, "utf-8");
|
|
3166
3422
|
return true;
|
|
3167
3423
|
}
|
|
@@ -3180,6 +3436,52 @@ export async function applyInitUpdate(input) {
|
|
|
3180
3436
|
const applied = [];
|
|
3181
3437
|
const skipped = [];
|
|
3182
3438
|
const clientRecoveryMode = input.clientRecovery ?? "auto";
|
|
3439
|
+
const requestedModelMatrix = input.modelMatrix ??
|
|
3440
|
+
(input.model
|
|
3441
|
+
? { LOW: input.model, MED: input.model, HIGH: input.model }
|
|
3442
|
+
: undefined);
|
|
3443
|
+
if (requestedModelMatrix) {
|
|
3444
|
+
if (!input.modelCatalog) {
|
|
3445
|
+
throw new Error("init update model selection requires a fresh Pi model catalog");
|
|
3446
|
+
}
|
|
3447
|
+
validateInitModelMatrixAgainstCatalog(requestedModelMatrix, input.modelCatalog);
|
|
3448
|
+
const harnessPath = path.join(repoRoot, "harness.json");
|
|
3449
|
+
const harness = JSON.parse(await readFile(harnessPath, "utf-8"));
|
|
3450
|
+
if (!isRecord(harness) ||
|
|
3451
|
+
!isRecord(harness.executors) ||
|
|
3452
|
+
!isRecord(harness.executors.pi)) {
|
|
3453
|
+
throw new Error("init update cannot select models until harness.json executors.pi exists");
|
|
3454
|
+
}
|
|
3455
|
+
const readiness = assessInitPiModelReadiness(harness.executors.pi, input.modelCatalog);
|
|
3456
|
+
if (readiness.status === "credentials-required" ||
|
|
3457
|
+
readiness.status === "model-definition-required" ||
|
|
3458
|
+
readiness.status === "discovery-failed") {
|
|
3459
|
+
throw new Error(`init update cannot change harness model routing while Pi model readiness is ${readiness.status}`);
|
|
3460
|
+
}
|
|
3461
|
+
for (const tier of readiness.preserveTiers) {
|
|
3462
|
+
const currentReference = readiness.tiers[tier].resolvedReference;
|
|
3463
|
+
if (currentReference &&
|
|
3464
|
+
requestedModelMatrix[tier] !== currentReference) {
|
|
3465
|
+
throw new Error(`init update preserves the current Pi ${tier} model because it is still in the Pi catalog: ${currentReference}`);
|
|
3466
|
+
}
|
|
3467
|
+
}
|
|
3468
|
+
if (readiness.reselectTiers.length > 0) {
|
|
3469
|
+
const nextPi = { ...harness.executors.pi };
|
|
3470
|
+
for (const tier of readiness.reselectTiers) {
|
|
3471
|
+
nextPi[tier] = requestedModelMatrix[tier];
|
|
3472
|
+
}
|
|
3473
|
+
const next = {
|
|
3474
|
+
...harness,
|
|
3475
|
+
executors: { ...harness.executors, pi: nextPi },
|
|
3476
|
+
};
|
|
3477
|
+
await writeFile(harnessPath, `${JSON.stringify(next, null, 2)}\n`, "utf-8");
|
|
3478
|
+
applied.push({
|
|
3479
|
+
type: "write-harness-model-matrix",
|
|
3480
|
+
path: "harness.json",
|
|
3481
|
+
reason: `replace only unavailable Pi tiers: ${readiness.reselectTiers.join(", ")}`,
|
|
3482
|
+
});
|
|
3483
|
+
}
|
|
3484
|
+
}
|
|
3183
3485
|
if (input.bootstrapSurface) {
|
|
3184
3486
|
await writeInitSurfaceState({
|
|
3185
3487
|
repoRoot,
|
|
@@ -3206,6 +3508,7 @@ export async function applyInitUpdate(input) {
|
|
|
3206
3508
|
governanceRoot,
|
|
3207
3509
|
clientRecovery: clientRecoveryMode,
|
|
3208
3510
|
homeDir: input.homeDir,
|
|
3511
|
+
modelCatalog: input.modelCatalog,
|
|
3209
3512
|
});
|
|
3210
3513
|
for (const action of report.deterministicActions) {
|
|
3211
3514
|
if (action.type === "bootstrap-surface") {
|
|
@@ -3245,6 +3548,7 @@ export async function applyInitUpdate(input) {
|
|
|
3245
3548
|
governanceRoot,
|
|
3246
3549
|
clientRecovery: clientRecoveryMode,
|
|
3247
3550
|
homeDir: input.homeDir,
|
|
3551
|
+
modelCatalog: input.modelCatalog,
|
|
3248
3552
|
}),
|
|
3249
3553
|
};
|
|
3250
3554
|
}
|
|
@@ -3258,6 +3562,16 @@ function formatClientRecoverySummary(report) {
|
|
|
3258
3562
|
`clientRecovery.pi.path: ${pi.path}`,
|
|
3259
3563
|
];
|
|
3260
3564
|
}
|
|
3565
|
+
function formatModelReadinessSummary(report) {
|
|
3566
|
+
const readiness = report.modelReadiness;
|
|
3567
|
+
if (!readiness)
|
|
3568
|
+
return [];
|
|
3569
|
+
return [
|
|
3570
|
+
`modelReadiness.status: ${readiness.status}`,
|
|
3571
|
+
`modelReadiness.preserveTiers: ${readiness.preserveTiers.join(",") || "none"}`,
|
|
3572
|
+
`modelReadiness.reselectTiers: ${readiness.reselectTiers.join(",") || "none"}`,
|
|
3573
|
+
];
|
|
3574
|
+
}
|
|
3261
3575
|
function formatCheckUpdateText(report) {
|
|
3262
3576
|
return [
|
|
3263
3577
|
`loop-agent init update check for ${report.repoRoot}`,
|
|
@@ -3266,6 +3580,7 @@ function formatCheckUpdateText(report) {
|
|
|
3266
3580
|
`deterministicActions: ${report.deterministicActions.length}`,
|
|
3267
3581
|
`modelMergeTasks: ${report.modelMergeTasks.length}`,
|
|
3268
3582
|
`humanDecisions: ${report.humanDecisions.length}`,
|
|
3583
|
+
...formatModelReadinessSummary(report),
|
|
3269
3584
|
...formatClientRecoverySummary(report),
|
|
3270
3585
|
"recommendedNext:",
|
|
3271
3586
|
...report.recommendedNext.map((item) => `- ${item}`),
|
|
@@ -3279,6 +3594,14 @@ function formatCheckUpdateMarkdown(report) {
|
|
|
3279
3594
|
`- repoRoot: \`${report.repoRoot}\``,
|
|
3280
3595
|
`- controllerVersion: \`${report.controllerVersion}\``,
|
|
3281
3596
|
`- surfaceState: \`${report.surfaceState}\``,
|
|
3597
|
+
...(report.modelReadiness
|
|
3598
|
+
? [
|
|
3599
|
+
"",
|
|
3600
|
+
"## Pi Model Readiness",
|
|
3601
|
+
"",
|
|
3602
|
+
...formatModelReadinessSummary(report).map((line) => `- ${line}`),
|
|
3603
|
+
]
|
|
3604
|
+
: []),
|
|
3282
3605
|
...(piLines.length > 0
|
|
3283
3606
|
? ["", "## Client Recovery (Pi user config, read-only)", "", ...piLines]
|
|
3284
3607
|
: []),
|
|
@@ -3313,6 +3636,14 @@ export async function runInitDoctor(input) {
|
|
|
3313
3636
|
const manifest = await loadHarnessManifest(repoRoot);
|
|
3314
3637
|
add("harness.json", manifest.adapter === "loop-agent" || manifest.project === "loop-agent", `project=${manifest.project}`);
|
|
3315
3638
|
add("governance root", await exists(path.join(repoRoot, manifest.governanceRoot, "README.md")), manifest.governanceRoot);
|
|
3639
|
+
if (input.modelCatalog) {
|
|
3640
|
+
const readiness = assessInitPiModelReadiness(isRecord(manifest.executors?.pi)
|
|
3641
|
+
? manifest.executors.pi
|
|
3642
|
+
: undefined, input.modelCatalog);
|
|
3643
|
+
add("Pi model matrix", readiness.status === "ready", readiness.status === "ready"
|
|
3644
|
+
? "LOW/MED/HIGH resolve against the current Pi model catalog"
|
|
3645
|
+
: `${readiness.status}; preserve=${readiness.preserveTiers.join(",") || "none"}; reselect=${readiness.reselectTiers.join(",") || "none"}`);
|
|
3646
|
+
}
|
|
3316
3647
|
}
|
|
3317
3648
|
catch (error) {
|
|
3318
3649
|
add("harness.json", false, error instanceof Error ? error.message : String(error));
|
|
@@ -3354,6 +3685,7 @@ export async function runInitReconcile(input) {
|
|
|
3354
3685
|
repoRoot,
|
|
3355
3686
|
projectName: input.projectName,
|
|
3356
3687
|
governanceRoot: input.governanceRoot,
|
|
3688
|
+
modelCatalog: input.modelCatalog,
|
|
3357
3689
|
});
|
|
3358
3690
|
if (preReport.surfaceState === "missing") {
|
|
3359
3691
|
return { status: "needs-baseline", report: preReport };
|
|
@@ -3374,6 +3706,7 @@ export async function runInitReconcile(input) {
|
|
|
3374
3706
|
projectName: input.projectName,
|
|
3375
3707
|
governanceRoot: input.governanceRoot,
|
|
3376
3708
|
applySafe: true,
|
|
3709
|
+
modelCatalog: input.modelCatalog,
|
|
3377
3710
|
});
|
|
3378
3711
|
const status = update.report.ok
|
|
3379
3712
|
? "clean"
|
|
@@ -3395,6 +3728,9 @@ function parseInitArgs(repoRoot, args) {
|
|
|
3395
3728
|
let merge = true;
|
|
3396
3729
|
let provider;
|
|
3397
3730
|
let model;
|
|
3731
|
+
let lowModel;
|
|
3732
|
+
let medModel;
|
|
3733
|
+
let highModel;
|
|
3398
3734
|
let clientRecovery = "auto";
|
|
3399
3735
|
let json = false;
|
|
3400
3736
|
let markdown = false;
|
|
@@ -3409,6 +3745,7 @@ function parseInitArgs(repoRoot, args) {
|
|
|
3409
3745
|
const arg = args[i];
|
|
3410
3746
|
if ((arg === "instructions" ||
|
|
3411
3747
|
arg === "doctor" ||
|
|
3748
|
+
arg === "model-catalog" ||
|
|
3412
3749
|
arg === "check-update" ||
|
|
3413
3750
|
arg === "update" ||
|
|
3414
3751
|
arg === "reconcile" ||
|
|
@@ -3441,6 +3778,18 @@ function parseInitArgs(repoRoot, args) {
|
|
|
3441
3778
|
model = args[++i];
|
|
3442
3779
|
else if (arg.startsWith("--model="))
|
|
3443
3780
|
model = arg.slice("--model=".length);
|
|
3781
|
+
else if (arg === "--low-model")
|
|
3782
|
+
lowModel = args[++i];
|
|
3783
|
+
else if (arg.startsWith("--low-model="))
|
|
3784
|
+
lowModel = arg.slice("--low-model=".length);
|
|
3785
|
+
else if (arg === "--med-model")
|
|
3786
|
+
medModel = args[++i];
|
|
3787
|
+
else if (arg.startsWith("--med-model="))
|
|
3788
|
+
medModel = arg.slice("--med-model=".length);
|
|
3789
|
+
else if (arg === "--high-model")
|
|
3790
|
+
highModel = args[++i];
|
|
3791
|
+
else if (arg.startsWith("--high-model="))
|
|
3792
|
+
highModel = arg.slice("--high-model=".length);
|
|
3444
3793
|
else if (arg === "--client-recovery")
|
|
3445
3794
|
clientRecovery = parseClientRecoveryMode(args[++i]);
|
|
3446
3795
|
else if (arg.startsWith("--client-recovery="))
|
|
@@ -3478,6 +3827,13 @@ function parseInitArgs(repoRoot, args) {
|
|
|
3478
3827
|
throw new Error("init upgrade --version-choice must be current|upgrade|cancel|retry");
|
|
3479
3828
|
}
|
|
3480
3829
|
const normalizedModel = normalizeInitModelReference({ provider, model });
|
|
3830
|
+
const modelMatrix = normalizeInitModelMatrix({
|
|
3831
|
+
provider,
|
|
3832
|
+
model,
|
|
3833
|
+
lowModel,
|
|
3834
|
+
medModel,
|
|
3835
|
+
highModel,
|
|
3836
|
+
});
|
|
3481
3837
|
return {
|
|
3482
3838
|
repoRoot,
|
|
3483
3839
|
projectName,
|
|
@@ -3486,6 +3842,7 @@ function parseInitArgs(repoRoot, args) {
|
|
|
3486
3842
|
merge,
|
|
3487
3843
|
provider,
|
|
3488
3844
|
model: normalizedModel,
|
|
3845
|
+
modelMatrix,
|
|
3489
3846
|
clientRecovery,
|
|
3490
3847
|
subcommand,
|
|
3491
3848
|
json,
|
|
@@ -3499,21 +3856,49 @@ function parseInitArgs(repoRoot, args) {
|
|
|
3499
3856
|
versionChoice,
|
|
3500
3857
|
};
|
|
3501
3858
|
}
|
|
3859
|
+
async function discoverInitCatalogForRepo(repoRoot) {
|
|
3860
|
+
const harness = await readJsonIfExists(path.join(repoRoot, "harness.json"));
|
|
3861
|
+
const pi = isRecord(harness.executors) && isRecord(harness.executors.pi)
|
|
3862
|
+
? harness.executors.pi
|
|
3863
|
+
: undefined;
|
|
3864
|
+
const includePiReferences = pi
|
|
3865
|
+
? PI_MODEL_TIERS.flatMap((tier) => {
|
|
3866
|
+
const value = pi[tier];
|
|
3867
|
+
const model = typeof value === "string"
|
|
3868
|
+
? value
|
|
3869
|
+
: isRecord(value) && typeof value.model === "string"
|
|
3870
|
+
? value.model
|
|
3871
|
+
: undefined;
|
|
3872
|
+
return model && isQualifiedModelReference(model) ? [model] : [];
|
|
3873
|
+
})
|
|
3874
|
+
: [];
|
|
3875
|
+
return discoverInitModelCatalog({ repoRoot, includePiReferences });
|
|
3876
|
+
}
|
|
3502
3877
|
export async function runInit(repoRoot, rawArgs, dependencies) {
|
|
3503
3878
|
const parsed = parseInitArgs(repoRoot, rawArgs);
|
|
3504
3879
|
if (parsed.subcommand === "instructions") {
|
|
3505
3880
|
console.log(buildInitInstructions(parsed));
|
|
3506
3881
|
return;
|
|
3507
3882
|
}
|
|
3883
|
+
if (parsed.subcommand === "model-catalog") {
|
|
3884
|
+
const catalog = await discoverInitCatalogForRepo(parsed.repoRoot);
|
|
3885
|
+
console.log(JSON.stringify(catalog, null, 2));
|
|
3886
|
+
return;
|
|
3887
|
+
}
|
|
3508
3888
|
if (parsed.subcommand === "doctor") {
|
|
3509
|
-
const
|
|
3889
|
+
const modelCatalog = await discoverInitCatalogForRepo(parsed.repoRoot);
|
|
3890
|
+
const report = await runInitDoctor({
|
|
3891
|
+
repoRoot: parsed.repoRoot,
|
|
3892
|
+
modelCatalog,
|
|
3893
|
+
});
|
|
3510
3894
|
console.log(JSON.stringify(report, null, 2));
|
|
3511
3895
|
if (!report.ok)
|
|
3512
3896
|
process.exitCode = 1;
|
|
3513
3897
|
return;
|
|
3514
3898
|
}
|
|
3515
3899
|
if (parsed.subcommand === "check-update") {
|
|
3516
|
-
const
|
|
3900
|
+
const modelCatalog = await discoverInitCatalogForRepo(parsed.repoRoot);
|
|
3901
|
+
const report = await checkInitUpdate({ ...parsed, modelCatalog });
|
|
3517
3902
|
if (parsed.json)
|
|
3518
3903
|
console.log(JSON.stringify(report, null, 2));
|
|
3519
3904
|
else if (parsed.markdown)
|
|
@@ -3526,7 +3911,11 @@ export async function runInit(repoRoot, rawArgs, dependencies) {
|
|
|
3526
3911
|
if (!parsed.bootstrapSurface && !parsed.applySafe) {
|
|
3527
3912
|
throw new Error("usage: init update [--bootstrap-surface] [--apply-safe]");
|
|
3528
3913
|
}
|
|
3529
|
-
|
|
3914
|
+
if ((parsed.model || parsed.modelMatrix) && !parsed.applySafe) {
|
|
3915
|
+
throw new Error("init update model selection requires --apply-safe");
|
|
3916
|
+
}
|
|
3917
|
+
const modelCatalog = await discoverInitCatalogForRepo(parsed.repoRoot);
|
|
3918
|
+
const result = await applyInitUpdate({ ...parsed, modelCatalog });
|
|
3530
3919
|
console.log(parsed.json
|
|
3531
3920
|
? JSON.stringify(result, null, 2)
|
|
3532
3921
|
: formatInitUpdateResult(result));
|
|
@@ -3537,12 +3926,23 @@ export async function runInit(repoRoot, rawArgs, dependencies) {
|
|
|
3537
3926
|
const selected = [parsed.upgradeStatus, parsed.upgradeContinue, parsed.upgradeReport].filter(Boolean).length;
|
|
3538
3927
|
if (selected > 1)
|
|
3539
3928
|
throw new Error("init upgrade accepts only one of --status, --continue, or --report");
|
|
3929
|
+
const mode = parsed.upgradeStatus
|
|
3930
|
+
? "status"
|
|
3931
|
+
: parsed.upgradeContinue
|
|
3932
|
+
? "continue"
|
|
3933
|
+
: parsed.upgradeReport
|
|
3934
|
+
? "report"
|
|
3935
|
+
: "start";
|
|
3936
|
+
const modelCatalog = mode === "start"
|
|
3937
|
+
? await discoverInitCatalogForRepo(parsed.repoRoot)
|
|
3938
|
+
: undefined;
|
|
3540
3939
|
const result = await runInitUpgrade({
|
|
3541
3940
|
repoRoot: parsed.repoRoot,
|
|
3542
3941
|
runId: parsed.runId,
|
|
3543
|
-
mode
|
|
3942
|
+
mode,
|
|
3544
3943
|
versionChoice: parsed.versionChoice,
|
|
3545
3944
|
readRuntimeActivity: dependencies.readRuntimeActivity,
|
|
3945
|
+
modelCatalog,
|
|
3546
3946
|
});
|
|
3547
3947
|
if ("markdown" in result)
|
|
3548
3948
|
console.log(result.markdown);
|
|
@@ -3551,8 +3951,10 @@ export async function runInit(repoRoot, rawArgs, dependencies) {
|
|
|
3551
3951
|
return;
|
|
3552
3952
|
}
|
|
3553
3953
|
if (parsed.subcommand === "reconcile") {
|
|
3954
|
+
const modelCatalog = await discoverInitCatalogForRepo(parsed.repoRoot);
|
|
3554
3955
|
const result = await runInitReconcile({
|
|
3555
3956
|
...parsed,
|
|
3957
|
+
modelCatalog,
|
|
3556
3958
|
readRuntimeActivity: dependencies.readRuntimeActivity,
|
|
3557
3959
|
});
|
|
3558
3960
|
console.log(parsed.json
|
|
@@ -3560,6 +3962,31 @@ export async function runInit(repoRoot, rawArgs, dependencies) {
|
|
|
3560
3962
|
: formatReconcileResult(result));
|
|
3561
3963
|
return;
|
|
3562
3964
|
}
|
|
3965
|
+
const catalog = await discoverInitCatalogForRepo(parsed.repoRoot);
|
|
3966
|
+
const requestedMatrix = parsed.modelMatrix ??
|
|
3967
|
+
(parsed.model
|
|
3968
|
+
? { LOW: parsed.model, MED: parsed.model, HIGH: parsed.model }
|
|
3969
|
+
: undefined);
|
|
3970
|
+
if (requestedMatrix) {
|
|
3971
|
+
validateInitModelMatrixAgainstCatalog(requestedMatrix, catalog);
|
|
3972
|
+
}
|
|
3973
|
+
else {
|
|
3974
|
+
const harness = await readJsonIfExists(path.join(parsed.repoRoot, "harness.json"));
|
|
3975
|
+
const readiness = assessInitPiModelReadiness(isRecord(harness.executors) && isRecord(harness.executors.pi)
|
|
3976
|
+
? harness.executors.pi
|
|
3977
|
+
: undefined, catalog);
|
|
3978
|
+
if (readiness.status !== "ready") {
|
|
3979
|
+
const setup = catalog.piSetup;
|
|
3980
|
+
if (setup.status === "model-definition-required") {
|
|
3981
|
+
throw new Error(`Pi model setup is required before init. Ask the user for: ${setup.requiredUserInput.join("; ")}. Help merge the definition into ${setup.configPath} without storing credential values, rerun init model-catalog, then initialize with all three tier flags.`);
|
|
3982
|
+
}
|
|
3983
|
+
if (setup.status === "credentials-required" ||
|
|
3984
|
+
setup.status === "discovery-failed") {
|
|
3985
|
+
throw new Error(`${setup.reason} ${setup.nextSteps.join(" ")}`);
|
|
3986
|
+
}
|
|
3987
|
+
throw new Error(`Pi model selection is required before init (${readiness.status}). Run init model-catalog, preserve tiers still present in Pi, select only missing tiers, and pass --low-model/--med-model/--high-model.`);
|
|
3988
|
+
}
|
|
3989
|
+
}
|
|
3563
3990
|
const result = await initializeLoopAgentProject(parsed);
|
|
3564
3991
|
console.log(parsed.json ? JSON.stringify(result, null, 2) : formatInitResult(result));
|
|
3565
3992
|
}
|