@tea-agent/loop-agent 0.42.0-next.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 +319 -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 +3976 -76
- package/dist/executors/pi-executor.js +15 -4
- 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/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 +5 -0
- package/dist/worker/observe/static/styles.css +95 -0
- package/dist/worker/observe/static/views/dag-inspector.js +14 -7
- 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 +69 -10
- 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 +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/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 +1028 -562
- package/dist/workflows/dag/interrupt-request.js +7 -0
- package/dist/workflows/dag/node-execution.js +828 -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 +219 -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 +23 -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
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* `createDefaultPiSdkBindings()`.
|
|
27
27
|
*/
|
|
28
28
|
import { createHash, randomBytes } from "node:crypto";
|
|
29
|
-
import { access } from "node:fs/promises";
|
|
29
|
+
import { access, mkdir, rm, stat, writeFile } from "node:fs/promises";
|
|
30
30
|
import path from "node:path";
|
|
31
31
|
import { extractAssistantContentBlocks, extractAssistantText, isAssistantSdkMessage, } from "./assistant-content.js";
|
|
32
32
|
import { parseProviderError, } from "./provider-error.js";
|
|
@@ -43,6 +43,7 @@ import { filterActiveInterviewTools } from "../interview/tools.js";
|
|
|
43
43
|
import { RUNTIME_CONTEXT_TEXT_MAX, redactRuntimeText, } from "./runtime-context.js";
|
|
44
44
|
import { buildTodoContinuationContext, } from "./todos.js";
|
|
45
45
|
import { buildGoalContinuationContext, } from "./goals.js";
|
|
46
|
+
import { injectWizardLocalHeaders, } from "./sdd-data-alignment.js";
|
|
46
47
|
/**
|
|
47
48
|
* Base system prompt fragment every General Operator Chat session receives.
|
|
48
49
|
*
|
|
@@ -298,6 +299,24 @@ export function createGoalCompactionContextExtension(options) {
|
|
|
298
299
|
},
|
|
299
300
|
};
|
|
300
301
|
}
|
|
302
|
+
export function createWizardLocalHeadersExtension(options) {
|
|
303
|
+
return {
|
|
304
|
+
name: "operator-chat-wizard-local-headers",
|
|
305
|
+
hidden: true,
|
|
306
|
+
factory: (api) => {
|
|
307
|
+
api.on("before_provider_headers", async (rawEvent, context) => {
|
|
308
|
+
if (!context || !rawEvent.headers || typeof rawEvent.headers !== "object")
|
|
309
|
+
return;
|
|
310
|
+
await injectWizardLocalHeaders({
|
|
311
|
+
headers: rawEvent.headers,
|
|
312
|
+
context,
|
|
313
|
+
sessionId: options.sessionId,
|
|
314
|
+
service: options.service,
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
},
|
|
318
|
+
};
|
|
319
|
+
}
|
|
301
320
|
/**
|
|
302
321
|
* Build Pi custom ToolDefinition objects for every whitelisted operator action.
|
|
303
322
|
* Each tool's execute() routes into the operator action dispatcher (Gate 3).
|
|
@@ -931,6 +950,44 @@ export class PiSessionInitFailedError extends Error {
|
|
|
931
950
|
this.name = "PiSessionInitFailedError";
|
|
932
951
|
}
|
|
933
952
|
}
|
|
953
|
+
async function readModelConfigFileVersion(filePath) {
|
|
954
|
+
try {
|
|
955
|
+
const value = await stat(filePath, { bigint: true });
|
|
956
|
+
return {
|
|
957
|
+
exists: true,
|
|
958
|
+
dev: value.dev,
|
|
959
|
+
ino: value.ino,
|
|
960
|
+
size: value.size,
|
|
961
|
+
mtimeNs: value.mtimeNs,
|
|
962
|
+
ctimeNs: value.ctimeNs,
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
catch (error) {
|
|
966
|
+
if (error !== null &&
|
|
967
|
+
typeof error === "object" &&
|
|
968
|
+
"code" in error &&
|
|
969
|
+
error.code === "ENOENT") {
|
|
970
|
+
return { exists: false };
|
|
971
|
+
}
|
|
972
|
+
throw new Error("MODEL_CONFIG_CHECK_FAILED");
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
function sameModelConfigFileVersion(left, right) {
|
|
976
|
+
if (!left.exists || !right.exists)
|
|
977
|
+
return left.exists === right.exists;
|
|
978
|
+
return (left.dev === right.dev &&
|
|
979
|
+
left.ino === right.ino &&
|
|
980
|
+
left.size === right.size &&
|
|
981
|
+
left.mtimeNs === right.mtimeNs &&
|
|
982
|
+
left.ctimeNs === right.ctimeNs);
|
|
983
|
+
}
|
|
984
|
+
async function captureModelConfigFile(agentDir) {
|
|
985
|
+
const filePath = path.join(agentDir, "models.json");
|
|
986
|
+
return {
|
|
987
|
+
path: filePath,
|
|
988
|
+
version: await readModelConfigFileVersion(filePath),
|
|
989
|
+
};
|
|
990
|
+
}
|
|
934
991
|
/**
|
|
935
992
|
* Console Pi runtime — holds Chat sessions and drives prompt() with three-gate
|
|
936
993
|
* tool enforcement. Session-per-sessionId; cross-request reuse via open().
|
|
@@ -940,6 +997,8 @@ export class ConsolePiRuntime {
|
|
|
940
997
|
loader;
|
|
941
998
|
sessions = new Map();
|
|
942
999
|
modelRuntimes = new Map();
|
|
1000
|
+
/** Non-sensitive models.json metadata used to avoid reparsing unchanged config. */
|
|
1001
|
+
modelConfigFiles = new Map();
|
|
943
1002
|
sessionManagers = new Map();
|
|
944
1003
|
/** Per-session SDK service scope (resourceLoader / settingsManager) used by the live runtime snapshot. */
|
|
945
1004
|
serviceScopes = new Map();
|
|
@@ -949,6 +1008,8 @@ export class ConsolePiRuntime {
|
|
|
949
1008
|
reloadInFlight = new Set();
|
|
950
1009
|
/** Synchronous prompt ownership per session (admission before first await). */
|
|
951
1010
|
promptOwners = new Set();
|
|
1011
|
+
/** Steering requests accepted by the SDK but not yet consumed into Pi context. */
|
|
1012
|
+
pendingSteering = new Map();
|
|
952
1013
|
/** At most one detached automatic-title task per durable Console session. */
|
|
953
1014
|
titleInFlight = new Set();
|
|
954
1015
|
/** Composer thinking selection ("auto" = no explicit override). */
|
|
@@ -1019,10 +1080,17 @@ export class ConsolePiRuntime {
|
|
|
1019
1080
|
registry: this.options.goalRegistry,
|
|
1020
1081
|
})
|
|
1021
1082
|
: undefined;
|
|
1083
|
+
const wizardLocalHeadersExtension = sessionId && this.options.sddDataAlignment
|
|
1084
|
+
? createWizardLocalHeadersExtension({
|
|
1085
|
+
sessionId,
|
|
1086
|
+
service: this.options.sddDataAlignment,
|
|
1087
|
+
})
|
|
1088
|
+
: undefined;
|
|
1022
1089
|
const extraFactories = [
|
|
1023
1090
|
todoContextExtension,
|
|
1024
1091
|
goalContextExtension,
|
|
1025
1092
|
pressureExtension,
|
|
1093
|
+
wizardLocalHeadersExtension,
|
|
1026
1094
|
].filter((item) => Boolean(item));
|
|
1027
1095
|
return {
|
|
1028
1096
|
...base,
|
|
@@ -1263,6 +1331,15 @@ export class ConsolePiRuntime {
|
|
|
1263
1331
|
if (!sessionFile) {
|
|
1264
1332
|
throw new Error(`chat session shell missing sessionFile for ${sessionId}`);
|
|
1265
1333
|
}
|
|
1334
|
+
// Pi's SessionManager.create deliberately postpones writing a JSONL until
|
|
1335
|
+
// an assistant response. Materialize a valid header now, otherwise Console
|
|
1336
|
+
// could persist a path that disappears on a restart before first reply.
|
|
1337
|
+
await this.bindings.materializeSessionShell({
|
|
1338
|
+
sessionManager,
|
|
1339
|
+
sessionId,
|
|
1340
|
+
sessionFile,
|
|
1341
|
+
cwd: this.options.cwd,
|
|
1342
|
+
});
|
|
1266
1343
|
this.sessionManagers.set(sessionId, sessionManager);
|
|
1267
1344
|
this.thinkingSelections.set(sessionId, "auto");
|
|
1268
1345
|
// Detached single-flight materialization; dependent routes await the same promise.
|
|
@@ -1341,6 +1418,9 @@ export class ConsolePiRuntime {
|
|
|
1341
1418
|
if (this.disposedSessions.has(sessionId))
|
|
1342
1419
|
return;
|
|
1343
1420
|
const agentDir = await safeGetAgentDir(this.bindings);
|
|
1421
|
+
// Capture before services parse models.json. A concurrent edit can therefore
|
|
1422
|
+
// cause at most one harmless extra refresh, never a missed next-turn refresh.
|
|
1423
|
+
const modelConfigFile = await captureModelConfigFile(agentDir);
|
|
1344
1424
|
const appendSystemPrompt = [
|
|
1345
1425
|
OPERATOR_CHAT_SYSTEM_PROMPT_BASE,
|
|
1346
1426
|
...(init.systemPromptSuffix ? [init.systemPromptSuffix] : []),
|
|
@@ -1400,6 +1480,7 @@ export class ConsolePiRuntime {
|
|
|
1400
1480
|
this.sessions.set(sessionId, session);
|
|
1401
1481
|
this.sessionManagers.set(sessionId, init.sessionManager);
|
|
1402
1482
|
this.modelRuntimes.set(sessionId, modelRuntime);
|
|
1483
|
+
this.modelConfigFiles.set(sessionId, modelConfigFile);
|
|
1403
1484
|
this.sessionModels.set(sessionId, model);
|
|
1404
1485
|
this.serviceScopes.set(sessionId, {
|
|
1405
1486
|
resourceLoader: services.resourceLoader,
|
|
@@ -1420,6 +1501,7 @@ export class ConsolePiRuntime {
|
|
|
1420
1501
|
this.sessions.delete(sessionId);
|
|
1421
1502
|
this.sessionManagers.delete(sessionId);
|
|
1422
1503
|
this.modelRuntimes.delete(sessionId);
|
|
1504
|
+
this.modelConfigFiles.delete(sessionId);
|
|
1423
1505
|
this.sessionModels.delete(sessionId);
|
|
1424
1506
|
this.serviceScopes.delete(sessionId);
|
|
1425
1507
|
this.revisions.delete(sessionId);
|
|
@@ -1498,6 +1580,7 @@ export class ConsolePiRuntime {
|
|
|
1498
1580
|
this.sessions.set(sessionId, built.session);
|
|
1499
1581
|
this.sessionManagers.set(sessionId, sessionManager);
|
|
1500
1582
|
this.modelRuntimes.set(sessionId, built.modelRuntime);
|
|
1583
|
+
this.modelConfigFiles.set(sessionId, built.modelConfigFile);
|
|
1501
1584
|
this.sessionModels.set(sessionId, init.model);
|
|
1502
1585
|
this.serviceScopes.set(sessionId, built.services);
|
|
1503
1586
|
this.revisions.set(sessionId, 1);
|
|
@@ -1569,11 +1652,69 @@ export class ConsolePiRuntime {
|
|
|
1569
1652
|
if (!resolved)
|
|
1570
1653
|
throw new Error("MODEL_NOT_AVAILABLE");
|
|
1571
1654
|
await session.setModel(resolved);
|
|
1572
|
-
|
|
1573
|
-
return {
|
|
1655
|
+
const applied = {
|
|
1574
1656
|
provider: session.model?.provider ?? model.provider,
|
|
1575
1657
|
modelId: session.model?.id ?? session.model?.modelId ?? model.modelId,
|
|
1576
1658
|
};
|
|
1659
|
+
this.sessionModels.set(sessionId, applied);
|
|
1660
|
+
this.bumpRevision(sessionId);
|
|
1661
|
+
return applied;
|
|
1662
|
+
}
|
|
1663
|
+
async refreshModelConfiguration(sessionId, mode) {
|
|
1664
|
+
const session = this.sessions.get(sessionId);
|
|
1665
|
+
const modelRuntime = this.modelRuntimes.get(sessionId);
|
|
1666
|
+
const configFile = this.modelConfigFiles.get(sessionId);
|
|
1667
|
+
if (!session || !modelRuntime || !configFile) {
|
|
1668
|
+
throw new Error(`chat session not active: ${sessionId}`);
|
|
1669
|
+
}
|
|
1670
|
+
const nextVersion = await readModelConfigFileVersion(configFile.path);
|
|
1671
|
+
if (mode === "if-changed" &&
|
|
1672
|
+
sameModelConfigFileVersion(configFile.version, nextVersion)) {
|
|
1673
|
+
return false;
|
|
1674
|
+
}
|
|
1675
|
+
if (typeof modelRuntime.refresh !== "function") {
|
|
1676
|
+
throw new Error("MODEL_CONFIG_REFRESH_UNSUPPORTED");
|
|
1677
|
+
}
|
|
1678
|
+
const stored = this.sessionModels.get(sessionId);
|
|
1679
|
+
const provider = session.model?.provider ?? stored?.provider;
|
|
1680
|
+
const modelId = session.model?.id ?? session.model?.modelId ?? stored?.modelId;
|
|
1681
|
+
try {
|
|
1682
|
+
await modelRuntime.refresh({ allowNetwork: false });
|
|
1683
|
+
if (typeof modelRuntime.getError === "function" && modelRuntime.getError()) {
|
|
1684
|
+
throw new Error("MODEL_CONFIG_REFRESH_FAILED");
|
|
1685
|
+
}
|
|
1686
|
+
if (provider && modelId) {
|
|
1687
|
+
if (typeof session.setModel !== "function") {
|
|
1688
|
+
throw new Error("MODEL_CONFIG_REBIND_UNSUPPORTED");
|
|
1689
|
+
}
|
|
1690
|
+
const resolved = this.bindings.resolveModel({
|
|
1691
|
+
modelRuntime,
|
|
1692
|
+
provider,
|
|
1693
|
+
modelId,
|
|
1694
|
+
});
|
|
1695
|
+
if (!resolved) {
|
|
1696
|
+
throw new Error("MODEL_NOT_AVAILABLE_AFTER_CONFIG_REFRESH");
|
|
1697
|
+
}
|
|
1698
|
+
await session.setModel(resolved);
|
|
1699
|
+
this.sessionModels.set(sessionId, { provider, modelId });
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
catch (error) {
|
|
1703
|
+
if (error instanceof Error &&
|
|
1704
|
+
(error.message === "MODEL_CONFIG_REBIND_UNSUPPORTED" ||
|
|
1705
|
+
error.message === "MODEL_NOT_AVAILABLE_AFTER_CONFIG_REFRESH")) {
|
|
1706
|
+
throw error;
|
|
1707
|
+
}
|
|
1708
|
+
// Do not surface SDK/config error text: it may contain credential material.
|
|
1709
|
+
throw new Error("MODEL_CONFIG_REFRESH_FAILED");
|
|
1710
|
+
}
|
|
1711
|
+
if (this.disposedSessions.has(sessionId) ||
|
|
1712
|
+
this.sessions.get(sessionId) !== session) {
|
|
1713
|
+
throw new Error(`chat session not active: ${sessionId}`);
|
|
1714
|
+
}
|
|
1715
|
+
configFile.version = nextVersion;
|
|
1716
|
+
this.bumpRevision(sessionId);
|
|
1717
|
+
return true;
|
|
1577
1718
|
}
|
|
1578
1719
|
/**
|
|
1579
1720
|
* Apply a thinking-level selection. `auto` records the selection WITHOUT
|
|
@@ -1696,6 +1837,8 @@ export class ConsolePiRuntime {
|
|
|
1696
1837
|
};
|
|
1697
1838
|
}
|
|
1698
1839
|
const busyReasons = [];
|
|
1840
|
+
if (this.promptOwners.has(sessionId))
|
|
1841
|
+
busyReasons.push("prompt in flight");
|
|
1699
1842
|
if (session.isStreaming === true || session.isIdle === false)
|
|
1700
1843
|
busyReasons.push("streaming");
|
|
1701
1844
|
if (session.isCompacting === true)
|
|
@@ -1727,6 +1870,7 @@ export class ConsolePiRuntime {
|
|
|
1727
1870
|
const services = this.serviceScopes.get(sessionId);
|
|
1728
1871
|
if (services)
|
|
1729
1872
|
this.applySessionCompactionOverride(sessionId, services, session);
|
|
1873
|
+
await this.refreshModelConfiguration(sessionId, "force");
|
|
1730
1874
|
}
|
|
1731
1875
|
catch (error) {
|
|
1732
1876
|
return {
|
|
@@ -1738,7 +1882,6 @@ export class ConsolePiRuntime {
|
|
|
1738
1882
|
finally {
|
|
1739
1883
|
this.reloadInFlight.delete(sessionId);
|
|
1740
1884
|
}
|
|
1741
|
-
this.bumpRevision(sessionId);
|
|
1742
1885
|
// Reload replaces the effective resource/session context. Persisted Goal
|
|
1743
1886
|
// remains visible, but continuation authorization must be renewed by user.
|
|
1744
1887
|
this.options.goalRegistry?.disarm(sessionId);
|
|
@@ -2204,6 +2347,16 @@ export class ConsolePiRuntime {
|
|
|
2204
2347
|
});
|
|
2205
2348
|
}
|
|
2206
2349
|
catch (error) {
|
|
2350
|
+
const code = error && typeof error === "object" && "code" in error
|
|
2351
|
+
? String(error.code ?? "")
|
|
2352
|
+
: "";
|
|
2353
|
+
if (code === "SESSION_FILE_MISSING" || code === "ENOENT") {
|
|
2354
|
+
return {
|
|
2355
|
+
ok: false,
|
|
2356
|
+
code: "NO_SESSION_FILE",
|
|
2357
|
+
message: `Pi session file missing or not openable: ${init.sessionFile}`,
|
|
2358
|
+
};
|
|
2359
|
+
}
|
|
2207
2360
|
return {
|
|
2208
2361
|
ok: false,
|
|
2209
2362
|
code: "REOPEN_FAILED",
|
|
@@ -2257,6 +2410,7 @@ export class ConsolePiRuntime {
|
|
|
2257
2410
|
this.sessions.set(sessionId, session);
|
|
2258
2411
|
this.sessionManagers.set(sessionId, sessionManager);
|
|
2259
2412
|
this.modelRuntimes.set(sessionId, built.modelRuntime);
|
|
2413
|
+
this.modelConfigFiles.set(sessionId, built.modelConfigFile);
|
|
2260
2414
|
this.sessionModels.set(sessionId, init.model);
|
|
2261
2415
|
this.serviceScopes.set(sessionId, built.services);
|
|
2262
2416
|
this.revisions.set(sessionId, 1);
|
|
@@ -2310,6 +2464,7 @@ export class ConsolePiRuntime {
|
|
|
2310
2464
|
*/
|
|
2311
2465
|
async buildSessionWithServices(init) {
|
|
2312
2466
|
const agentDir = await safeGetAgentDir(this.bindings);
|
|
2467
|
+
const modelConfigFile = await captureModelConfigFile(agentDir);
|
|
2313
2468
|
const appendSystemPrompt = [
|
|
2314
2469
|
OPERATOR_CHAT_SYSTEM_PROMPT_BASE,
|
|
2315
2470
|
...(init.systemPromptSuffix ? [init.systemPromptSuffix] : []),
|
|
@@ -2343,6 +2498,7 @@ export class ConsolePiRuntime {
|
|
|
2343
2498
|
return {
|
|
2344
2499
|
session,
|
|
2345
2500
|
modelRuntime,
|
|
2501
|
+
modelConfigFile,
|
|
2346
2502
|
services: {
|
|
2347
2503
|
resourceLoader: services.resourceLoader,
|
|
2348
2504
|
settingsManager: services.settingsManager,
|
|
@@ -2354,14 +2510,31 @@ export class ConsolePiRuntime {
|
|
|
2354
2510
|
return this.sessions.get(sessionId);
|
|
2355
2511
|
}
|
|
2356
2512
|
/** Inject one queued message into the currently running Pi turn. */
|
|
2357
|
-
async steer(sessionId, text, images) {
|
|
2513
|
+
async steer(sessionId, clientRequestId, text, images) {
|
|
2358
2514
|
const session = this.sessions.get(sessionId);
|
|
2359
2515
|
if (!session || !this.promptOwners.has(sessionId) || session.isStreaming === false) {
|
|
2360
2516
|
throw new Error("CHAT_TURN_NOT_ACTIVE");
|
|
2361
2517
|
}
|
|
2362
2518
|
if (typeof session.steer !== "function")
|
|
2363
2519
|
throw new Error("CHAT_STEER_UNAVAILABLE");
|
|
2364
|
-
|
|
2520
|
+
const pending = this.pendingSteering.get(sessionId) ?? [];
|
|
2521
|
+
const identity = { clientRequestId, text };
|
|
2522
|
+
pending.push(identity);
|
|
2523
|
+
this.pendingSteering.set(sessionId, pending);
|
|
2524
|
+
try {
|
|
2525
|
+
await session.steer(text, images);
|
|
2526
|
+
}
|
|
2527
|
+
catch (error) {
|
|
2528
|
+
const current = this.pendingSteering.get(sessionId);
|
|
2529
|
+
if (current) {
|
|
2530
|
+
const index = current.indexOf(identity);
|
|
2531
|
+
if (index >= 0)
|
|
2532
|
+
current.splice(index, 1);
|
|
2533
|
+
if (current.length === 0)
|
|
2534
|
+
this.pendingSteering.delete(sessionId);
|
|
2535
|
+
}
|
|
2536
|
+
throw error;
|
|
2537
|
+
}
|
|
2365
2538
|
}
|
|
2366
2539
|
hasSession(sessionId) {
|
|
2367
2540
|
return this.sessions.has(sessionId);
|
|
@@ -2397,12 +2570,13 @@ export class ConsolePiRuntime {
|
|
|
2397
2570
|
*/
|
|
2398
2571
|
async prompt(sessionId, text, onEvent, options) {
|
|
2399
2572
|
// Synchronous admission fence — before any await.
|
|
2400
|
-
if (this.promptOwners.has(sessionId)) {
|
|
2573
|
+
if (this.promptOwners.has(sessionId) || this.reloadInFlight.has(sessionId)) {
|
|
2401
2574
|
throw new Error("CHAT_TURN_ACTIVE");
|
|
2402
2575
|
}
|
|
2403
2576
|
this.promptOwners.add(sessionId);
|
|
2404
2577
|
let unsub;
|
|
2405
2578
|
let abortPromise;
|
|
2579
|
+
let initialUserMessageStarted = false;
|
|
2406
2580
|
const onAbort = () => {
|
|
2407
2581
|
// M1 T08 / AC-R3-003: Stop must abort only the current turn, NEVER
|
|
2408
2582
|
// dispose the session. Await session.abort() so ownership settles
|
|
@@ -2459,6 +2633,11 @@ export class ConsolePiRuntime {
|
|
|
2459
2633
|
if (options?.signal?.aborted) {
|
|
2460
2634
|
return;
|
|
2461
2635
|
}
|
|
2636
|
+
await this.refreshModelConfiguration(sessionId, "if-changed");
|
|
2637
|
+
// Stop may arrive while stat/refresh/rebind is in flight.
|
|
2638
|
+
if (options?.signal?.aborted) {
|
|
2639
|
+
return;
|
|
2640
|
+
}
|
|
2462
2641
|
const emitContextUsage = () => {
|
|
2463
2642
|
try {
|
|
2464
2643
|
const contextUsage = session.getSessionStats?.()?.contextUsage;
|
|
@@ -2500,6 +2679,27 @@ export class ConsolePiRuntime {
|
|
|
2500
2679
|
};
|
|
2501
2680
|
unsub = session.subscribe((event) => {
|
|
2502
2681
|
const observedAt = Date.now();
|
|
2682
|
+
if (event.type === "message_start" && isUserSdkMessage(event.message)) {
|
|
2683
|
+
if (!initialUserMessageStarted) {
|
|
2684
|
+
// The first user message_start belongs to session.prompt(text), not
|
|
2685
|
+
// to a steering request that may have raced with prompt startup.
|
|
2686
|
+
initialUserMessageStarted = true;
|
|
2687
|
+
}
|
|
2688
|
+
else {
|
|
2689
|
+
const pending = this.pendingSteering.get(sessionId);
|
|
2690
|
+
const accepted = pending?.shift();
|
|
2691
|
+
if (pending?.length === 0)
|
|
2692
|
+
this.pendingSteering.delete(sessionId);
|
|
2693
|
+
if (accepted) {
|
|
2694
|
+
onEvent({
|
|
2695
|
+
type: "steering_accepted",
|
|
2696
|
+
sessionId,
|
|
2697
|
+
clientRequestId: accepted.clientRequestId,
|
|
2698
|
+
text: accepted.text,
|
|
2699
|
+
});
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
}
|
|
2503
2703
|
if (event.type === "compaction_start") {
|
|
2504
2704
|
activeCompaction = {
|
|
2505
2705
|
compactionId: newCompactionId(sessionId),
|
|
@@ -2638,6 +2838,10 @@ export class ConsolePiRuntime {
|
|
|
2638
2838
|
finally {
|
|
2639
2839
|
if (this.options.questionRegistry)
|
|
2640
2840
|
this.options.questionRegistry.clearTurn(sessionId);
|
|
2841
|
+
// Unconsumed durable DeferredTurns remain dispatching and are recovered by
|
|
2842
|
+
// the route layer after this active Turn closes. Never carry their volatile
|
|
2843
|
+
// identities into a later prompt.
|
|
2844
|
+
this.pendingSteering.delete(sessionId);
|
|
2641
2845
|
this.promptOwners.delete(sessionId);
|
|
2642
2846
|
}
|
|
2643
2847
|
}
|
|
@@ -2759,11 +2963,13 @@ export class ConsolePiRuntime {
|
|
|
2759
2963
|
this.sessionManagers.delete(sessionId);
|
|
2760
2964
|
this.mainlineLeaves.delete(sessionId);
|
|
2761
2965
|
this.modelRuntimes.delete(sessionId);
|
|
2966
|
+
this.modelConfigFiles.delete(sessionId);
|
|
2762
2967
|
this.sessionModels.delete(sessionId);
|
|
2763
2968
|
this.serviceScopes.delete(sessionId);
|
|
2764
2969
|
this.revisions.delete(sessionId);
|
|
2765
2970
|
this.thinkingSelections.delete(sessionId);
|
|
2766
2971
|
this.reloadInFlight.delete(sessionId);
|
|
2972
|
+
this.pendingSteering.delete(sessionId);
|
|
2767
2973
|
}
|
|
2768
2974
|
disposeAll() {
|
|
2769
2975
|
for (const id of [...this.sessions.keys()])
|
|
@@ -2796,6 +3002,11 @@ function mapAssistantMessageEvent(type, sessionId, message) {
|
|
|
2796
3002
|
...(content.length > 0 ? { content } : {}),
|
|
2797
3003
|
};
|
|
2798
3004
|
}
|
|
3005
|
+
function isUserSdkMessage(message) {
|
|
3006
|
+
return Boolean(message &&
|
|
3007
|
+
typeof message === "object" &&
|
|
3008
|
+
message.role === "user");
|
|
3009
|
+
}
|
|
2799
3010
|
function mapSdkEvent(sessionId, event, compactionId) {
|
|
2800
3011
|
switch (event.type) {
|
|
2801
3012
|
case "agent_start":
|
|
@@ -2981,6 +3192,36 @@ export function createDefaultPiSdkBindings() {
|
|
|
2981
3192
|
id: opts.sessionId,
|
|
2982
3193
|
});
|
|
2983
3194
|
},
|
|
3195
|
+
materializeSessionShell: async (opts) => {
|
|
3196
|
+
const sdk = (await loadSdk());
|
|
3197
|
+
const manager = opts.sessionManager;
|
|
3198
|
+
if (typeof manager.setSessionFile !== "function") {
|
|
3199
|
+
throw new Error("Pi SessionManager cannot materialize a session file");
|
|
3200
|
+
}
|
|
3201
|
+
let created = false;
|
|
3202
|
+
try {
|
|
3203
|
+
await mkdir(path.dirname(opts.sessionFile), { recursive: true });
|
|
3204
|
+
// Match Pi's documented session header. Rebinding through the public
|
|
3205
|
+
// SessionManager API marks it flushed, so Pi appends the first user
|
|
3206
|
+
// entry instead of trying to create the already-present file later.
|
|
3207
|
+
await writeFile(opts.sessionFile, `${JSON.stringify({
|
|
3208
|
+
type: "session",
|
|
3209
|
+
version: typeof sdk.CURRENT_SESSION_VERSION === "number"
|
|
3210
|
+
? sdk.CURRENT_SESSION_VERSION
|
|
3211
|
+
: 3,
|
|
3212
|
+
id: opts.sessionId,
|
|
3213
|
+
timestamp: new Date().toISOString(),
|
|
3214
|
+
cwd: opts.cwd,
|
|
3215
|
+
})}\n`, { encoding: "utf8", flag: "wx", mode: 0o600 });
|
|
3216
|
+
created = true;
|
|
3217
|
+
manager.setSessionFile(opts.sessionFile);
|
|
3218
|
+
}
|
|
3219
|
+
catch (error) {
|
|
3220
|
+
if (created)
|
|
3221
|
+
await rm(opts.sessionFile, { force: true }).catch(() => undefined);
|
|
3222
|
+
throw error;
|
|
3223
|
+
}
|
|
3224
|
+
},
|
|
2984
3225
|
createInMemorySessionManager: async (opts) => {
|
|
2985
3226
|
const sdk = (await loadSdk());
|
|
2986
3227
|
return sdk.SessionManager.inMemory(opts.cwd);
|
|
@@ -2997,13 +3238,19 @@ export function createDefaultPiSdkBindings() {
|
|
|
2997
3238
|
// wrong conversation under the durable Console sessionId. Fail closed
|
|
2998
3239
|
// before open when the persisted file is absent.
|
|
2999
3240
|
await access(opts.sessionFile);
|
|
3000
|
-
return sdk.SessionManager.open(opts.sessionFile);
|
|
3001
3241
|
}
|
|
3002
|
-
catch {
|
|
3003
|
-
|
|
3004
|
-
|
|
3242
|
+
catch (error) {
|
|
3243
|
+
const code = error && typeof error === "object" && "code" in error
|
|
3244
|
+
? String(error.code ?? "")
|
|
3245
|
+
: "";
|
|
3246
|
+
if (code === "ENOENT") {
|
|
3247
|
+
throw Object.assign(new Error(`SESSION_FILE_MISSING: ${opts.sessionFile}`), { code: "SESSION_FILE_MISSING", cause: error });
|
|
3248
|
+
}
|
|
3249
|
+
// File exists but is unreadable (permissions / I/O) — caller maps
|
|
3250
|
+
// undefined to 503 rather than silently creating a fresh session.
|
|
3005
3251
|
return undefined;
|
|
3006
3252
|
}
|
|
3253
|
+
return sdk.SessionManager.open(opts.sessionFile);
|
|
3007
3254
|
},
|
|
3008
3255
|
resolveModel: (opts) => {
|
|
3009
3256
|
const rt = opts.modelRuntime;
|
|
@@ -9,6 +9,9 @@ const TREE_LIMIT = 500;
|
|
|
9
9
|
export const TEXT_PREVIEW_LIMIT = 512 * 1024;
|
|
10
10
|
/** Full-file ceiling for the sandboxed HTML preview route and its assets. */
|
|
11
11
|
export const HTML_PREVIEW_ASSET_LIMIT = 20 * 1024 * 1024;
|
|
12
|
+
/** Self-contained HTML exports commonly embed data and can be much larger than
|
|
13
|
+
* an individual image/script asset. Keep a separate, still-bounded ceiling. */
|
|
14
|
+
export const HTML_PREVIEW_DOCUMENT_LIMIT = 64 * 1024 * 1024;
|
|
12
15
|
export const REPO_HTML_PREVIEW_ROUTE_PREFIX = "/api/operator/v1/chat/repo/html/";
|
|
13
16
|
const SKIP_DIRS = new Set([
|
|
14
17
|
".git",
|
|
@@ -190,8 +193,12 @@ export async function readRepoHtmlAsset(repoRoot, rawPath) {
|
|
|
190
193
|
const info = await handle.stat();
|
|
191
194
|
if (!info.isFile())
|
|
192
195
|
throw new RepoBrowserError(400, "NOT_A_FILE", "path is not a regular file");
|
|
193
|
-
|
|
194
|
-
|
|
196
|
+
const contentType = repoHtmlAssetContentType(guard.repoRelative);
|
|
197
|
+
const limit = contentType.startsWith("text/html")
|
|
198
|
+
? HTML_PREVIEW_DOCUMENT_LIMIT
|
|
199
|
+
: HTML_PREVIEW_ASSET_LIMIT;
|
|
200
|
+
if (info.size > limit)
|
|
201
|
+
throw new RepoBrowserError(413, "TOO_LARGE", `file exceeds ${limit} byte HTML preview limit`);
|
|
195
202
|
const rechecked = await guardReadPath(repoRoot, rawPath);
|
|
196
203
|
if (!rechecked.ok)
|
|
197
204
|
throw new RepoBrowserError(403, guardedCode(rechecked.code), "file path changed while it was being opened");
|
|
@@ -202,7 +209,7 @@ export async function readRepoHtmlAsset(repoRoot, rawPath) {
|
|
|
202
209
|
throw new RepoBrowserError(403, "OUT_OF_REPO", "file identity changed while it was being opened");
|
|
203
210
|
return {
|
|
204
211
|
body: await handle.readFile(),
|
|
205
|
-
contentType
|
|
212
|
+
contentType,
|
|
206
213
|
bytes: info.size,
|
|
207
214
|
};
|
|
208
215
|
}
|