@tea-agent/loop-agent 0.41.1 → 0.42.0-next.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +397 -5
- package/dist/adapters/context-transfer/optional-pi-handoff.js +31 -0
- package/dist/adapters/context-transfer/pi-session.js +61 -0
- package/dist/application/dag/generate-task-dag.js +75 -19
- package/dist/application/dag/run-dag.js +41 -0
- package/dist/application/task-lifecycle/advance.js +24 -5
- package/dist/application/task-lifecycle/observe.js +171 -17
- package/dist/application/task-lifecycle/plan-transitions.js +42 -7
- package/dist/application/task-lifecycle/recommendations.js +13 -2
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +19 -0
- package/dist/cli/program.js +29 -2
- package/dist/commands/client-recovery.js +3 -0
- package/dist/commands/dag-artifact.js +284 -0
- package/dist/commands/dag-context.js +184 -0
- package/dist/commands/dag-follow-up.js +138 -0
- package/dist/commands/dag-rerun-task.js +2 -0
- package/dist/commands/dag-rerun.js +206 -1
- package/dist/commands/init.js +27 -1
- package/dist/commands/task-advance.js +19 -0
- package/dist/executors/dag-pi-executor.js +4013 -86
- package/dist/executors/pi-executor.js +15 -4
- package/dist/executors/pi-read-budget-policy.js +239 -0
- package/dist/executors/pi-sdk-executor.js +3 -0
- package/dist/executors/shell-executor.js +978 -188
- package/dist/executors/shell-write-guard.js +7 -0
- package/dist/{worker → infrastructure}/console/app-data.js +4 -0
- package/dist/infrastructure/console/artifact-revision-store.js +430 -0
- package/dist/infrastructure/console/context-export-store.js +160 -0
- package/dist/infrastructure/console/dir-lock.js +132 -0
- package/dist/infrastructure/console/operation-store.js +417 -0
- package/dist/infrastructure/harness/artifact-store.js +10 -1
- package/dist/infrastructure/harness/atomic-write.js +12 -2
- package/dist/shared/context-transfer/artifact-revision.js +172 -0
- package/dist/shared/context-transfer.js +418 -0
- package/dist/shared/dag-failure-category.js +12 -0
- package/dist/shared/openspec-spec.js +70 -4
- package/dist/shared/operator/capabilities.js +21 -0
- package/dist/shared/operator/safe-run-summary.js +1 -0
- package/dist/shared/path-safety.js +93 -0
- package/dist/shared/preview.js +28 -4
- package/dist/task/config-types.js +107 -6
- package/dist/task/contract/adopt.js +4 -0
- package/dist/task/contract/import-revision.js +4 -0
- package/dist/task/contract/project.js +3 -0
- package/dist/task/contract/schema.js +2 -1
- package/dist/task/frontend-project-capability.js +203 -20
- package/dist/task/runtime.js +5 -2
- package/dist/task/source-prepare/build-draft.js +3 -3
- package/dist/task/source-prepare/fragment-inventory.js +64 -25
- package/dist/task/source-prepare/prepare.js +126 -21
- package/dist/task/source-prepare/semantic-intake.js +6 -2
- package/dist/task/source-references.js +22 -1
- package/dist/task/task-demand-routing.js +3 -0
- package/dist/worker/console/chat/chat-event-store.js +2 -2
- package/dist/worker/console/chat/model-resolver.js +7 -4
- package/dist/worker/console/chat/pi-runtime.js +86 -5
- package/dist/worker/console/chat/resource-preferences-store.js +1 -1
- package/dist/worker/console/chat/routes.js +136 -53
- package/dist/worker/console/chat/sdd-data-alignment.js +222 -0
- package/dist/worker/console/chat/session-catalog.js +2 -0
- package/dist/worker/console/chat/session-store.js +43 -1
- package/dist/worker/console/chat/shortcuts.js +18 -2
- package/dist/worker/console/chat/user-questions.js +1 -1
- package/dist/worker/console/console-update-runtime.js +1 -1
- package/dist/worker/console/context-transfer-diagnostics.js +198 -0
- package/dist/worker/console/dag-confirmation.js +1 -1
- package/dist/worker/console/dag-execution-receipt.js +1 -1
- package/dist/worker/console/doctor.js +1 -1
- package/dist/worker/console/draft-store.js +1 -1
- 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/human-gate-token.js +1 -1
- package/dist/worker/console/index.js +6 -3
- package/dist/worker/console/interview/assessment.js +1 -1
- package/dist/worker/console/interview/session.js +1 -1
- package/dist/worker/console/operation-runner.js +45 -4
- package/dist/worker/console/operation-sse.js +1 -1
- package/dist/worker/console/operation-wait.js +1 -1
- package/dist/worker/console/operator-actions.js +273 -10
- package/dist/worker/console/operator-surface-health.js +1 -1
- package/dist/worker/console/operator-user-error.js +169 -0
- package/dist/worker/console/pi-plugins.js +57 -0
- package/dist/worker/console/pi-readiness.js +3 -2
- package/dist/worker/console/routes.js +637 -8
- package/dist/worker/console/security.js +35 -0
- package/dist/worker/console/server.js +29 -4
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-D7oU983t.js → abnfDiagram-N423BO3Z-CcS17TBr.js} +1 -1
- package/dist/worker/console/static/assets/{arc-BrKSRdFS.js → arc-COptKq2S.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CiXV7L56.js → architectureDiagram-T3A2C74G-h4LKMHjP.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-D_Hs_gIG.js → blockDiagram-VBNYF7ZC-COA1MOH0.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DbM-Lqqh.js → c4Diagram-5PPSVZJV-DJUf0QPm.js} +1 -1
- package/dist/worker/console/static/assets/channel-DdBCaOJ6.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-D1M8uqJw.js → chunk-2GRJ4B5K-mtWfKrUX.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-4D3RFHT6.js → chunk-2Q5K7J3B-B8pXsxDQ.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-CdwpY_n9.js → chunk-5RXB4S5H-ipKzByl1.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BLeLZ9PG.js → chunk-5VM5RSS4-DYi3Ald_.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-CeRNEMvc.js → chunk-6Q2QTUOP-DQtGYoty.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CjkHP8Vy.js → chunk-GF5L2VYU-BU0qS2YV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-B54ZUQY-.js → chunk-JWPE2WC7-DVH9dIGE.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-4bKaMVmx.js → chunk-KBJHAD2P-Dt60SmgM.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-Cyjfznu_.js → chunk-RYQCIY6F-BoaUGuEY.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CDN-zGFV.js → chunk-XXDRQBXY-_WHFDTp2.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DZFra1GO.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DZFra1GO.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-HxJmz411.js → cose-bilkent-JH36ORCC-BxkbTIRd.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-DtSt5K2w.js → cynefin-VYW2F7L2-Bf2UVnoG.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-VtkSpRmG.js → cynefinDiagram-MW4NZA55-Bo23q1J_.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-Bj4DT4du.js → dagre-VZM6K2ZE-Dd_UU49i.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Bpuk6BmP.js → diagram-7IWD3JNH-ebUa1a9y.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BHYGv_yO.js → diagram-B4RE2ZJO-BucthU8r.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DEx3WwnT.js → diagram-LBJQPF4R-BgqNpAkm.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-Cj0dS5e8.js → diagram-Q27KOJAE-DC4q5NGa.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-B97h8l21.js → diagram-UB23O5K3-CBqCaMeb.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-DMSFAhBd.js → ebnfDiagram-BXEA7PRR-CGtnbQ3-.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DhVOCbt6.js → erDiagram-JOGREHBK-CG3LUao5.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-GlakITWL.js → flowDiagram-UKHOOZJN-D2ZVgoFS.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-VxPpu9eQ.js → ganttDiagram-PKOTCBZU-DoYFZiKt.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Bs_e5DRT.js → gitGraphDiagram-DS77QQ5N-CKoP1s6j.js} +1 -1
- package/dist/worker/console/static/assets/index-CAZ2fC_X.css +1 -0
- package/dist/worker/console/static/assets/index-vbTcFnFs.js +449 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-BXYuU4Uw.js → infoDiagram-6WML65LV-Duofv8p2.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-C34erCbU.js → ishikawaDiagram-WSZJBQD7-D2nlCkA1.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DZCAiOU3.js → journeyDiagram-NVQOT4AX-Dd4IHdus.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-C9gaTdUm.js → kanban-definition-27J2QSJJ-Bi20AOdt.js} +1 -1
- package/dist/worker/console/static/assets/{linear-CvI0Z17U.js → linear-D59YJ9kB.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-DMMSIx7j.js → mermaid.core-Bl13LpNa.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-BKKP0EO6.js → mindmap-definition-FAOFIHXS-C6DLP5eY.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-C3LiFbl5.js → pegDiagram-VL7TDLO6-BjO4cy64.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-Bb3Om2Dz.js → pieDiagram-7S7Q4E2Y-D7i2qZTG.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-DNthMjzc.js → quadrantDiagram-CIZ2JOQS-Dx8o_h0y.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-miFsr4Tv.js → railroadDiagram-AXF67PYL-B55orI_t.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-yXUncmn6.js → requirementDiagram-LRYGKXZP-zWaehp6f.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-DLeWKLak.js → sankeyDiagram-W5VNT64P-CcA-pjvD.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-CI3he1R3.js → sequenceDiagram-SI44F4Z6-BOFbzNHI.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-B7jfDkS5.js → sizeCapture-X5ZJPWSS-BjAejah1.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-BmrZod7J.js → stateDiagram-OKZ733FA-DXYwxJwZ.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Clg3V9t1.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CDOlHVSY.js → swimlanes-SLNWSIFB-OYiOch8n.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DX1dxAAW.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Cz-XX1gy.js → timeline-definition-Z64GVDOM-cZfH3nmU.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-caV4cI8C.js → vennDiagram-T6HMQDX7-BDE7b3E1.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-dImPlnIO.js → wardleyDiagram-T6FBY63Y-kyyGy9WJ.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CtRYVJcn.js → xychartDiagram-ELKLHX3M-CJj6VTog.js} +1 -1
- package/dist/worker/console/static/index.html +7 -2
- package/dist/worker/console/static-src/active-run-badge.js +17 -0
- package/dist/worker/console/static-src/app/useOperatorActions.js +3 -2
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +5 -5
- package/dist/worker/console/static-src/operator-chat/input-history.js +8 -6
- package/dist/worker/console/static-src/operator-chat/runtime-snapshot-store.js +10 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +258 -23
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +112 -4
- package/dist/worker/console/static-src/operator-chat/useComposer.js +13 -4
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +29 -6
- package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +8 -2
- package/dist/worker/console/static-src/shell/console-update-reload.js +52 -0
- package/dist/worker/console/static-src/shell/workspace-route.js +11 -0
- package/dist/worker/console/workspace-context.js +142 -2
- package/dist/worker/console/workspace-registry.js +2 -2
- package/dist/worker/continuation/worker-continuation.js +278 -0
- package/dist/worker/materialize/frontend-split-task-materializer.js +72 -0
- package/dist/worker/observe/health.js +1 -1
- package/dist/worker/observe/node-transparency.js +572 -0
- package/dist/worker/observe/routes.js +55 -1
- package/dist/worker/observe/static/api.js +69 -5
- package/dist/worker/observe/static/dag-context-reason-labels.d.ts +9 -0
- package/dist/worker/observe/static/dag-context-reason-labels.js +120 -0
- package/dist/worker/observe/static/dag-helpers.js +4 -2
- package/dist/worker/observe/static/dag-node-purpose.js +5 -0
- package/dist/worker/observe/static/inspect-workspace.js +23 -0
- package/dist/worker/observe/static/kpi.js +2 -2
- package/dist/worker/observe/static/operator-chrome.d.ts +10 -2
- package/dist/worker/observe/static/operator-chrome.js +37 -23
- package/dist/worker/observe/static/relations.js +2 -2
- package/dist/worker/observe/static/router.d.ts +12 -1
- package/dist/worker/observe/static/router.js +48 -6
- package/dist/worker/observe/static/run-processing.js +4 -2
- package/dist/worker/observe/static/shell-chrome.js +2 -2
- package/dist/worker/observe/static/state.js +8 -1
- package/dist/worker/observe/static/styles.css +1092 -86
- package/dist/worker/observe/static/views/dag-inspector.js +1228 -64
- package/dist/worker/observe/static/views/dag.js +10 -2
- package/dist/worker/observe/static/views/dags.js +3 -1
- package/dist/worker/observe/static/views/dashboard.js +14 -6
- package/dist/worker/observe/static/views/pool.js +7 -2
- package/dist/worker/observe/static/views/run.js +12 -3
- package/dist/worker/observe/static/views/session-timeline.js +142 -18
- package/dist/worker/observe/static/views/task.js +7 -2
- package/dist/worker/outcomes/adapters.js +19 -0
- package/dist/worker/outcomes/types.js +1 -0
- package/dist/worker/pool/attempt-lease.js +97 -66
- package/dist/worker/pool/begin-attempt-with-lease.js +1 -0
- package/dist/worker/pool/reconcile.js +46 -1
- package/dist/worker/pool/run-store.js +2 -0
- package/dist/worker/pool/state-projection.js +2 -0
- package/dist/worker/task-spec/workflow-routing.js +8 -3
- package/dist/workflows/dag/artifact-bindings.js +149 -0
- package/dist/workflows/dag/artifact-revision-schema-registry.js +31 -0
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +215 -28
- package/dist/workflows/dag/backend-test-markdown-workflow.js +13 -1
- package/dist/workflows/dag/backend-test-plan-protocol.js +106 -0
- package/dist/workflows/dag/backend-test-scenario-param.js +172 -25
- package/dist/workflows/dag/backend-test-scenario-partitions.js +62 -1
- package/dist/workflows/dag/backend-test-writer-completeness.js +104 -12
- package/dist/workflows/dag/budget-enforcement.js +10 -0
- package/dist/workflows/dag/context-receipt.js +305 -0
- package/dist/workflows/dag/context-transfer/context-bundle.js +423 -0
- package/dist/workflows/dag/context-transfer/operator-actions.js +61 -0
- package/dist/workflows/dag/context-transfer/renderers.js +93 -0
- package/dist/workflows/dag/contract-validator-registrations.js +1 -2
- package/dist/workflows/dag/dag-retry-schema.js +138 -0
- package/dist/workflows/dag/frontend-closeout.js +221 -0
- package/dist/workflows/dag/frontend-design-policy.js +400 -0
- package/dist/workflows/dag/frontend-human-decision.js +182 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +1237 -192
- package/dist/workflows/dag/frontend-plan-render.js +2 -1
- package/dist/workflows/dag/frontend-prewrite-gate.js +256 -350
- package/dist/workflows/dag/frontend-provider-capability-matrix.js +159 -0
- package/dist/workflows/dag/frontend-recovery-capsule.js +455 -0
- package/dist/workflows/dag/frontend-recovery-controller.js +226 -0
- package/dist/workflows/dag/frontend-recovery-lineage.js +178 -0
- package/dist/workflows/dag/frontend-recovery-plan.js +21 -10
- package/dist/workflows/dag/frontend-recovery-run.js +166 -34
- package/dist/workflows/dag/frontend-repair.js +1 -432
- package/dist/workflows/dag/frontend-review-context.js +261 -15
- package/dist/workflows/dag/frontend-review-findings.js +270 -0
- package/dist/workflows/dag/frontend-shadow-dual-write.js +941 -0
- package/dist/workflows/dag/frontend-shape-capsule-store.js +191 -0
- package/dist/workflows/dag/frontend-shape-facts.js +419 -0
- package/dist/workflows/dag/frontend-shape.js +435 -0
- package/dist/workflows/dag/frontend-source-fidelity-ledger.js +108 -0
- package/dist/workflows/dag/frontend-split-application-service.js +203 -0
- package/dist/workflows/dag/frontend-split-orchestrator.js +899 -0
- package/dist/workflows/dag/frontend-typed-event-store.js +452 -0
- package/dist/workflows/dag/frontend-typed-event-transaction.js +180 -0
- package/dist/workflows/dag/frontend-verification-trace.js +252 -25
- package/dist/workflows/dag/frontend-worktree-diff.js +250 -17
- package/dist/workflows/dag/frontend-writer-admission.js +319 -0
- package/dist/workflows/dag/frontend-writer-rollback.js +32 -0
- package/dist/workflows/dag/frontend-writer-status.js +256 -0
- package/dist/workflows/dag/init-hybrid.js +1174 -562
- package/dist/workflows/dag/interrupt-request.js +7 -0
- package/dist/workflows/dag/node-execution.js +940 -31
- package/dist/workflows/dag/path-safety.js +1 -0
- package/dist/workflows/dag/prompt.js +99 -7
- package/dist/workflows/dag/recovery-lease.js +170 -0
- package/dist/workflows/dag/report.js +37 -1
- package/dist/workflows/dag/rerun-feedback.js +315 -1
- package/dist/workflows/dag/rerun-plan.js +568 -3
- package/dist/workflows/dag/rerun-run.js +642 -18
- package/dist/workflows/dag/rerun-task.js +251 -13
- package/dist/workflows/dag/retry-policy.js +219 -104
- package/dist/workflows/dag/runner.js +596 -121
- package/dist/workflows/dag/scheduler.js +133 -20
- package/dist/workflows/dag/skill-snapshot.js +17 -0
- package/dist/workflows/dag/types.js +426 -22
- package/dist/workflows/dag/validate.js +28 -12
- package/docs/architecture/runtime-boundaries.md +6 -6
- package/docs/architecture/worker-and-feature.md +1 -1
- package/docs/examples/README.md +5 -0
- package/docs/init-surface.manifest.json +30 -12
- package/docs/skills/vetted-skill-registry.md +4 -2
- package/docs/templates/README.md +2 -0
- package/docs/templates/agent-dag-report.schema.json +8 -2
- package/docs/templates/agent-dag.schema.json +33 -1
- package/docs/templates/backend-test-dag.json +23 -20
- package/docs/templates/frontend-implementation-contract.schema.json +4 -1
- package/docs/templates/frontend-implementation-dag.json +89 -0
- package/docs/templates/product-line/task.yaml +1 -1
- package/docs/templates/spec-registry.schema.json +45 -0
- package/harness.json +1 -1
- package/package.json +6 -3
- package/skills/frontend-bounded-implement/SKILL.md +15 -14
- package/skills/frontend-bounded-implement/references/code-standards.md +19 -0
- package/skills/frontend-contract/SKILL.md +23 -0
- package/skills/frontend-contract/references/contract-protocol.md +34 -0
- package/skills/frontend-design-review/SKILL.md +22 -41
- package/skills/frontend-plan/SKILL.md +26 -0
- package/skills/frontend-plan/references/decision-contract.md +37 -0
- package/skills/frontend-plan/references/design-decisions.md +17 -0
- package/skills/frontend-review/SKILL.md +20 -15
- package/skills/frontend-review/references/review-findings.md +6 -7
- package/skills/frontend-scout/SKILL.md +25 -0
- package/skills/frontend-scout/references/design-evidence.md +16 -0
- package/skills/frontend-scout/references/scout-evidence.md +23 -0
- package/skills/frontend-verification/SKILL.md +1 -1
- package/skills/loop-agent/references/command-reference.md +7 -0
- package/skills/loop-agent/references/hybrid-dag.md +2 -2
- package/dist/worker/console/operation-store.js +0 -169
- package/dist/worker/console/static/assets/channel-D1xajGpe.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BmuGTfRr.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BmuGTfRr.js +0 -1
- package/dist/worker/console/static/assets/index-BLzcAykx.js +0 -451
- package/dist/worker/console/static/assets/index-Cs9JUSPs.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-D9ykKEWP.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BvPOXALT.js +0 -8
- package/skills/frontend-implementation/SKILL.md +0 -52
- package/skills/frontend-implementation/references/code-standards.md +0 -33
- package/skills/frontend-implementation/references/design-spec.md +0 -56
- package/skills/frontend-implementation/references/node-contracts.md +0 -31
- /package/dist/{worker → infrastructure}/console/repo-fingerprint.js +0 -0
|
@@ -13,10 +13,11 @@
|
|
|
13
13
|
* Read routes (GET capabilities / sessions) are open to loopback.
|
|
14
14
|
*/
|
|
15
15
|
import { randomUUID } from "node:crypto";
|
|
16
|
+
import { access } from "node:fs/promises";
|
|
16
17
|
import { parseAssistantContent } from "./assistant-content.js";
|
|
17
18
|
import { OPERATOR_CHAT_ALLOWED_TOOLS, OPERATOR_CHAT_DENIED_TOOLS, OPERATOR_CHAT_DENIED_OPERATOR_ACTIONS, } from "./tools.js";
|
|
18
19
|
import { buildModelCallableToolSchemas } from "./tool-adapter.js";
|
|
19
|
-
import { cleanChatTitle, acceptedOperationIdFromResult } from "./session-store.js";
|
|
20
|
+
import { cleanChatTitle, acceptedOperationIdFromResult, isBlankChatSession } from "./session-store.js";
|
|
20
21
|
import { hashChatTurnPayload, projectChatToolEventPayload, projectCompactSnapshot, } from "./chat-event-store.js";
|
|
21
22
|
import { projectDeferredTurnSummary, projectDeferredTurnSummaries, } from "./deferred-turn.js";
|
|
22
23
|
import { THINKING_LEVELS, RuntimeSnapshotUnavailableError, } from "./pi-runtime.js";
|
|
@@ -41,7 +42,13 @@ import { openSseResponse, writeSseEvent } from "../operation-sse.js";
|
|
|
41
42
|
import { buildTodoCompactionInstructions, projectCurrentTodos } from "./todos.js";
|
|
42
43
|
import { buildGoalCompactionInstructions, buildGoalContinuationContext, } from "./goals.js";
|
|
43
44
|
import { classifyGoalRoundAdmission, } from "./goal-round-driver.js";
|
|
44
|
-
import { isTerminalOperationState } from "
|
|
45
|
+
import { isTerminalOperationState } from "../../../infrastructure/console/operation-store.js";
|
|
46
|
+
import { SddDataAlignmentError, } from "./sdd-data-alignment.js";
|
|
47
|
+
function projectBrowserChatSession(record) {
|
|
48
|
+
const { telemetrySessionId, ...projected } = record;
|
|
49
|
+
void telemetrySessionId;
|
|
50
|
+
return projected;
|
|
51
|
+
}
|
|
45
52
|
/**
|
|
46
53
|
* Reattach the durable event-ring order to session-store rows. The session
|
|
47
54
|
* store remains the content authority; the ring only supplies chronology.
|
|
@@ -468,7 +475,7 @@ function sendRuntimeUnavailable(res, error) {
|
|
|
468
475
|
* handled here — only reopen/init readiness failures.
|
|
469
476
|
*/
|
|
470
477
|
function mapReopenSessionFailure(reopen) {
|
|
471
|
-
if (reopen.code === "NO_SESSION_FILE") {
|
|
478
|
+
if (reopen.code === "NO_SESSION_FILE" || reopen.code === "SESSION_FILE_MISSING") {
|
|
472
479
|
return {
|
|
473
480
|
status: 404,
|
|
474
481
|
code: "PI_SESSION_NOT_FOUND",
|
|
@@ -488,6 +495,19 @@ function mapReopenSessionFailure(reopen) {
|
|
|
488
495
|
message: reopen.message,
|
|
489
496
|
};
|
|
490
497
|
}
|
|
498
|
+
/** Drop an orphan catalog record whose promised Pi JSONL is gone. */
|
|
499
|
+
async function healMissingSessionFile(deps, sessionId, reopen) {
|
|
500
|
+
if (reopen && reopen.code !== "NO_SESSION_FILE" && reopen.code !== "SESSION_FILE_MISSING") {
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
if (typeof deps.store.remove !== "function")
|
|
504
|
+
return;
|
|
505
|
+
await deps.store.remove(sessionId).catch(() => undefined);
|
|
506
|
+
}
|
|
507
|
+
async function mapFailedReopen(deps, sessionId, reopen) {
|
|
508
|
+
await healMissingSessionFile(deps, sessionId, reopen);
|
|
509
|
+
return mapReopenSessionFailure(reopen);
|
|
510
|
+
}
|
|
491
511
|
export async function handleChatCapabilities(_req, res, deps) {
|
|
492
512
|
const toolSchemas = buildModelCallableToolSchemas();
|
|
493
513
|
sendJson(res, 200, {
|
|
@@ -555,9 +575,15 @@ workspaceId) {
|
|
|
555
575
|
});
|
|
556
576
|
return;
|
|
557
577
|
}
|
|
578
|
+
let createdSessionId;
|
|
579
|
+
let persisted = false;
|
|
558
580
|
try {
|
|
559
581
|
const snapshot = snapshotInput === undefined ? undefined : projectCompactSnapshot(snapshotInput);
|
|
560
|
-
//
|
|
582
|
+
// A Console record promises that its Pi JSONL can be reopened after a
|
|
583
|
+
// Console restart. Do not make that promise until Pi has finished
|
|
584
|
+
// materializing the session: the old fast path wrote a record for a
|
|
585
|
+
// future JSONL path, leaving an unrecoverable orphan when init failed or
|
|
586
|
+
// the process restarted in that window.
|
|
561
587
|
const handle = await deps.runtime.createSession({
|
|
562
588
|
...(provider && modelId ? { model: { provider, modelId } } : {}),
|
|
563
589
|
...(snapshot
|
|
@@ -577,56 +603,51 @@ workspaceId) {
|
|
|
577
603
|
});
|
|
578
604
|
return;
|
|
579
605
|
}
|
|
606
|
+
createdSessionId = handle.sessionId;
|
|
607
|
+
await deps.runtime.ensureSessionReady(handle.sessionId);
|
|
608
|
+
const selection = deps.runtime.getRuntimeSelection(handle.sessionId);
|
|
580
609
|
const session = await deps.store.create({
|
|
581
610
|
sessionId: handle.sessionId,
|
|
582
611
|
sessionFile: handle.sessionFile,
|
|
583
|
-
modelProvider: handle.model?.provider,
|
|
584
|
-
modelId: handle.model?.modelId,
|
|
612
|
+
modelProvider: selection.model?.provider ?? handle.model?.provider,
|
|
613
|
+
modelId: selection.model?.modelId ?? handle.model?.modelId,
|
|
585
614
|
// AC-R7-002: explicit parameter from the dispatcher — the runtime
|
|
586
615
|
// store.create wrapper is gone; body workspaceId (if any) only acts as
|
|
587
616
|
// a fallback for direct (non-dispatcher) handler tests.
|
|
588
617
|
workspaceId: workspaceId ?? (typeof body.workspaceId === "string" && body.workspaceId ? body.workspaceId : undefined),
|
|
589
618
|
});
|
|
619
|
+
persisted = true;
|
|
590
620
|
await deps.attachSession?.(handle.sessionId).catch(() => undefined);
|
|
591
621
|
if (snapshot)
|
|
592
622
|
deps.events.append(handle.sessionId, `${handle.sessionId}:snapshot`, {
|
|
593
623
|
kind: "compact",
|
|
594
624
|
data: snapshot,
|
|
595
625
|
});
|
|
596
|
-
// Best-effort: after materialization, patch resolved default model onto the
|
|
597
|
-
// durable record. Failures stay on the init fence (PI_SESSION_INIT_FAILED).
|
|
598
|
-
if (typeof deps.runtime.ensureSessionReady === "function") {
|
|
599
|
-
void deps.runtime
|
|
600
|
-
.ensureSessionReady(handle.sessionId)
|
|
601
|
-
.then(async () => {
|
|
602
|
-
if (typeof deps.runtime.getRuntimeSelection !== "function")
|
|
603
|
-
return;
|
|
604
|
-
const selection = deps.runtime.getRuntimeSelection(handle.sessionId);
|
|
605
|
-
if (!selection?.model)
|
|
606
|
-
return;
|
|
607
|
-
await deps.store.setRuntimeSelection(handle.sessionId, {
|
|
608
|
-
modelProvider: selection.model.provider,
|
|
609
|
-
modelId: selection.model.modelId,
|
|
610
|
-
});
|
|
611
|
-
})
|
|
612
|
-
.catch(() => undefined);
|
|
613
|
-
}
|
|
614
626
|
sendJson(res, 201, {
|
|
615
627
|
ok: true,
|
|
616
628
|
sessionId: handle.sessionId,
|
|
617
629
|
sessionFile: handle.sessionFile,
|
|
618
630
|
model: handle.model,
|
|
619
|
-
session,
|
|
631
|
+
session: { ...session, isBlank: isBlankChatSession(session) },
|
|
620
632
|
activeTools: handle.activeTools,
|
|
621
633
|
hasBash: true,
|
|
622
|
-
...(handle.initializing ? { initializing: true } : {}),
|
|
623
634
|
});
|
|
624
635
|
}
|
|
625
636
|
catch (error) {
|
|
637
|
+
// Before store.create succeeds, this handle has no durable owner. Dispose
|
|
638
|
+
// it so a failed create cannot survive in-memory until the next restart.
|
|
639
|
+
if (createdSessionId && !persisted)
|
|
640
|
+
deps.runtime.dispose(createdSessionId);
|
|
626
641
|
const message = error instanceof Error ? error.message : String(error);
|
|
627
|
-
|
|
642
|
+
const code = error && typeof error === "object" && "code" in error
|
|
643
|
+
? String(error.code ?? "")
|
|
644
|
+
: "";
|
|
645
|
+
sendJson(res, code === "PI_SESSION_INIT_FAILED" ? 503 : 500, {
|
|
628
646
|
ok: false,
|
|
629
|
-
error: {
|
|
647
|
+
error: {
|
|
648
|
+
code: code === "PI_SESSION_INIT_FAILED" ? code : "CHAT_SESSION_CREATE_FAILED",
|
|
649
|
+
message,
|
|
650
|
+
},
|
|
630
651
|
});
|
|
631
652
|
}
|
|
632
653
|
}
|
|
@@ -731,10 +752,7 @@ export async function handleBranchChatSession(req, res, deps, sessionId) {
|
|
|
731
752
|
sessionFile: record.sessionFile,
|
|
732
753
|
});
|
|
733
754
|
if (!reopened.ok) {
|
|
734
|
-
const mapped =
|
|
735
|
-
code: reopened.code,
|
|
736
|
-
message: reopened.message,
|
|
737
|
-
});
|
|
755
|
+
const mapped = await mapFailedReopen(deps, sessionId, reopened);
|
|
738
756
|
sendJson(res, mapped.status, {
|
|
739
757
|
ok: false,
|
|
740
758
|
error: { code: mapped.code, message: mapped.message },
|
|
@@ -809,7 +827,31 @@ export async function handleGetChatSession(_req, res, deps, sessionId) {
|
|
|
809
827
|
});
|
|
810
828
|
return;
|
|
811
829
|
}
|
|
830
|
+
if (record.sessionFile) {
|
|
831
|
+
try {
|
|
832
|
+
await access(record.sessionFile);
|
|
833
|
+
}
|
|
834
|
+
catch (error) {
|
|
835
|
+
const code = error && typeof error === "object" && "code" in error
|
|
836
|
+
? String(error.code ?? "")
|
|
837
|
+
: "";
|
|
838
|
+
if (code === "ENOENT") {
|
|
839
|
+
await healMissingSessionFile(deps, sessionId, {
|
|
840
|
+
code: "NO_SESSION_FILE",
|
|
841
|
+
});
|
|
842
|
+
sendJson(res, 404, {
|
|
843
|
+
ok: false,
|
|
844
|
+
error: {
|
|
845
|
+
code: "PI_SESSION_NOT_FOUND",
|
|
846
|
+
message: `Pi session file missing or not openable: ${record.sessionFile}`,
|
|
847
|
+
},
|
|
848
|
+
});
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
}
|
|
812
853
|
await deps.operationLinker.recoverSession(sessionId);
|
|
854
|
+
const messagePage = await deps.store.listMessages(sessionId, { limit: 50 });
|
|
813
855
|
const composerDraft = await new ComposerDraftStore(deps.appData).get(sessionId);
|
|
814
856
|
const snapshot = deps.events.snapshot(sessionId);
|
|
815
857
|
const telemetry = deriveChatSessionTelemetry(snapshot);
|
|
@@ -820,8 +862,8 @@ export async function handleGetChatSession(_req, res, deps, sessionId) {
|
|
|
820
862
|
ok: true,
|
|
821
863
|
eventEpoch: deps.events.epoch(sessionId),
|
|
822
864
|
session: {
|
|
823
|
-
...record,
|
|
824
|
-
messages: projectStoredMessagesWithEventOrder(
|
|
865
|
+
...projectBrowserChatSession(record),
|
|
866
|
+
messages: projectStoredMessagesWithEventOrder(messagePage.messages, snapshot),
|
|
825
867
|
composerDraft,
|
|
826
868
|
activeTurnId: activeTurn?.turnId,
|
|
827
869
|
turnPhase: activeTurn
|
|
@@ -833,6 +875,10 @@ export async function handleGetChatSession(_req, res, deps, sessionId) {
|
|
|
833
875
|
todos: projectCurrentTodos(snapshot) ?? [],
|
|
834
876
|
goal: deps.goalRegistry?.get(sessionId) ?? null,
|
|
835
877
|
},
|
|
878
|
+
history: {
|
|
879
|
+
hasMore: messagePage.hasMore,
|
|
880
|
+
...(messagePage.nextCursor ? { nextCursor: messagePage.nextCursor } : {}),
|
|
881
|
+
},
|
|
836
882
|
hasBash: true,
|
|
837
883
|
sessionStats: telemetry.stats,
|
|
838
884
|
usage: telemetry.usage,
|
|
@@ -1406,6 +1452,7 @@ export async function listChatSessionSummaries(deps, ownerWorkspaceId) {
|
|
|
1406
1452
|
: "idle",
|
|
1407
1453
|
deferredTurn: projectDeferredTurnSummary(session.deferredTurns?.[0]),
|
|
1408
1454
|
deferredTurns: projectDeferredTurnSummaries(session.deferredTurns ?? []),
|
|
1455
|
+
isBlank: isBlankChatSession(session) && !active,
|
|
1409
1456
|
};
|
|
1410
1457
|
});
|
|
1411
1458
|
}
|
|
@@ -1436,7 +1483,7 @@ export async function handlePatchChatSession(req, res, deps, sessionId) {
|
|
|
1436
1483
|
});
|
|
1437
1484
|
return;
|
|
1438
1485
|
}
|
|
1439
|
-
sendJson(res, 200, { ok: true, session: record });
|
|
1486
|
+
sendJson(res, 200, { ok: true, session: projectBrowserChatSession(record) });
|
|
1440
1487
|
}
|
|
1441
1488
|
catch (error) {
|
|
1442
1489
|
sendJson(res, 404, {
|
|
@@ -1486,10 +1533,7 @@ export async function handleReopenChatSession(req, res, deps, sessionId) {
|
|
|
1486
1533
|
: {}),
|
|
1487
1534
|
});
|
|
1488
1535
|
if (!result.ok) {
|
|
1489
|
-
const mapped =
|
|
1490
|
-
code: result.code,
|
|
1491
|
-
message: result.message,
|
|
1492
|
-
});
|
|
1536
|
+
const mapped = await mapFailedReopen(deps, sessionId, result);
|
|
1493
1537
|
sendJson(res, mapped.status, {
|
|
1494
1538
|
ok: false,
|
|
1495
1539
|
error: { code: mapped.code, message: mapped.message },
|
|
@@ -1575,10 +1619,7 @@ export async function handleChatPrompt(req, res, deps, sessionId) {
|
|
|
1575
1619
|
: {}),
|
|
1576
1620
|
});
|
|
1577
1621
|
if (!reopen.ok) {
|
|
1578
|
-
const mapped =
|
|
1579
|
-
code: reopen.code,
|
|
1580
|
-
message: reopen.message,
|
|
1581
|
-
});
|
|
1622
|
+
const mapped = await mapFailedReopen(deps, sessionId, reopen);
|
|
1582
1623
|
sendJson(res, mapped.status, {
|
|
1583
1624
|
ok: false,
|
|
1584
1625
|
error: { code: mapped.code, message: mapped.message },
|
|
@@ -1847,10 +1888,7 @@ export async function handleCreateChatTurn(req, res, deps, sessionId, trustedPay
|
|
|
1847
1888
|
: {}),
|
|
1848
1889
|
});
|
|
1849
1890
|
if (!reopen.ok) {
|
|
1850
|
-
const mapped =
|
|
1851
|
-
code: reopen.code,
|
|
1852
|
-
message: reopen.message,
|
|
1853
|
-
});
|
|
1891
|
+
const mapped = await mapFailedReopen(deps, sessionId, reopen);
|
|
1854
1892
|
sendJson(res, mapped.status, {
|
|
1855
1893
|
ok: false,
|
|
1856
1894
|
accepted: false,
|
|
@@ -2776,6 +2814,9 @@ export async function handleChatCommands(res, deps, sessionId) {
|
|
|
2776
2814
|
res.end(JSON.stringify({
|
|
2777
2815
|
ok: true,
|
|
2778
2816
|
commands,
|
|
2817
|
+
capabilities: {
|
|
2818
|
+
sddBingo: Boolean(await deps.sddDataAlignment?.isEligible()),
|
|
2819
|
+
},
|
|
2779
2820
|
...(warning ? { warning } : {}),
|
|
2780
2821
|
}));
|
|
2781
2822
|
}
|
|
@@ -2786,10 +2827,46 @@ export async function handleChatCommands(res, deps, sessionId) {
|
|
|
2786
2827
|
res.end(JSON.stringify({
|
|
2787
2828
|
ok: true,
|
|
2788
2829
|
commands: [],
|
|
2830
|
+
capabilities: { sddBingo: false },
|
|
2789
2831
|
warning: error instanceof Error ? error.message : "commands unavailable",
|
|
2790
2832
|
}));
|
|
2791
2833
|
}
|
|
2792
2834
|
}
|
|
2835
|
+
async function handleSddBingo(req, res, deps, sessionId) {
|
|
2836
|
+
const gate = gateMutation(req, deps);
|
|
2837
|
+
if (!gate.ok) {
|
|
2838
|
+
sendJson(res, gate.status, { ok: false, error: { code: gate.code, message: gate.message } });
|
|
2839
|
+
return;
|
|
2840
|
+
}
|
|
2841
|
+
if (!deps.sddDataAlignment) {
|
|
2842
|
+
sendJson(res, 403, {
|
|
2843
|
+
ok: false,
|
|
2844
|
+
error: { code: "SDD_NOT_ELIGIBLE", message: "当前用户不可使用 SDD 数据对齐" },
|
|
2845
|
+
});
|
|
2846
|
+
return;
|
|
2847
|
+
}
|
|
2848
|
+
try {
|
|
2849
|
+
const body = await readJsonBody(req);
|
|
2850
|
+
if (Object.keys(body).some((key) => key !== "skillName")) {
|
|
2851
|
+
throw new SddDataAlignmentError("SDD_INVALID_SKILL_NAME", "请求体只允许 skillName 字段", 400);
|
|
2852
|
+
}
|
|
2853
|
+
await deps.sddDataAlignment.report(sessionId, body.skillName);
|
|
2854
|
+
sendJson(res, 200, { ok: true });
|
|
2855
|
+
}
|
|
2856
|
+
catch (error) {
|
|
2857
|
+
if (error instanceof SddDataAlignmentError) {
|
|
2858
|
+
sendJson(res, error.status, {
|
|
2859
|
+
ok: false,
|
|
2860
|
+
error: { code: error.code, message: error.message },
|
|
2861
|
+
});
|
|
2862
|
+
return;
|
|
2863
|
+
}
|
|
2864
|
+
sendJson(res, 502, {
|
|
2865
|
+
ok: false,
|
|
2866
|
+
error: { code: "SDD_UPSTREAM_FAILED", message: "SDD 数据对齐上报失败" },
|
|
2867
|
+
});
|
|
2868
|
+
}
|
|
2869
|
+
}
|
|
2793
2870
|
export async function handleRuntimeContext(res, deps, sessionId) {
|
|
2794
2871
|
const ensured = await ensureActiveSession(deps, sessionId);
|
|
2795
2872
|
if (!ensured.ok) {
|
|
@@ -2921,7 +2998,11 @@ export async function handleModels(req, res, deps, sessionId) {
|
|
|
2921
2998
|
// folded into a 409 MODEL_SWITCH_FAILED (the frontend refresh path
|
|
2922
2999
|
// re-reads the snapshot to converge on the applied selection).
|
|
2923
3000
|
const snapshot = await deps.runtime.getRuntimeSnapshot(sessionId);
|
|
2924
|
-
sendJson(res, 200, {
|
|
3001
|
+
sendJson(res, 200, {
|
|
3002
|
+
ok: true,
|
|
3003
|
+
session: projectBrowserChatSession(updated),
|
|
3004
|
+
snapshot,
|
|
3005
|
+
});
|
|
2925
3006
|
}
|
|
2926
3007
|
catch (error) {
|
|
2927
3008
|
if (error instanceof RuntimeSnapshotUnavailableError) {
|
|
@@ -2999,7 +3080,7 @@ export async function ensureActiveSession(deps, sessionId) {
|
|
|
2999
3080
|
: {}),
|
|
3000
3081
|
});
|
|
3001
3082
|
if (!reopened.ok) {
|
|
3002
|
-
return { ok: false, ...
|
|
3083
|
+
return { ok: false, ...(await mapFailedReopen(deps, sessionId, reopened)) };
|
|
3003
3084
|
}
|
|
3004
3085
|
return { ok: true };
|
|
3005
3086
|
}
|
|
@@ -3946,10 +4027,7 @@ export async function handleChatCompact(req, res, deps, sessionId) {
|
|
|
3946
4027
|
: {}),
|
|
3947
4028
|
});
|
|
3948
4029
|
if (!reopened.ok) {
|
|
3949
|
-
const mapped =
|
|
3950
|
-
code: reopened.code,
|
|
3951
|
-
message: reopened.message,
|
|
3952
|
-
});
|
|
4030
|
+
const mapped = await mapFailedReopen(deps, sessionId, reopened);
|
|
3953
4031
|
sendJson(res, mapped.status, {
|
|
3954
4032
|
ok: false,
|
|
3955
4033
|
error: { code: mapped.code, message: mapped.message },
|
|
@@ -4188,6 +4266,11 @@ export async function handleChatRequest(req, res, deps, pathname, workspaceScope
|
|
|
4188
4266
|
await handleSkillAutoInvocation(req, res, deps, decodeURIComponent(skillAutoInvocation[1]), decodeURIComponent(skillAutoInvocation[2]));
|
|
4189
4267
|
return true;
|
|
4190
4268
|
}
|
|
4269
|
+
const sddBingo = pathname.match(/^\/api\/operator\/v1\/chat\/sessions\/([^/]+)\/sdd-bingo$/);
|
|
4270
|
+
if (method === "POST" && sddBingo) {
|
|
4271
|
+
await handleSddBingo(req, res, deps, decodeURIComponent(sddBingo[1]));
|
|
4272
|
+
return true;
|
|
4273
|
+
}
|
|
4191
4274
|
const m4Route = pathname.match(/^\/api\/operator\/v1\/chat\/sessions\/([^/]+)\/(draft|files|runtime-context|runtime-snapshot|models|model|reload|commands)$/);
|
|
4192
4275
|
if (m4Route) {
|
|
4193
4276
|
const id = decodeURIComponent(m4Route[1]);
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { readFile, stat } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
export const SDD_DEFAULT_SKILL_NAME = "SDD-implementation-test-review";
|
|
6
|
+
export const SDD_TELEMETRY_URL = "https://wizard-ai.paic.com.cn/code_pilot/api/skill-usage-log";
|
|
7
|
+
export const SDD_TELEMETRY_SIGN = "52aad0c09e6b46818a11ee702d6fe0ba";
|
|
8
|
+
export const WIZARD_LOCAL_DEFAULT_USER_AGENT = "Builder/OpenCode ai-sdk/provider-utils/4.0.21 runtime/bun/1.3.11";
|
|
9
|
+
function sameAuthFileVersion(left, right) {
|
|
10
|
+
if (!left.exists || !right.exists)
|
|
11
|
+
return left.exists === right.exists;
|
|
12
|
+
return left.dev === right.dev && left.ino === right.ino && left.size === right.size &&
|
|
13
|
+
left.mtimeNs === right.mtimeNs && left.ctimeNs === right.ctimeNs;
|
|
14
|
+
}
|
|
15
|
+
/** Reads ~/.aima-go/auth.json with metadata-based parse caching and fail-closed semantics. */
|
|
16
|
+
export class AimaxAuthReader {
|
|
17
|
+
authFile;
|
|
18
|
+
cached;
|
|
19
|
+
constructor(authFile = path.join(os.homedir(), ".aima-go", "auth.json")) {
|
|
20
|
+
this.authFile = authFile;
|
|
21
|
+
}
|
|
22
|
+
async getToken() {
|
|
23
|
+
let version;
|
|
24
|
+
try {
|
|
25
|
+
const info = await stat(this.authFile, { bigint: true });
|
|
26
|
+
version = { exists: true, dev: info.dev, ino: info.ino, size: info.size,
|
|
27
|
+
mtimeNs: info.mtimeNs, ctimeNs: info.ctimeNs };
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
this.cached = { version: { exists: false } };
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
if (this.cached && sameAuthFileVersion(this.cached.version, version))
|
|
34
|
+
return this.cached.token;
|
|
35
|
+
let token;
|
|
36
|
+
try {
|
|
37
|
+
const parsed = JSON.parse(await readFile(this.authFile, "utf8"));
|
|
38
|
+
const candidate = parsed["aimax-auth-token"];
|
|
39
|
+
if (typeof candidate === "string" && candidate.trim())
|
|
40
|
+
token = candidate;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// Malformed or unreadable files are intentionally ineligible.
|
|
44
|
+
}
|
|
45
|
+
this.cached = { version, ...(token ? { token } : {}) };
|
|
46
|
+
return token;
|
|
47
|
+
}
|
|
48
|
+
async isEligible() {
|
|
49
|
+
return Boolean(await this.getToken());
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const OPENCODE_RANDOM_ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
53
|
+
const OPENCODE_TIME_MASK = (1n << 48n) - 1n;
|
|
54
|
+
let lastOpenCodeMillisecond = -1;
|
|
55
|
+
let openCodeMillisecondCounter = 0;
|
|
56
|
+
/** Generate the OpenCode-compatible `ses_` identifier without an OpenCode dependency. */
|
|
57
|
+
export function createOpenCodeSessionId(options = {}) {
|
|
58
|
+
const now = Math.floor((options.now ?? Date.now)());
|
|
59
|
+
if (now === lastOpenCodeMillisecond)
|
|
60
|
+
openCodeMillisecondCounter = (openCodeMillisecondCounter + 1) & 0xfff;
|
|
61
|
+
else {
|
|
62
|
+
lastOpenCodeMillisecond = now;
|
|
63
|
+
openCodeMillisecondCounter = 0;
|
|
64
|
+
}
|
|
65
|
+
const ordered = BigInt(now) * 0x1000n + BigInt(openCodeMillisecondCounter);
|
|
66
|
+
const timePart = ((~ordered) & OPENCODE_TIME_MASK).toString(16).padStart(12, "0");
|
|
67
|
+
const bytes = (options.random ?? randomBytes)(14);
|
|
68
|
+
let randomPart = "";
|
|
69
|
+
for (const byte of bytes)
|
|
70
|
+
randomPart += OPENCODE_RANDOM_ALPHABET[byte % OPENCODE_RANDOM_ALPHABET.length];
|
|
71
|
+
return `ses_${timePart}${randomPart}`;
|
|
72
|
+
}
|
|
73
|
+
export class SddDataAlignmentError extends Error {
|
|
74
|
+
code;
|
|
75
|
+
status;
|
|
76
|
+
constructor(code, message, status) {
|
|
77
|
+
super(message);
|
|
78
|
+
this.code = code;
|
|
79
|
+
this.status = status;
|
|
80
|
+
this.name = "SddDataAlignmentError";
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
export function normalizeSddSkillName(value) {
|
|
84
|
+
if (value !== undefined && typeof value !== "string") {
|
|
85
|
+
throw new SddDataAlignmentError("SDD_INVALID_SKILL_NAME", "skillName 必须是字符串", 400);
|
|
86
|
+
}
|
|
87
|
+
const raw = value ?? "";
|
|
88
|
+
const normalized = raw.trim() || SDD_DEFAULT_SKILL_NAME;
|
|
89
|
+
if ([...normalized].length > 256 || /[\u0000-\u001f\u007f]/u.test(raw)) {
|
|
90
|
+
throw new SddDataAlignmentError("SDD_INVALID_SKILL_NAME", "skillName 不能超过 256 个字符或包含控制字符", 400);
|
|
91
|
+
}
|
|
92
|
+
return normalized;
|
|
93
|
+
}
|
|
94
|
+
export class SddDataAlignmentService {
|
|
95
|
+
store;
|
|
96
|
+
auth;
|
|
97
|
+
fetchImpl;
|
|
98
|
+
constructor(store, auth = new AimaxAuthReader(), fetchImpl = fetch) {
|
|
99
|
+
this.store = store;
|
|
100
|
+
this.auth = auth;
|
|
101
|
+
this.fetchImpl = fetchImpl;
|
|
102
|
+
}
|
|
103
|
+
async isEligible() {
|
|
104
|
+
return this.auth.isEligible();
|
|
105
|
+
}
|
|
106
|
+
async ensureTelemetrySessionId(sessionId) {
|
|
107
|
+
const value = await this.store.ensureTelemetrySessionId(sessionId, createOpenCodeSessionId);
|
|
108
|
+
if (!value)
|
|
109
|
+
throw new SddDataAlignmentError("SDD_SESSION_NOT_FOUND", "当前对话不存在", 404);
|
|
110
|
+
return value;
|
|
111
|
+
}
|
|
112
|
+
async report(sessionId, rawSkillName) {
|
|
113
|
+
const token = await this.auth.getToken();
|
|
114
|
+
if (!token) {
|
|
115
|
+
throw new SddDataAlignmentError("SDD_NOT_ELIGIBLE", "当前用户不可使用 SDD 数据对齐", 403);
|
|
116
|
+
}
|
|
117
|
+
const skillName = normalizeSddSkillName(rawSkillName);
|
|
118
|
+
const telemetrySessionId = await this.ensureTelemetrySessionId(sessionId);
|
|
119
|
+
const controller = new AbortController();
|
|
120
|
+
const timeout = setTimeout(() => controller.abort(), 30_000);
|
|
121
|
+
let response;
|
|
122
|
+
try {
|
|
123
|
+
response = await this.fetchImpl(SDD_TELEMETRY_URL, {
|
|
124
|
+
method: "POST",
|
|
125
|
+
headers: {
|
|
126
|
+
"Content-Type": "application/json",
|
|
127
|
+
"ws-auth": token,
|
|
128
|
+
"ws-sign": SDD_TELEMETRY_SIGN,
|
|
129
|
+
Connection: "keep-alive",
|
|
130
|
+
"User-Agent": "opencode/1.3.6",
|
|
131
|
+
Accept: "*/*",
|
|
132
|
+
Host: "wizard-ai.paic.com.cn",
|
|
133
|
+
"Accept-Encoding": "gzip, deflate, br, zstd",
|
|
134
|
+
},
|
|
135
|
+
body: JSON.stringify({ sessionId: telemetrySessionId, builderId: "opencode", skillName }),
|
|
136
|
+
signal: controller.signal,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
if (controller.signal.aborted) {
|
|
141
|
+
throw new SddDataAlignmentError("SDD_UPSTREAM_TIMEOUT", "SDD 数据对齐上报超时", 504);
|
|
142
|
+
}
|
|
143
|
+
throw new SddDataAlignmentError("SDD_UPSTREAM_FAILED", "SDD 数据对齐上报失败", 502);
|
|
144
|
+
}
|
|
145
|
+
finally {
|
|
146
|
+
clearTimeout(timeout);
|
|
147
|
+
}
|
|
148
|
+
const body = await readBoundedTelemetryJson(response);
|
|
149
|
+
if (!response.ok || body?.ret !== 0 || body.succ !== true) {
|
|
150
|
+
throw new SddDataAlignmentError("SDD_UPSTREAM_FAILED", "SDD 数据对齐上报失败", 502);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
async function readBoundedTelemetryJson(response) {
|
|
155
|
+
const reader = response.body?.getReader();
|
|
156
|
+
if (!reader)
|
|
157
|
+
return undefined;
|
|
158
|
+
const chunks = [];
|
|
159
|
+
let total = 0;
|
|
160
|
+
try {
|
|
161
|
+
for (;;) {
|
|
162
|
+
const item = await reader.read();
|
|
163
|
+
if (item.done)
|
|
164
|
+
break;
|
|
165
|
+
total += item.value.byteLength;
|
|
166
|
+
if (total > 64 * 1024) {
|
|
167
|
+
await reader.cancel();
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
chunks.push(item.value);
|
|
171
|
+
}
|
|
172
|
+
const merged = new Uint8Array(total);
|
|
173
|
+
let offset = 0;
|
|
174
|
+
for (const chunk of chunks) {
|
|
175
|
+
merged.set(chunk, offset);
|
|
176
|
+
offset += chunk.byteLength;
|
|
177
|
+
}
|
|
178
|
+
return JSON.parse(new TextDecoder().decode(merged));
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
finally {
|
|
184
|
+
reader.releaseLock();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
function deleteHeaderCaseInsensitive(headers, name) {
|
|
188
|
+
for (const key of Object.keys(headers))
|
|
189
|
+
if (key.toLowerCase() === name.toLowerCase())
|
|
190
|
+
delete headers[key];
|
|
191
|
+
}
|
|
192
|
+
function configuredHeader(headers, name) {
|
|
193
|
+
const key = Object.keys(headers ?? {}).find((entry) => entry.toLowerCase() === name.toLowerCase());
|
|
194
|
+
return key ? headers?.[key] : undefined;
|
|
195
|
+
}
|
|
196
|
+
function setStaticHeader(headers, configured, name, defaultValue) {
|
|
197
|
+
deleteHeaderCaseInsensitive(headers, name);
|
|
198
|
+
headers[name] = configuredHeader(configured, name) ?? defaultValue;
|
|
199
|
+
}
|
|
200
|
+
export async function injectWizardLocalHeaders(options) {
|
|
201
|
+
const model = options.context.model;
|
|
202
|
+
if (model?.provider !== "wizard-local" || !model.id)
|
|
203
|
+
return;
|
|
204
|
+
if (!(await options.service.isEligible()))
|
|
205
|
+
return;
|
|
206
|
+
const modelRegistry = options.context.modelRegistry;
|
|
207
|
+
if (!modelRegistry?.getApiKeyAndHeaders)
|
|
208
|
+
return;
|
|
209
|
+
// Keep the method call attached to ModelRegistry. Its implementation reads
|
|
210
|
+
// the private runtime through `this`; extracting the function would make the
|
|
211
|
+
// SDK path fail closed and silently drop all custom headers.
|
|
212
|
+
const auth = await modelRegistry.getApiKeyAndHeaders(model);
|
|
213
|
+
if (!auth.ok || typeof auth.apiKey !== "string" || !auth.apiKey)
|
|
214
|
+
return;
|
|
215
|
+
setStaticHeader(options.headers, auth.headers, "client-code", "OpenCode");
|
|
216
|
+
setStaticHeader(options.headers, auth.headers, "User-Agent", WIZARD_LOCAL_DEFAULT_USER_AGENT);
|
|
217
|
+
for (const name of ["x-api-key", "X-Session-Id", "X-Model-Id"])
|
|
218
|
+
deleteHeaderCaseInsensitive(options.headers, name);
|
|
219
|
+
options.headers["x-api-key"] = auth.apiKey;
|
|
220
|
+
options.headers["X-Session-Id"] = await options.service.ensureTelemetrySessionId(options.sessionId);
|
|
221
|
+
options.headers["X-Model-Id"] = model.id;
|
|
222
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isBlankChatSession } from "./session-store.js";
|
|
1
2
|
import { projectDeferredTurnSummaries, projectDeferredTurnSummary, } from "./deferred-turn.js";
|
|
2
3
|
/**
|
|
3
4
|
* Process-wide Session baseline, mirroring DSH's independent session.list.
|
|
@@ -33,6 +34,7 @@ export class ChatSessionCatalog {
|
|
|
33
34
|
: {}),
|
|
34
35
|
deferredTurn: projectDeferredTurnSummary(record.deferredTurns?.[0]),
|
|
35
36
|
deferredTurns: projectDeferredTurnSummaries(record.deferredTurns ?? []),
|
|
37
|
+
isBlank: isBlankChatSession(record),
|
|
36
38
|
...(workspaceId ? { workspaceId } : {}),
|
|
37
39
|
});
|
|
38
40
|
}
|