@tea-agent/loop-agent 0.42.0-next.1 → 0.42.0-next.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 +1 -1
- package/CHANGELOG.md +321 -1
- 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 +7 -0
- package/dist/cli/program.js +6 -1
- package/dist/commands/client-recovery.js +3 -0
- package/dist/commands/dag-follow-up.js +138 -0
- package/dist/commands/dag-rerun-task.js +2 -0
- package/dist/commands/init.js +27 -1
- package/dist/commands/task-advance.js +19 -0
- package/dist/executors/dag-pi-executor.js +4010 -78
- package/dist/executors/pi-executor.js +15 -4
- package/dist/executors/pi-extension-resolver.js +14 -2
- package/dist/executors/pi-read-budget-policy.js +239 -0
- package/dist/executors/shell-executor.js +978 -188
- package/dist/executors/shell-write-guard.js +7 -0
- package/dist/infrastructure/console/operation-store.js +226 -6
- package/dist/shared/dag-failure-category.js +12 -0
- package/dist/shared/openspec-spec.js +70 -4
- package/dist/shared/operator/capabilities.js +7 -0
- package/dist/task/config-types.js +105 -5
- 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/worker/console/chat/chat-ui-policy.js +4 -2
- package/dist/worker/console/chat/model-resolver.js +7 -4
- package/dist/worker/console/chat/pi-runtime.js +258 -11
- package/dist/worker/console/chat/repo-browser.js +10 -3
- package/dist/worker/console/chat/routes.js +165 -71
- 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 +159 -70
- package/dist/worker/console/chat/shortcuts.js +19 -3
- package/dist/worker/console/chat/turn-process.js +111 -56
- 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/operator-actions.js +169 -7
- package/dist/worker/console/pi-readiness.js +3 -2
- package/dist/worker/console/server.js +23 -1
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-DZZ8m3PO.js → abnfDiagram-N423BO3Z-CcS17TBr.js} +1 -1
- package/dist/worker/console/static/assets/{arc-D6PvaVd-.js → arc-COptKq2S.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-B_OTOiI8.js → architectureDiagram-T3A2C74G-h4LKMHjP.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-Bv6rqHBg.js → blockDiagram-VBNYF7ZC-COA1MOH0.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-B8eHr0oz.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-DYwR0im2.js → chunk-2GRJ4B5K-mtWfKrUX.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-D2WPGqXt.js → chunk-2Q5K7J3B-B8pXsxDQ.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-CQISJ_I7.js → chunk-5RXB4S5H-ipKzByl1.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-C0o2Du1e.js → chunk-5VM5RSS4-DYi3Ald_.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-4f8kr-U7.js → chunk-6Q2QTUOP-DQtGYoty.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-D_OWvXzX.js → chunk-GF5L2VYU-BU0qS2YV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-CasdPz5X.js → chunk-JWPE2WC7-DVH9dIGE.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-Cj8lRrla.js → chunk-KBJHAD2P-Dt60SmgM.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CUvD1FSd.js → chunk-RYQCIY6F-BoaUGuEY.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CqLw_eqB.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-ku-WqJPx.js → cose-bilkent-JH36ORCC-BxkbTIRd.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-Bq_PYDhl.js → cynefin-VYW2F7L2-Bf2UVnoG.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-BiGZV641.js → cynefinDiagram-MW4NZA55-Bo23q1J_.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-C4JRlglF.js → dagre-VZM6K2ZE-Dd_UU49i.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-B0dNeWYG.js → diagram-7IWD3JNH-ebUa1a9y.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-Cj35YvYM.js → diagram-B4RE2ZJO-BucthU8r.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-uzQoJ2-8.js → diagram-LBJQPF4R-BgqNpAkm.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-D1a-Buoz.js → diagram-Q27KOJAE-DC4q5NGa.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-XjRrLRSs.js → diagram-UB23O5K3-CBqCaMeb.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Da_O24cW.js → ebnfDiagram-BXEA7PRR-CGtnbQ3-.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-BLJ8jrYU.js → erDiagram-JOGREHBK-CG3LUao5.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-B9GrLjM0.js → flowDiagram-UKHOOZJN-D2ZVgoFS.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CeJ0TqiK.js → ganttDiagram-PKOTCBZU-DoYFZiKt.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Bm2eNNFX.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-DB26i3d8.js → infoDiagram-6WML65LV-Duofv8p2.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-BOGRyeZT.js → ishikawaDiagram-WSZJBQD7-D2nlCkA1.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-CZRoBO_6.js → journeyDiagram-NVQOT4AX-Dd4IHdus.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-qlWhJyoB.js → kanban-definition-27J2QSJJ-Bi20AOdt.js} +1 -1
- package/dist/worker/console/static/assets/{linear-CfUiDDB3.js → linear-D59YJ9kB.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-BQe6fpqj.js → mermaid.core-Bl13LpNa.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-zFzWHw64.js → mindmap-definition-FAOFIHXS-C6DLP5eY.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DFGUqjZO.js → pegDiagram-VL7TDLO6-BjO4cy64.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-BB5i0l8Z.js → pieDiagram-7S7Q4E2Y-D7i2qZTG.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-obbVZ_X5.js → quadrantDiagram-CIZ2JOQS-Dx8o_h0y.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BwdaNzc1.js → railroadDiagram-AXF67PYL-B55orI_t.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-CkTJaugh.js → requirementDiagram-LRYGKXZP-zWaehp6f.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-BwJ-hIgq.js → sankeyDiagram-W5VNT64P-CcA-pjvD.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BzSgmm7w.js → sequenceDiagram-SI44F4Z6-BOFbzNHI.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-Bn3oj5Q1.js → sizeCapture-X5ZJPWSS-BjAejah1.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-dxhGtL8K.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-BLpEInya.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-ChJGYcXy.js → timeline-definition-Z64GVDOM-cZfH3nmU.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DK-qjRer.js → vennDiagram-T6HMQDX7-BDE7b3E1.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-3qeaWAg-.js → wardleyDiagram-T6FBY63Y-kyyGy9WJ.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CootlyP9.js → xychartDiagram-ELKLHX3M-CJj6VTog.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/useRecoveryConsole.js +5 -5
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +19 -8
- package/dist/worker/console/static-src/operator-chat/input-history.js +8 -6
- package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +15 -7
- 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 +36 -9
- package/dist/worker/console/static-src/shell/workspace-route.js +11 -0
- package/dist/worker/console/workspace-context.js +115 -1
- package/dist/worker/materialize/frontend-split-task-materializer.js +72 -0
- package/dist/worker/observe/routes.js +4 -0
- package/dist/worker/observe/static/constants.js +22 -22
- package/dist/worker/observe/static/dag-context-reason-labels.js +19 -0
- package/dist/worker/observe/static/dag-helpers.js +4 -2
- package/dist/worker/observe/static/dag-history-labels.js +1 -0
- package/dist/worker/observe/static/dag-inspector-humanize.d.ts +16 -0
- package/dist/worker/observe/static/dag-inspector-humanize.js +339 -0
- package/dist/worker/observe/static/dag-node-purpose.js +13 -8
- package/dist/worker/observe/static/index.html +4 -4
- 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/prompt-restart-candidates.js +4 -2
- package/dist/worker/observe/static/relations.js +7 -7
- 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 +5 -0
- package/dist/worker/observe/static/styles.css +231 -0
- package/dist/worker/observe/static/views/dag-graph.js +1 -1
- package/dist/worker/observe/static/views/dag-inspector.js +336 -181
- package/dist/worker/observe/static/views/dag.js +26 -14
- 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 +74 -13
- package/dist/worker/observe/static/views/task.js +7 -2
- 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 +210 -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/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-status.js +256 -0
- package/dist/workflows/dag/init-hybrid.js +1031 -564
- package/dist/workflows/dag/interrupt-request.js +7 -0
- package/dist/workflows/dag/node-execution.js +854 -4
- package/dist/workflows/dag/prompt.js +70 -3
- 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 +17 -6
- package/dist/workflows/dag/rerun-run.js +44 -6
- package/dist/workflows/dag/rerun-task.js +251 -13
- package/dist/workflows/dag/retry-policy.js +230 -104
- package/dist/workflows/dag/runner.js +580 -124
- package/dist/workflows/dag/scheduler.js +133 -20
- package/dist/workflows/dag/types.js +244 -16
- package/dist/workflows/dag/validate.js +28 -12
- 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 +1 -1
- package/docs/templates/backend-test-dag.json +25 -20
- package/docs/templates/frontend-implementation-contract.schema.json +4 -1
- package/docs/templates/frontend-implementation-dag.json +89 -0
- package/docs/templates/spec-registry.schema.json +45 -0
- package/harness.json +1 -1
- package/package.json +4 -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 +1 -0
- package/skills/loop-agent/references/hybrid-dag.md +2 -2
- package/dist/worker/console/static/assets/channel-BU5gOilw.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DIzKJGHr.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DIzKJGHr.js +0 -1
- package/dist/worker/console/static/assets/index-H9rFJiGL.css +0 -1
- package/dist/worker/console/static/assets/index-xwu9GxEc.js +0 -451
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Cld2qK9v.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-TIHpiT7w.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
|
@@ -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";
|
|
@@ -42,6 +43,12 @@ import { buildTodoCompactionInstructions, projectCurrentTodos } from "./todos.js
|
|
|
42
43
|
import { buildGoalCompactionInstructions, buildGoalContinuationContext, } from "./goals.js";
|
|
43
44
|
import { classifyGoalRoundAdmission, } from "./goal-round-driver.js";
|
|
44
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,
|
|
@@ -1065,20 +1111,10 @@ export async function handleSteerDeferredTurn(req, res, deps, sessionId, clientR
|
|
|
1065
1111
|
}
|
|
1066
1112
|
const item = claim.deferredTurn;
|
|
1067
1113
|
try {
|
|
1068
|
-
await deps.runtime.steer(sessionId, item.text, item.images);
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
});
|
|
1073
|
-
deps.events.append(sessionId, active.turnId, {
|
|
1074
|
-
kind: "user_message",
|
|
1075
|
-
data: {
|
|
1076
|
-
clientRequestId: item.clientRequestId,
|
|
1077
|
-
message: { id: userMessage.id, text: userMessage.text, at: userMessage.at },
|
|
1078
|
-
},
|
|
1079
|
-
});
|
|
1080
|
-
await deps.store.clearDeferredTurnForRequest(sessionId, item.clientRequestId);
|
|
1081
|
-
await emitDeferredTurnUpdate(deps, sessionId, active.turnId, undefined, item.revision + 2);
|
|
1114
|
+
await deps.runtime.steer(sessionId, item.clientRequestId, item.text, item.images);
|
|
1115
|
+
// session.steer() only acknowledges queueing. Keep the durable item in
|
|
1116
|
+
// dispatching until Pi emits the corresponding user message_start.
|
|
1117
|
+
await emitDeferredTurnUpdate(deps, sessionId, active.turnId, item, item.revision);
|
|
1082
1118
|
sendJson(res, 200, {
|
|
1083
1119
|
ok: true,
|
|
1084
1120
|
accepted: true,
|
|
@@ -1102,6 +1138,21 @@ export async function handleSteerDeferredTurn(req, res, deps, sessionId, clientR
|
|
|
1102
1138
|
});
|
|
1103
1139
|
}
|
|
1104
1140
|
}
|
|
1141
|
+
async function persistAcceptedSteering(deps, sessionId, turnId, userMessage, clientRequestId) {
|
|
1142
|
+
deps.events.append(sessionId, turnId, {
|
|
1143
|
+
kind: "user_message",
|
|
1144
|
+
data: {
|
|
1145
|
+
clientRequestId,
|
|
1146
|
+
steering: true,
|
|
1147
|
+
message: {
|
|
1148
|
+
id: userMessage.id,
|
|
1149
|
+
text: userMessage.text,
|
|
1150
|
+
at: userMessage.at,
|
|
1151
|
+
},
|
|
1152
|
+
},
|
|
1153
|
+
});
|
|
1154
|
+
await emitDeferredTurnUpdate(deps, sessionId, turnId, undefined, 0);
|
|
1155
|
+
}
|
|
1105
1156
|
/** Lightweight busy-truth endpoint for the client reconcile loop (pi-web
|
|
1106
1157
|
* get_state equivalent): exposes the active turn + the latest turn's durable
|
|
1107
1158
|
* outcome WITHOUT the full message payload, so the UI can poll it on an
|
|
@@ -1401,6 +1452,7 @@ export async function listChatSessionSummaries(deps, ownerWorkspaceId) {
|
|
|
1401
1452
|
: "idle",
|
|
1402
1453
|
deferredTurn: projectDeferredTurnSummary(session.deferredTurns?.[0]),
|
|
1403
1454
|
deferredTurns: projectDeferredTurnSummaries(session.deferredTurns ?? []),
|
|
1455
|
+
isBlank: isBlankChatSession(session) && !active,
|
|
1404
1456
|
};
|
|
1405
1457
|
});
|
|
1406
1458
|
}
|
|
@@ -1431,7 +1483,7 @@ export async function handlePatchChatSession(req, res, deps, sessionId) {
|
|
|
1431
1483
|
});
|
|
1432
1484
|
return;
|
|
1433
1485
|
}
|
|
1434
|
-
sendJson(res, 200, { ok: true, session: record });
|
|
1486
|
+
sendJson(res, 200, { ok: true, session: projectBrowserChatSession(record) });
|
|
1435
1487
|
}
|
|
1436
1488
|
catch (error) {
|
|
1437
1489
|
sendJson(res, 404, {
|
|
@@ -1481,10 +1533,7 @@ export async function handleReopenChatSession(req, res, deps, sessionId) {
|
|
|
1481
1533
|
: {}),
|
|
1482
1534
|
});
|
|
1483
1535
|
if (!result.ok) {
|
|
1484
|
-
const mapped =
|
|
1485
|
-
code: result.code,
|
|
1486
|
-
message: result.message,
|
|
1487
|
-
});
|
|
1536
|
+
const mapped = await mapFailedReopen(deps, sessionId, result);
|
|
1488
1537
|
sendJson(res, mapped.status, {
|
|
1489
1538
|
ok: false,
|
|
1490
1539
|
error: { code: mapped.code, message: mapped.message },
|
|
@@ -1570,10 +1619,7 @@ export async function handleChatPrompt(req, res, deps, sessionId) {
|
|
|
1570
1619
|
: {}),
|
|
1571
1620
|
});
|
|
1572
1621
|
if (!reopen.ok) {
|
|
1573
|
-
const mapped =
|
|
1574
|
-
code: reopen.code,
|
|
1575
|
-
message: reopen.message,
|
|
1576
|
-
});
|
|
1622
|
+
const mapped = await mapFailedReopen(deps, sessionId, reopen);
|
|
1577
1623
|
sendJson(res, mapped.status, {
|
|
1578
1624
|
ok: false,
|
|
1579
1625
|
error: { code: mapped.code, message: mapped.message },
|
|
@@ -1661,6 +1707,9 @@ export async function handleChatPrompt(req, res, deps, sessionId) {
|
|
|
1661
1707
|
if (!res.writableEnded)
|
|
1662
1708
|
writeChatSse(res, persisted);
|
|
1663
1709
|
},
|
|
1710
|
+
onSteeringMessagePersisted: async (_record, userMessage, accepted) => {
|
|
1711
|
+
await persistAcceptedSteering(deps, sessionId, turnId, userMessage, accepted.clientRequestId);
|
|
1712
|
+
},
|
|
1664
1713
|
onOperationAccepted: (accepted) => deps.operationLinker.link({
|
|
1665
1714
|
sessionId,
|
|
1666
1715
|
turnId,
|
|
@@ -1839,10 +1888,7 @@ export async function handleCreateChatTurn(req, res, deps, sessionId, trustedPay
|
|
|
1839
1888
|
: {}),
|
|
1840
1889
|
});
|
|
1841
1890
|
if (!reopen.ok) {
|
|
1842
|
-
const mapped =
|
|
1843
|
-
code: reopen.code,
|
|
1844
|
-
message: reopen.message,
|
|
1845
|
-
});
|
|
1891
|
+
const mapped = await mapFailedReopen(deps, sessionId, reopen);
|
|
1846
1892
|
sendJson(res, mapped.status, {
|
|
1847
1893
|
ok: false,
|
|
1848
1894
|
accepted: false,
|
|
@@ -1971,6 +2017,9 @@ export async function handleCreateChatTurn(req, res, deps, sessionId, trustedPay
|
|
|
1971
2017
|
if (placeholder)
|
|
1972
2018
|
emitSessionTitle(deps, sessionId, turn.turnId, placeholder);
|
|
1973
2019
|
},
|
|
2020
|
+
onSteeringMessagePersisted: async (_record, userMessage, accepted) => {
|
|
2021
|
+
await persistAcceptedSteering(deps, sessionId, turn.turnId, userMessage, accepted.clientRequestId);
|
|
2022
|
+
},
|
|
1974
2023
|
persistUserMessage: trustedPayload?.persistUserMessage,
|
|
1975
2024
|
onOperationAccepted: (accepted) => deps.operationLinker.link({
|
|
1976
2025
|
sessionId,
|
|
@@ -2646,13 +2695,13 @@ async function handleRepoHtmlPreview(res, deps, rawPath) {
|
|
|
2646
2695
|
"Cache-Control": "no-store",
|
|
2647
2696
|
"X-Content-Type-Options": "nosniff",
|
|
2648
2697
|
"Referrer-Policy": "no-referrer",
|
|
2649
|
-
//
|
|
2650
|
-
// sandbox
|
|
2651
|
-
//
|
|
2698
|
+
// Project scripts may drive tabs and other local interactions, but the
|
|
2699
|
+
// opaque sandbox still withholds same-origin access, forms, popups and
|
|
2700
|
+
// top-level navigation. Network requests remain disabled below.
|
|
2652
2701
|
"Content-Security-Policy": [
|
|
2653
|
-
"sandbox",
|
|
2702
|
+
"sandbox allow-scripts",
|
|
2654
2703
|
"default-src 'none'",
|
|
2655
|
-
|
|
2704
|
+
`script-src 'unsafe-inline' ${origin}`,
|
|
2656
2705
|
"object-src 'none'",
|
|
2657
2706
|
"frame-src 'none'",
|
|
2658
2707
|
"form-action 'none'",
|
|
@@ -2765,6 +2814,9 @@ export async function handleChatCommands(res, deps, sessionId) {
|
|
|
2765
2814
|
res.end(JSON.stringify({
|
|
2766
2815
|
ok: true,
|
|
2767
2816
|
commands,
|
|
2817
|
+
capabilities: {
|
|
2818
|
+
sddBingo: Boolean(await deps.sddDataAlignment?.isEligible()),
|
|
2819
|
+
},
|
|
2768
2820
|
...(warning ? { warning } : {}),
|
|
2769
2821
|
}));
|
|
2770
2822
|
}
|
|
@@ -2775,10 +2827,46 @@ export async function handleChatCommands(res, deps, sessionId) {
|
|
|
2775
2827
|
res.end(JSON.stringify({
|
|
2776
2828
|
ok: true,
|
|
2777
2829
|
commands: [],
|
|
2830
|
+
capabilities: { sddBingo: false },
|
|
2778
2831
|
warning: error instanceof Error ? error.message : "commands unavailable",
|
|
2779
2832
|
}));
|
|
2780
2833
|
}
|
|
2781
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
|
+
}
|
|
2782
2870
|
export async function handleRuntimeContext(res, deps, sessionId) {
|
|
2783
2871
|
const ensured = await ensureActiveSession(deps, sessionId);
|
|
2784
2872
|
if (!ensured.ok) {
|
|
@@ -2910,7 +2998,11 @@ export async function handleModels(req, res, deps, sessionId) {
|
|
|
2910
2998
|
// folded into a 409 MODEL_SWITCH_FAILED (the frontend refresh path
|
|
2911
2999
|
// re-reads the snapshot to converge on the applied selection).
|
|
2912
3000
|
const snapshot = await deps.runtime.getRuntimeSnapshot(sessionId);
|
|
2913
|
-
sendJson(res, 200, {
|
|
3001
|
+
sendJson(res, 200, {
|
|
3002
|
+
ok: true,
|
|
3003
|
+
session: projectBrowserChatSession(updated),
|
|
3004
|
+
snapshot,
|
|
3005
|
+
});
|
|
2914
3006
|
}
|
|
2915
3007
|
catch (error) {
|
|
2916
3008
|
if (error instanceof RuntimeSnapshotUnavailableError) {
|
|
@@ -2988,7 +3080,7 @@ export async function ensureActiveSession(deps, sessionId) {
|
|
|
2988
3080
|
: {}),
|
|
2989
3081
|
});
|
|
2990
3082
|
if (!reopened.ok) {
|
|
2991
|
-
return { ok: false, ...
|
|
3083
|
+
return { ok: false, ...(await mapFailedReopen(deps, sessionId, reopened)) };
|
|
2992
3084
|
}
|
|
2993
3085
|
return { ok: true };
|
|
2994
3086
|
}
|
|
@@ -3935,10 +4027,7 @@ export async function handleChatCompact(req, res, deps, sessionId) {
|
|
|
3935
4027
|
: {}),
|
|
3936
4028
|
});
|
|
3937
4029
|
if (!reopened.ok) {
|
|
3938
|
-
const mapped =
|
|
3939
|
-
code: reopened.code,
|
|
3940
|
-
message: reopened.message,
|
|
3941
|
-
});
|
|
4030
|
+
const mapped = await mapFailedReopen(deps, sessionId, reopened);
|
|
3942
4031
|
sendJson(res, mapped.status, {
|
|
3943
4032
|
ok: false,
|
|
3944
4033
|
error: { code: mapped.code, message: mapped.message },
|
|
@@ -4177,6 +4266,11 @@ export async function handleChatRequest(req, res, deps, pathname, workspaceScope
|
|
|
4177
4266
|
await handleSkillAutoInvocation(req, res, deps, decodeURIComponent(skillAutoInvocation[1]), decodeURIComponent(skillAutoInvocation[2]));
|
|
4178
4267
|
return true;
|
|
4179
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
|
+
}
|
|
4180
4274
|
const m4Route = pathname.match(/^\/api\/operator\/v1\/chat\/sessions\/([^/]+)\/(draft|files|runtime-context|runtime-snapshot|models|model|reload|commands)$/);
|
|
4181
4275
|
if (m4Route) {
|
|
4182
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
|
}
|