@tea-agent/loop-agent 0.39.0-beta.1 → 0.39.0-beta.11
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 +349 -97
- 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 +2370 -64
- package/dist/executors/pi-executor.js +11 -2
- 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 +225 -33
- package/dist/executors/pi-writer-tool-policy.js +57 -1
- package/dist/executors/shell-executor.js +1258 -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 +75 -5
- 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 +331 -18
- package/dist/worker/console/chat/provider-error.js +98 -0
- package/dist/worker/console/chat/routes.js +395 -59
- 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 +79 -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-CXj_GnSb.js} +1 -1
- package/dist/worker/console/static/assets/{arc-Bj2M8iO5.js → arc-BZp6JAp7.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CLlXe4-6.js → architectureDiagram-T3A2C74G-DfpcEuYU.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-psEp5xH0.js → blockDiagram-VBNYF7ZC-_Gf0xadb.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DeKcOCGJ.js → c4Diagram-5PPSVZJV-Ct2QPCmv.js} +1 -1
- package/dist/worker/console/static/assets/channel-3TxJgYaH.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-K0C744p1.js → chunk-2GRJ4B5K-BfklkzKl.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-xbNw4J9-.js → chunk-2Q5K7J3B-Dy25vZJV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DjLaSDUO.js → chunk-5RXB4S5H-BFlCRZep.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CtiPlKel.js → chunk-5VM5RSS4-op3oVxIE.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DZtolir7.js → chunk-6Q2QTUOP-C0R7rzF2.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CPcdNeew.js → chunk-GF5L2VYU-Bt44TCGy.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-NgHc6V37.js → chunk-JWPE2WC7-_uEE_XFx.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DQ_T-hg8.js → chunk-KBJHAD2P-C3TOYGZ9.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DgLsxcVP.js → chunk-RYQCIY6F-Cz60oBPV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-UrVoM9z1.js → chunk-XXDRQBXY-8Bik0qis.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BHIkXpp3.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BHIkXpp3.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CLkYvTb3.js → cose-bilkent-JH36ORCC-C2QIOA4a.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BwT_xKrE.js → cynefin-VYW2F7L2-CSH_yUUd.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Bi9tuzif.js → cynefinDiagram-MW4NZA55-BDLw2XFM.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BCIqkWBV.js → dagre-VZM6K2ZE-CcD9ZtF4.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Dip6l9_Z.js → diagram-7IWD3JNH-DqwtkBsI.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B-xkh_wK.js → diagram-B4RE2ZJO-BWVEcqsC.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DZO0kTt_.js → diagram-LBJQPF4R-M-WAbtQi.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-D6ZplNbZ.js → diagram-Q27KOJAE-DKW7SixP.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BPekbhoS.js → diagram-UB23O5K3-PHHPPtrj.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BtmaJpK5.js → ebnfDiagram-BXEA7PRR-BQD-B4RX.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-6DS0Xc44.js → erDiagram-JOGREHBK-BFvULb51.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CihnROSm.js → flowDiagram-UKHOOZJN-Bw-aXTCb.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CZC4zOE2.js → ganttDiagram-PKOTCBZU-BGKw04Qx.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Dq1fhzGN.js → gitGraphDiagram-DS77QQ5N-RqKaPR-I.js} +1 -1
- package/dist/worker/console/static/assets/index-B_D8rbWc.js +407 -0
- package/dist/worker/console/static/assets/index-BdNx6fj0.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DlSl4BGx.js → infoDiagram-6WML65LV-YO5dnzrJ.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-snnzFl_U.js → ishikawaDiagram-WSZJBQD7-Bi1VZHMq.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-BDE7qpMx.js → journeyDiagram-NVQOT4AX-Bszls1DD.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CD2Ci04G.js → kanban-definition-27J2QSJJ-PhzeaZ09.js} +1 -1
- package/dist/worker/console/static/assets/{linear-DdnapKIH.js → linear-BsjbDoXi.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-BVjAT9b8.js → mermaid.core-0B7NnWKk.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-D0yJk-zA.js → mindmap-definition-FAOFIHXS-BJr4Fj-q.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BVo9tFP-.js → pegDiagram-VL7TDLO6-moC4fpGB.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DnOV-mZ_.js → pieDiagram-7S7Q4E2Y-BEw37-2c.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CzUJo58i.js → quadrantDiagram-CIZ2JOQS-Cq6LyasU.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Bvikrolh.js → railroadDiagram-AXF67PYL-DUCMcK0D.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DtaSVCap.js → requirementDiagram-LRYGKXZP-C3upTZm7.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-C2c9A0wW.js → sankeyDiagram-W5VNT64P-BI_gMsCW.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DmXJcU7r.js → sequenceDiagram-SI44F4Z6-YFOIRzfN.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-B4GUFW92.js → sizeCapture-X5ZJPWSS-dOnB7UDD.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Dsad2MXf.js → stateDiagram-OKZ733FA-BXUniaIh.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Cdi6UhLa.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DGq48Fbi.js → swimlanes-SLNWSIFB-2tA4wTNu.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-D-RJBbb0.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CSSi6OFf.js → timeline-definition-Z64GVDOM-DO0HkJXC.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BqyzPrWv.js → vennDiagram-T6HMQDX7-DvIOzixv.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DlznVb6S.js → wardleyDiagram-T6FBY63Y-DXDZ0cTj.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CfBcgJ_K.js → xychartDiagram-ELKLHX3M-B32Ark0D.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 +141 -8
- package/dist/worker/console/static-src/operator-chat/compaction-message.js +79 -0
- 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/spatial-overlay.js +1 -2
- package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +25 -0
- package/dist/worker/console/static-src/operator-chat/turn-submission.js +16 -3
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +181 -87
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +183 -48
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +177 -8
- 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 +339 -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 +17 -17
- 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 +2 -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 +1072 -660
- package/dist/workflows/dag/interrupt-request.js +10 -3
- package/dist/workflows/dag/lifecycle.js +3 -3
- package/dist/workflows/dag/node-execution.js +552 -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 +338 -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 +63 -1
- package/docs/templates/backend-test-dag.json +265 -237
- 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 +1 -1
- 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/worker/console/static-src/operator-chat/activity-rail-presentation.js +0 -73
- package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +0 -59
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { access, open, readFile, stat, } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
class PiReadBudgetSoftLimitError extends Error {
|
|
4
|
+
}
|
|
5
|
+
class PiReadAccessDeniedError extends Error {
|
|
6
|
+
}
|
|
7
|
+
class PiReadBudgetGuard {
|
|
8
|
+
budget;
|
|
9
|
+
files = new Set();
|
|
10
|
+
violations = [];
|
|
11
|
+
bytes = 0;
|
|
12
|
+
operations = 0;
|
|
13
|
+
startedAt = Date.now();
|
|
14
|
+
allowedReadPaths;
|
|
15
|
+
allowedReadRoots;
|
|
16
|
+
constructor(budget, readAccess) {
|
|
17
|
+
this.budget = budget;
|
|
18
|
+
this.allowedReadPaths = readAccess?.allowedReadPaths
|
|
19
|
+
? new Set(readAccess.allowedReadPaths.map((candidate) => path.resolve(candidate)))
|
|
20
|
+
: undefined;
|
|
21
|
+
this.allowedReadRoots = (readAccess?.allowedReadRoots ?? []).map((candidate) => path.resolve(candidate));
|
|
22
|
+
}
|
|
23
|
+
issues() {
|
|
24
|
+
return [...this.violations];
|
|
25
|
+
}
|
|
26
|
+
report(message) {
|
|
27
|
+
if (!this.violations.includes(message))
|
|
28
|
+
this.violations.push(message);
|
|
29
|
+
}
|
|
30
|
+
exhaust(message) {
|
|
31
|
+
if (this.budget.onExhaustion === "return-guidance") {
|
|
32
|
+
throw new PiReadBudgetSoftLimitError(message);
|
|
33
|
+
}
|
|
34
|
+
this.report(message);
|
|
35
|
+
throw new Error(message);
|
|
36
|
+
}
|
|
37
|
+
assertElapsed() {
|
|
38
|
+
if (this.budget.maxMs === undefined)
|
|
39
|
+
return;
|
|
40
|
+
const elapsedMs = Date.now() - this.startedAt;
|
|
41
|
+
if (elapsedMs > this.budget.maxMs) {
|
|
42
|
+
const message = `read budget exhausted: ${elapsedMs}ms (budget ${this.budget.maxMs}ms)`;
|
|
43
|
+
this.exhaust(message);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
beforeToolOperation() {
|
|
47
|
+
this.assertElapsed();
|
|
48
|
+
if (this.budget.maxOperations === undefined)
|
|
49
|
+
return true;
|
|
50
|
+
if (this.operations >= this.budget.maxOperations) {
|
|
51
|
+
const message = `read budget exhausted: operation ${this.operations + 1} would exceed ${this.budget.maxOperations} read-only tool calls`;
|
|
52
|
+
if (this.budget.onExhaustion === "return-guidance")
|
|
53
|
+
return false;
|
|
54
|
+
this.exhaust(message);
|
|
55
|
+
}
|
|
56
|
+
this.operations += 1;
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
operationLimitGuidance() {
|
|
60
|
+
return {
|
|
61
|
+
content: [
|
|
62
|
+
{
|
|
63
|
+
type: "text",
|
|
64
|
+
text: "[read limit reached: no repository data was read. Do not call discovery tools again; use injected facts, commit remaining record_* facts, then call finalize_plan exactly once.]",
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
readAccessGuidance() {
|
|
70
|
+
return {
|
|
71
|
+
content: [
|
|
72
|
+
{
|
|
73
|
+
type: "text",
|
|
74
|
+
text: "[read denied: this node may read only named upstream artifacts or Scout-confirmed paths. Do not explore a new repository path; use committed facts or record an evidence gap, then finish the required terminal protocol.]",
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
assertReadablePath(absolutePath) {
|
|
80
|
+
if (!this.allowedReadPaths)
|
|
81
|
+
return;
|
|
82
|
+
const resolved = path.resolve(absolutePath);
|
|
83
|
+
if (this.allowedReadPaths.has(resolved))
|
|
84
|
+
return;
|
|
85
|
+
if (this.allowedReadRoots.some((root) => resolved === root || resolved.startsWith(`${root}${path.sep}`)))
|
|
86
|
+
return;
|
|
87
|
+
throw new PiReadAccessDeniedError(`read access denied: ${resolved}`);
|
|
88
|
+
}
|
|
89
|
+
reserveFile(absolutePath) {
|
|
90
|
+
this.assertElapsed();
|
|
91
|
+
if (this.files.has(absolutePath))
|
|
92
|
+
return;
|
|
93
|
+
if (this.files.size >= this.budget.maxFiles) {
|
|
94
|
+
const message = `read budget exhausted: file ${absolutePath} would exceed ${this.budget.maxFiles} files`;
|
|
95
|
+
this.exhaust(message);
|
|
96
|
+
}
|
|
97
|
+
this.files.add(absolutePath);
|
|
98
|
+
}
|
|
99
|
+
reserveBytes(bytes) {
|
|
100
|
+
this.assertElapsed();
|
|
101
|
+
const remaining = Math.max(0, this.budget.maxBytes - this.bytes);
|
|
102
|
+
if (remaining === 0) {
|
|
103
|
+
const message = `read budget exhausted: ${this.bytes} bytes (budget ${this.budget.maxBytes})`;
|
|
104
|
+
this.exhaust(message);
|
|
105
|
+
}
|
|
106
|
+
const granted = Math.min(bytes, remaining);
|
|
107
|
+
if (granted < bytes && this.budget.onExhaustion === "return-guidance") {
|
|
108
|
+
this.exhaust(`read budget exhausted: ${bytes} requested bytes would exceed ${this.budget.maxBytes} bytes`);
|
|
109
|
+
}
|
|
110
|
+
this.bytes += granted;
|
|
111
|
+
if (granted < bytes) {
|
|
112
|
+
this.report(`read budget truncated: ${bytes} requested bytes at ${this.bytes}/${this.budget.maxBytes} bytes`);
|
|
113
|
+
}
|
|
114
|
+
return granted;
|
|
115
|
+
}
|
|
116
|
+
async readFile(absolutePath) {
|
|
117
|
+
this.assertReadablePath(absolutePath);
|
|
118
|
+
this.reserveFile(absolutePath);
|
|
119
|
+
const metadata = await stat(absolutePath);
|
|
120
|
+
const granted = this.reserveBytes(metadata.size);
|
|
121
|
+
if (granted === metadata.size)
|
|
122
|
+
return await readFile(absolutePath);
|
|
123
|
+
const handle = await open(absolutePath, "r");
|
|
124
|
+
try {
|
|
125
|
+
const buffer = Buffer.allocUnsafe(granted);
|
|
126
|
+
const { bytesRead } = await handle.read(buffer, 0, granted, 0);
|
|
127
|
+
return buffer.subarray(0, bytesRead);
|
|
128
|
+
}
|
|
129
|
+
finally {
|
|
130
|
+
await handle.close();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
async access(absolutePath) {
|
|
134
|
+
this.assertReadablePath(absolutePath);
|
|
135
|
+
this.reserveFile(absolutePath);
|
|
136
|
+
await access(absolutePath);
|
|
137
|
+
}
|
|
138
|
+
limitToolResult(result) {
|
|
139
|
+
if (!result || typeof result !== "object" || !Array.isArray(result.content)) {
|
|
140
|
+
return result;
|
|
141
|
+
}
|
|
142
|
+
const content = result.content.map((item) => {
|
|
143
|
+
if (!item || item.type !== "text" || typeof item.text !== "string") {
|
|
144
|
+
this.report("read budget truncated: non-text tool result omitted");
|
|
145
|
+
return {
|
|
146
|
+
type: "text",
|
|
147
|
+
text: "[read budget reached: non-text result omitted]",
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
const granted = this.reserveBytes(Buffer.byteLength(item.text));
|
|
151
|
+
return {
|
|
152
|
+
...item,
|
|
153
|
+
text: truncateUtf8(item.text, granted),
|
|
154
|
+
};
|
|
155
|
+
});
|
|
156
|
+
return { ...result, content };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function truncateUtf8(text, maxBytes) {
|
|
160
|
+
if (Buffer.byteLength(text) <= maxBytes)
|
|
161
|
+
return text;
|
|
162
|
+
const marker = "\n[read result truncated by node budget]";
|
|
163
|
+
const markerBytes = Buffer.byteLength(marker);
|
|
164
|
+
if (maxBytes <= markerBytes)
|
|
165
|
+
return truncateUtf8Prefix(marker, maxBytes);
|
|
166
|
+
const contentBudget = maxBytes - markerBytes;
|
|
167
|
+
return `${truncateUtf8Prefix(text, contentBudget)}${marker}`;
|
|
168
|
+
}
|
|
169
|
+
function truncateUtf8Prefix(text, maxBytes) {
|
|
170
|
+
let result = "";
|
|
171
|
+
let bytes = 0;
|
|
172
|
+
for (const codePoint of text) {
|
|
173
|
+
const size = Buffer.byteLength(codePoint);
|
|
174
|
+
if (bytes + size > maxBytes)
|
|
175
|
+
break;
|
|
176
|
+
result += codePoint;
|
|
177
|
+
bytes += size;
|
|
178
|
+
}
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
181
|
+
function wrapTool(definition, guard, consumeResult) {
|
|
182
|
+
return {
|
|
183
|
+
...definition,
|
|
184
|
+
async execute(...args) {
|
|
185
|
+
try {
|
|
186
|
+
if (!guard.beforeToolOperation())
|
|
187
|
+
return guard.operationLimitGuidance();
|
|
188
|
+
const result = await definition.execute(...args);
|
|
189
|
+
return consumeResult ? guard.limitToolResult(result) : result;
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
if (error instanceof PiReadBudgetSoftLimitError) {
|
|
193
|
+
return guard.operationLimitGuidance();
|
|
194
|
+
}
|
|
195
|
+
if (error instanceof PiReadAccessDeniedError) {
|
|
196
|
+
return guard.readAccessGuidance();
|
|
197
|
+
}
|
|
198
|
+
throw error;
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Override Pi's built-in read-only tools with SDK definitions that meter the
|
|
205
|
+
* content before it reaches the model. The read tool caps filesystem bytes at
|
|
206
|
+
* the operation boundary; grep/find/ls have their returned payload clipped
|
|
207
|
+
* before the agent receives it. This is SDK-only by design: the CLI backend
|
|
208
|
+
* cannot inject same-name built-in overrides safely.
|
|
209
|
+
*/
|
|
210
|
+
export async function createPiReadBudgetCustomTools(input) {
|
|
211
|
+
const sdk = (await import("@earendil-works/pi-coding-agent"));
|
|
212
|
+
const createReadToolDefinition = sdk.createReadToolDefinition;
|
|
213
|
+
const createGrepToolDefinition = sdk.createGrepToolDefinition;
|
|
214
|
+
const createFindToolDefinition = sdk.createFindToolDefinition;
|
|
215
|
+
const createLsToolDefinition = sdk.createLsToolDefinition;
|
|
216
|
+
if (typeof createReadToolDefinition !== "function" ||
|
|
217
|
+
typeof createGrepToolDefinition !== "function" ||
|
|
218
|
+
typeof createFindToolDefinition !== "function" ||
|
|
219
|
+
typeof createLsToolDefinition !== "function") {
|
|
220
|
+
throw new Error("pi read budget policy unavailable: SDK read/grep/find/ls tool factories missing");
|
|
221
|
+
}
|
|
222
|
+
const guard = new PiReadBudgetGuard(input.budget, input.readAccess);
|
|
223
|
+
const read = wrapTool(createReadToolDefinition(input.repoRoot, {
|
|
224
|
+
operations: {
|
|
225
|
+
readFile: (absolutePath) => guard.readFile(absolutePath),
|
|
226
|
+
access: (absolutePath) => guard.access(absolutePath),
|
|
227
|
+
},
|
|
228
|
+
}), guard, false);
|
|
229
|
+
const grep = wrapTool(createGrepToolDefinition(input.repoRoot), guard, true);
|
|
230
|
+
const find = wrapTool(createFindToolDefinition(input.repoRoot), guard, true);
|
|
231
|
+
const ls = wrapTool(createLsToolDefinition(input.repoRoot), guard, true);
|
|
232
|
+
if (read.name !== "read" ||
|
|
233
|
+
grep.name !== "grep" ||
|
|
234
|
+
find.name !== "find" ||
|
|
235
|
+
ls.name !== "ls") {
|
|
236
|
+
throw new Error("pi read budget policy unavailable: SDK tool name mismatch");
|
|
237
|
+
}
|
|
238
|
+
return { customTools: [read, grep, find, ls], issues: () => guard.issues() };
|
|
239
|
+
}
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import { appendFile, mkdir } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { computeExtensionToolAllowlistExtras } from "./pi-extension-resolver.js";
|
|
3
4
|
import { BoundedTextPreview, classifyPiFailure, createPiJsonlStreamCollector, DEFAULT_ABORT_GRACE_MS, DEFAULT_STALL_TIMEOUT_MS, DEFAULT_TIMEOUT_MS, extractPiEventStopReason, extractAssistantTextFromPiJson, } from "./pi-executor.js";
|
|
4
5
|
import { serializeSessionEvent } from "./pi-event-serializer.js";
|
|
5
6
|
import { PI_RECOMMENDED_COMPACTION, PI_RECOMMENDED_RETRY, } from "../shared/pi-retry-settings.js";
|
|
7
|
+
/** Same-session recovery after a transport stall. Exactly one continue per SDK attempt. */
|
|
8
|
+
export const PI_SDK_STALL_CONTINUE_MAX = 1;
|
|
9
|
+
/**
|
|
10
|
+
* Fixed follow-up sent after aborting a stalled prompt.
|
|
11
|
+
* Kept short so the model can resume without repeating the original task payload.
|
|
12
|
+
*/
|
|
13
|
+
export const PI_SDK_STALL_CONTINUE_PROMPT = "继续。上一轮因长时间无 provider 活动已被中止;请从中断处接着完成,不要重复已完成的步骤。若此前没有有效输出,请重新执行原任务。";
|
|
6
14
|
let sdkSessionFactoryOverride;
|
|
7
15
|
let sdkImportOverrideForTests;
|
|
8
16
|
let sdkModuleOverrideForTests;
|
|
@@ -222,6 +230,9 @@ async function createSdkSession(sdk, input, shared) {
|
|
|
222
230
|
noContextFiles: true,
|
|
223
231
|
noSkills: true,
|
|
224
232
|
noExtensions: true,
|
|
233
|
+
...(input.additionalExtensionPaths && input.additionalExtensionPaths.length > 0
|
|
234
|
+
? { additionalExtensionPaths: input.additionalExtensionPaths }
|
|
235
|
+
: {}),
|
|
225
236
|
...(settingsManager ? { settingsManager } : {}),
|
|
226
237
|
appendSystemPromptOverride: (base) => [
|
|
227
238
|
...base,
|
|
@@ -229,6 +240,19 @@ async function createSdkSession(sdk, input, shared) {
|
|
|
229
240
|
],
|
|
230
241
|
});
|
|
231
242
|
await loader.reload();
|
|
243
|
+
// Discover extension-registered tool names BEFORE session creation (plan
|
|
244
|
+
// 2026-08-21 D5): verified against the real SDK — passing extension tool
|
|
245
|
+
// names in createAgentSession `tools` activates them, while a post-create
|
|
246
|
+
// setActiveToolsByName pin leaves the active set unchanged. Merge filtered
|
|
247
|
+
// extras into the create-time allowlist; protected builtins, the deny
|
|
248
|
+
// surface, and write-capable extension tools never join.
|
|
249
|
+
const extensionToolExtras = [];
|
|
250
|
+
if (input.additionalExtensionPaths &&
|
|
251
|
+
input.additionalExtensionPaths.length > 0) {
|
|
252
|
+
const registered = collectLoaderExtensionToolNames(loader);
|
|
253
|
+
extensionToolExtras.push(...computeExtensionToolAllowlistExtras(registered.map((name) => ({ name })), input.toolNames));
|
|
254
|
+
}
|
|
255
|
+
const sessionToolNames = [...input.toolNames, ...extensionToolExtras];
|
|
232
256
|
if (input.requireWriterCustomTools) {
|
|
233
257
|
if (!Array.isArray(input.customTools) || input.customTools.length === 0) {
|
|
234
258
|
throw new Error("pi writer tool policy missing customTools; refusing to create uncontrolled writer session");
|
|
@@ -238,7 +262,7 @@ async function createSdkSession(sdk, input, shared) {
|
|
|
238
262
|
cwd: input.cwd,
|
|
239
263
|
sessionManager: SessionManager.inMemory(input.cwd),
|
|
240
264
|
resourceLoader: loader,
|
|
241
|
-
tools:
|
|
265
|
+
tools: sessionToolNames,
|
|
242
266
|
modelRuntime,
|
|
243
267
|
...(settingsManager ? { settingsManager } : {}),
|
|
244
268
|
...(model ? { model } : {}),
|
|
@@ -247,6 +271,22 @@ async function createSdkSession(sdk, input, shared) {
|
|
|
247
271
|
? { customTools: input.customTools }
|
|
248
272
|
: {}),
|
|
249
273
|
});
|
|
274
|
+
if (extensionToolExtras.length > 0) {
|
|
275
|
+
// Fail closed when merged extension tools did not activate: a silent
|
|
276
|
+
// pin failure must never let the node run without its declared tools.
|
|
277
|
+
const activeTools = created.session.agent?.state?.tools;
|
|
278
|
+
if (!Array.isArray(activeTools)) {
|
|
279
|
+
throw new Error("pi SDK session does not expose agent.state.tools; refusing unverifiable extension-tool activation");
|
|
280
|
+
}
|
|
281
|
+
const activeNames = new Set(activeTools
|
|
282
|
+
.map((tool) => typeof tool?.name === "string" ? tool.name : undefined)
|
|
283
|
+
.filter((name) => Boolean(name)));
|
|
284
|
+
for (const extensionToolName of extensionToolExtras) {
|
|
285
|
+
if (!activeNames.has(extensionToolName)) {
|
|
286
|
+
throw new Error(`pi SDK extension-tool activation mismatch for ${extensionToolName}: tool did not activate at session creation`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
250
290
|
const customToolNames = (input.customTools ?? [])
|
|
251
291
|
.map((tool) => isRecord(tool) && typeof tool.name === "string" ? tool.name : undefined)
|
|
252
292
|
.filter((name) => Boolean(name));
|
|
@@ -259,7 +299,7 @@ async function createSdkSession(sdk, input, shared) {
|
|
|
259
299
|
.map((tool) => typeof tool?.name === "string" ? tool.name : undefined)
|
|
260
300
|
.filter((name) => Boolean(name)));
|
|
261
301
|
for (const customToolName of customToolNames) {
|
|
262
|
-
const expectedActive =
|
|
302
|
+
const expectedActive = sessionToolNames.includes(customToolName);
|
|
263
303
|
if (activeNames.has(customToolName) !== expectedActive) {
|
|
264
304
|
throw new Error(`pi SDK custom-tool activation mismatch for ${customToolName}: expectedActive=${expectedActive}`);
|
|
265
305
|
}
|
|
@@ -267,6 +307,47 @@ async function createSdkSession(sdk, input, shared) {
|
|
|
267
307
|
}
|
|
268
308
|
return created.session;
|
|
269
309
|
}
|
|
310
|
+
/**
|
|
311
|
+
* Collect tool names registered by loader-resolved extensions. The SDK exposes
|
|
312
|
+
* each extension's `tools` as a Record (name → definition) or Map; both shapes
|
|
313
|
+
* are handled. Missing getExtensions (older SDK) yields no extras — the node
|
|
314
|
+
* then runs with the baseline allowlist only.
|
|
315
|
+
*/
|
|
316
|
+
function collectLoaderExtensionToolNames(loader) {
|
|
317
|
+
const getExtensions = loader?.getExtensions;
|
|
318
|
+
if (typeof getExtensions !== "function")
|
|
319
|
+
return [];
|
|
320
|
+
let parsed;
|
|
321
|
+
try {
|
|
322
|
+
parsed = getExtensions.call(loader);
|
|
323
|
+
}
|
|
324
|
+
catch {
|
|
325
|
+
return [];
|
|
326
|
+
}
|
|
327
|
+
if (!parsed || typeof parsed !== "object")
|
|
328
|
+
return [];
|
|
329
|
+
const extensions = parsed.extensions;
|
|
330
|
+
if (!Array.isArray(extensions))
|
|
331
|
+
return [];
|
|
332
|
+
const names = [];
|
|
333
|
+
for (const extension of extensions) {
|
|
334
|
+
if (!extension || typeof extension !== "object")
|
|
335
|
+
continue;
|
|
336
|
+
const tools = extension.tools;
|
|
337
|
+
if (tools instanceof Map) {
|
|
338
|
+
for (const key of tools.keys()) {
|
|
339
|
+
if (typeof key === "string")
|
|
340
|
+
names.push(key);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
else if (tools && typeof tools === "object") {
|
|
344
|
+
for (const key of Object.keys(tools)) {
|
|
345
|
+
names.push(key);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return names;
|
|
350
|
+
}
|
|
270
351
|
/**
|
|
271
352
|
* Throttle high-noise Pi SDK events from session-events.jsonl.
|
|
272
353
|
* Always keeps tool lifecycle, turn/agent end, assistant messages, and errors.
|
|
@@ -404,6 +485,9 @@ function extractSdkUsageSample(event) {
|
|
|
404
485
|
const responseKey = responseKeyCandidates.find((value) => typeof value === "string" && value.length > 0);
|
|
405
486
|
return responseKey ? { responseKey, tokens } : { tokens };
|
|
406
487
|
}
|
|
488
|
+
function maxSdkContextTokens(samples) {
|
|
489
|
+
return samples.reduce((maximum, sample) => Math.max(maximum, sample.tokens), 0);
|
|
490
|
+
}
|
|
407
491
|
function aggregateSdkTokenUsage(samples) {
|
|
408
492
|
const identified = new Map();
|
|
409
493
|
let anonymousMaximum = 0;
|
|
@@ -425,6 +509,8 @@ function aggregateSdkTokenUsage(samples) {
|
|
|
425
509
|
* When reuseScope is active, only shared auth/model resources are reused; each attempt still
|
|
426
510
|
* creates and disposes its own session and resource loader. Session reuse across steps is
|
|
427
511
|
* deferred until a proven reset/isolation strategy exists.
|
|
512
|
+
* A transport stall may abort the current prompt and send one continue on the
|
|
513
|
+
* same session; that is in-attempt recovery, not cross-step session reuse.
|
|
428
514
|
*/
|
|
429
515
|
export async function executeSingleSdkAttempt(options) {
|
|
430
516
|
const modelConfig = options.modelConfig;
|
|
@@ -454,6 +540,9 @@ export async function executeSingleSdkAttempt(options) {
|
|
|
454
540
|
const stdoutPreview = new BoundedTextPreview("stdout");
|
|
455
541
|
const stdoutCollector = createPiJsonlStreamCollector();
|
|
456
542
|
const usageSamples = [];
|
|
543
|
+
let observedTurns = 0;
|
|
544
|
+
let contextBudgetExhausted = false;
|
|
545
|
+
let contextBudgetDetail = "";
|
|
457
546
|
// Writer-thinking-exhausted diagnosis evidence (AC-002). Collected in the
|
|
458
547
|
// subscribe callback regardless of shouldPersistSessionEvent filtering, so a
|
|
459
548
|
// length-stopped thinking-only attempt with no persisted write tool calls is
|
|
@@ -536,25 +625,67 @@ export async function executeSingleSdkAttempt(options) {
|
|
|
536
625
|
? { customTools: writerCustomTools }
|
|
537
626
|
: {}),
|
|
538
627
|
...(requireWriterCustomTools ? { requireWriterCustomTools: true } : {}),
|
|
628
|
+
...(options.piExtensionPaths && options.piExtensionPaths.length > 0
|
|
629
|
+
? { additionalExtensionPaths: options.piExtensionPaths }
|
|
630
|
+
: {}),
|
|
539
631
|
});
|
|
540
632
|
let resolveStall;
|
|
541
633
|
let resolveSettlement;
|
|
634
|
+
let resolveContextBudget;
|
|
542
635
|
let settled = false;
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
636
|
+
let abortConfirmed = true;
|
|
637
|
+
const persistRuntimeNote = (event) => {
|
|
638
|
+
const line = serializeSessionEvent(event);
|
|
639
|
+
stdoutPreview.append(`${line}\n`);
|
|
640
|
+
stdoutCollector.append(`${line}\n`);
|
|
641
|
+
sessionEventAppender?.append(line, event);
|
|
642
|
+
};
|
|
643
|
+
const resetSettlement = () => {
|
|
644
|
+
settled = false;
|
|
645
|
+
return new Promise((resolve) => {
|
|
646
|
+
resolveSettlement = resolve;
|
|
647
|
+
});
|
|
648
|
+
};
|
|
649
|
+
const resetStallPromise = () => {
|
|
650
|
+
if (stallTimeoutMs <= 0) {
|
|
651
|
+
resolveStall = undefined;
|
|
652
|
+
return null;
|
|
653
|
+
}
|
|
654
|
+
return new Promise((resolve) => {
|
|
548
655
|
resolveStall = resolve;
|
|
549
|
-
})
|
|
550
|
-
|
|
656
|
+
});
|
|
657
|
+
};
|
|
551
658
|
const armStallWatchdog = () => {
|
|
552
|
-
if (
|
|
659
|
+
if (stallTimeoutMs <= 0 || !resolveStall)
|
|
553
660
|
return;
|
|
554
661
|
if (stallHandle)
|
|
555
662
|
clearTimeout(stallHandle);
|
|
556
663
|
stallHandle = setTimeout(() => resolveStall?.("stall"), stallTimeoutMs);
|
|
557
664
|
};
|
|
665
|
+
const checkContextBudget = () => {
|
|
666
|
+
if (contextBudgetExhausted || !options.contextBudget)
|
|
667
|
+
return;
|
|
668
|
+
// Context protection follows the largest provider-reported response
|
|
669
|
+
// snapshot, not cumulative billing usage across turns. Summing repeated
|
|
670
|
+
// input contexts would abort healthy multi-turn sessions far below the
|
|
671
|
+
// model's actual context window.
|
|
672
|
+
const observedTokens = maxSdkContextTokens(usageSamples);
|
|
673
|
+
const turnExceeded = options.contextBudget.maxTurns !== undefined &&
|
|
674
|
+
observedTurns >= options.contextBudget.maxTurns;
|
|
675
|
+
const tokenExceeded = options.contextBudget.maxTokens !== undefined &&
|
|
676
|
+
observedTokens >= options.contextBudget.maxTokens;
|
|
677
|
+
if (!turnExceeded && !tokenExceeded)
|
|
678
|
+
return;
|
|
679
|
+
contextBudgetExhausted = true;
|
|
680
|
+
contextBudgetDetail = [
|
|
681
|
+
"context-budget-exhausted:",
|
|
682
|
+
`observedTurns=${observedTurns}`,
|
|
683
|
+
`observedTokens=${observedTokens}`,
|
|
684
|
+
`maxTurns=${options.contextBudget.maxTurns ?? "unbounded"}`,
|
|
685
|
+
`maxTokens=${options.contextBudget.maxTokens ?? "unbounded"}`,
|
|
686
|
+
].join(" ");
|
|
687
|
+
resolveContextBudget?.("context-budget");
|
|
688
|
+
};
|
|
558
689
|
unsubscribe = session.subscribe((event) => {
|
|
559
690
|
if (event.type === "agent_settled" && !settled) {
|
|
560
691
|
settled = true;
|
|
@@ -578,6 +709,9 @@ export async function executeSingleSdkAttempt(options) {
|
|
|
578
709
|
const usageSample = extractSdkUsageSample(event);
|
|
579
710
|
if (usageSample)
|
|
580
711
|
usageSamples.push(usageSample);
|
|
712
|
+
if (event.type === "turn_end")
|
|
713
|
+
observedTurns += 1;
|
|
714
|
+
checkContextBudget();
|
|
581
715
|
// Capture writer-thinking-exhausted evidence BEFORE the
|
|
582
716
|
// shouldPersistSessionEvent filter: thinking_delta is intentionally
|
|
583
717
|
// excluded from persisted JSONL, but must still count toward the
|
|
@@ -590,17 +724,6 @@ export async function executeSingleSdkAttempt(options) {
|
|
|
590
724
|
stdoutCollector.append(`${line}\n`);
|
|
591
725
|
sessionEventAppender?.append(line, event);
|
|
592
726
|
});
|
|
593
|
-
const filePrefix = options.attachedFiles
|
|
594
|
-
.map((file) => `@${file}`)
|
|
595
|
-
.join(" ");
|
|
596
|
-
const promptMessage = filePrefix
|
|
597
|
-
? `${filePrefix}\n${options.userMessage}`
|
|
598
|
-
: options.userMessage;
|
|
599
|
-
const promptPromise = session.prompt(promptMessage);
|
|
600
|
-
// A settlement event is permitted to win the race while prompt() remains
|
|
601
|
-
// pending. Observe a later rejection so it never becomes unhandled.
|
|
602
|
-
void promptPromise.catch(() => { });
|
|
603
|
-
armStallWatchdog();
|
|
604
727
|
const timeoutPromise = timeoutMs > 0
|
|
605
728
|
? new Promise((resolve) => {
|
|
606
729
|
timeoutHandle = setTimeout(() => {
|
|
@@ -608,21 +731,90 @@ export async function executeSingleSdkAttempt(options) {
|
|
|
608
731
|
}, timeoutMs);
|
|
609
732
|
})
|
|
610
733
|
: null;
|
|
611
|
-
const
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
734
|
+
const runTurn = async (userMessage) => {
|
|
735
|
+
const settlementPromise = resetSettlement();
|
|
736
|
+
const stallPromise = resetStallPromise();
|
|
737
|
+
const contextBudgetPromise = options.contextBudget
|
|
738
|
+
? new Promise((resolve) => {
|
|
739
|
+
resolveContextBudget = resolve;
|
|
740
|
+
})
|
|
741
|
+
: null;
|
|
742
|
+
const promptPromise = session.prompt(userMessage);
|
|
743
|
+
// A settlement event is permitted to win the race while prompt() remains
|
|
744
|
+
// pending. Observe a later rejection so it never becomes unhandled.
|
|
745
|
+
void promptPromise.catch(() => { });
|
|
746
|
+
armStallWatchdog();
|
|
747
|
+
return Promise.race([
|
|
748
|
+
promptPromise.then(() => "done"),
|
|
749
|
+
settlementPromise,
|
|
750
|
+
...(timeoutPromise ? [timeoutPromise] : []),
|
|
751
|
+
...(stallPromise ? [stallPromise] : []),
|
|
752
|
+
...(contextBudgetPromise ? [contextBudgetPromise] : []),
|
|
753
|
+
]);
|
|
754
|
+
};
|
|
755
|
+
const disposeSession = async () => {
|
|
623
756
|
disposeAttempted = true;
|
|
624
757
|
const disposeConfirmed = await runSessionActionWithGrace("dispose", () => session.dispose());
|
|
625
758
|
terminationConfirmed = abortConfirmed && disposeConfirmed;
|
|
759
|
+
};
|
|
760
|
+
const failClosedAfterWait = async (reason, extraStderr, abortFirst = true) => {
|
|
761
|
+
timedOut = true;
|
|
762
|
+
appendStderr(reason === "stall"
|
|
763
|
+
? `pi SDK step stalled after ${stallTimeoutMs}ms with no provider activity`
|
|
764
|
+
: `pi SDK step timed out after ${timeoutMs}ms`);
|
|
765
|
+
if (extraStderr)
|
|
766
|
+
appendStderr(extraStderr);
|
|
767
|
+
if (abortFirst) {
|
|
768
|
+
abortConfirmed = await runSessionActionWithGrace("abort", () => session.abort());
|
|
769
|
+
}
|
|
770
|
+
await disposeSession();
|
|
771
|
+
};
|
|
772
|
+
const filePrefix = options.attachedFiles
|
|
773
|
+
.map((file) => `@${file}`)
|
|
774
|
+
.join(" ");
|
|
775
|
+
const promptMessage = filePrefix
|
|
776
|
+
? `${filePrefix}\n${options.userMessage}`
|
|
777
|
+
: options.userMessage;
|
|
778
|
+
let continuesSent = 0;
|
|
779
|
+
let raced = await runTurn(promptMessage);
|
|
780
|
+
if (raced === "context-budget") {
|
|
781
|
+
appendStderr(contextBudgetDetail);
|
|
782
|
+
abortConfirmed = await runSessionActionWithGrace("abort", () => session.abort());
|
|
783
|
+
await disposeSession();
|
|
784
|
+
}
|
|
785
|
+
else if (raced === "stall") {
|
|
786
|
+
abortConfirmed = await runSessionActionWithGrace("abort", () => session.abort());
|
|
787
|
+
const withinAbsoluteTimeout = timeoutMs <= 0 || Date.now() - startedAt < timeoutMs;
|
|
788
|
+
const canContinue = abortConfirmed &&
|
|
789
|
+
writeToolCallCount === 0 &&
|
|
790
|
+
withinAbsoluteTimeout &&
|
|
791
|
+
continuesSent < PI_SDK_STALL_CONTINUE_MAX;
|
|
792
|
+
if (canContinue) {
|
|
793
|
+
// The aborted turn's stopReason must not leak onto a recovered continue.
|
|
794
|
+
stopReason = undefined;
|
|
795
|
+
continuesSent += 1;
|
|
796
|
+
persistRuntimeNote({
|
|
797
|
+
type: "loop-agent-stall-continue",
|
|
798
|
+
prompt: PI_SDK_STALL_CONTINUE_PROMPT,
|
|
799
|
+
maxContinues: PI_SDK_STALL_CONTINUE_MAX,
|
|
800
|
+
});
|
|
801
|
+
raced = await runTurn(PI_SDK_STALL_CONTINUE_PROMPT);
|
|
802
|
+
if (raced !== "done" && raced !== "settled") {
|
|
803
|
+
await failClosedAfterWait(raced === "stall" ? "stall" : "absolute-timeout", raced === "stall"
|
|
804
|
+
? "pi SDK stall continue did not recover; treating as timeout"
|
|
805
|
+
: "pi SDK stall continue hit the absolute timeout", true);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
else {
|
|
809
|
+
await failClosedAfterWait("stall", !abortConfirmed
|
|
810
|
+
? undefined
|
|
811
|
+
: writeToolCallCount > 0
|
|
812
|
+
? "pi SDK stall continue skipped because write tools already ran"
|
|
813
|
+
: "pi SDK stall continue skipped because absolute timeout elapsed", false);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
else if (raced === "absolute-timeout") {
|
|
817
|
+
await failClosedAfterWait("absolute-timeout");
|
|
626
818
|
}
|
|
627
819
|
}
|
|
628
820
|
catch (error) {
|