@tea-agent/loop-agent 0.1.0-fe-test.0
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 +114 -0
- package/CHANGELOG.md +2283 -0
- package/README.md +177 -0
- package/bin/agent-worker.js +22 -0
- package/bin/loop-agent.js +57 -0
- package/dist/adapters/aimax.js +91 -0
- package/dist/adapters/context.js +32 -0
- package/dist/adapters/index.js +29 -0
- package/dist/adapters/loop-agent.js +192 -0
- package/dist/adapters/types.js +1 -0
- package/dist/application/context-usage/skill-resolution-stats.js +263 -0
- package/dist/application/dag/args.js +486 -0
- package/dist/application/dag/generate-task-dag.js +408 -0
- package/dist/application/dag/report-dag.js +14 -0
- package/dist/application/dag/run-dag.js +109 -0
- package/dist/application/dag/validate-dag.js +166 -0
- package/dist/application/evaluation/alias.js +184 -0
- package/dist/application/evaluation/budget.js +192 -0
- package/dist/application/evaluation/campaign-hash.js +47 -0
- package/dist/application/evaluation/campaign-matrix.js +372 -0
- package/dist/application/evaluation/campaign-scorecard.js +135 -0
- package/dist/application/evaluation/campaign.js +370 -0
- package/dist/application/evaluation/candidate-hash.js +75 -0
- package/dist/application/evaluation/candidate.js +69 -0
- package/dist/application/evaluation/corpus-hash.js +38 -0
- package/dist/application/evaluation/corpus.js +56 -0
- package/dist/application/evaluation/experiment.js +294 -0
- package/dist/application/evaluation/ignition.js +198 -0
- package/dist/application/evaluation/integrity-audit.js +162 -0
- package/dist/application/evaluation/outer-loop.js +132 -0
- package/dist/application/evaluation/pi-cell-executor.js +39 -0
- package/dist/application/evaluation/private-verifier.js +46 -0
- package/dist/application/evaluation/promotion-policy.js +151 -0
- package/dist/application/evaluation/proposer.js +98 -0
- package/dist/application/evaluation/replay.js +289 -0
- package/dist/application/evaluation/types.js +652 -0
- package/dist/application/loop/run-action.js +19 -0
- package/dist/application/task-lifecycle/advance.js +1204 -0
- package/dist/application/task-lifecycle/gates.js +130 -0
- package/dist/application/task-lifecycle/index.js +7 -0
- package/dist/application/task-lifecycle/observe.js +424 -0
- package/dist/application/task-lifecycle/plan-transitions.js +251 -0
- package/dist/application/task-lifecycle/recommendations.js +180 -0
- package/dist/application/task-lifecycle/record.js +73 -0
- package/dist/application/task-lifecycle/types.js +1 -0
- package/dist/build-stamp.json +6 -0
- package/dist/cli/catalog.js +24 -0
- package/dist/cli/command-definitions.js +659 -0
- package/dist/cli/help.js +56 -0
- package/dist/cli/index.js +5 -0
- package/dist/cli/program.js +647 -0
- package/dist/cli/router.js +13 -0
- package/dist/cli/update/init-surface-notifier.js +167 -0
- package/dist/cli/update/notifier.js +117 -0
- package/dist/cli/update/npm-client.js +151 -0
- package/dist/cli/update/policy.js +92 -0
- package/dist/cli/update/runtime-activity.js +29 -0
- package/dist/cli/update/state.js +68 -0
- package/dist/cli-governance/active-residue-check.js +38 -0
- package/dist/cli.js +57 -0
- package/dist/commands/client-recovery.js +1373 -0
- package/dist/commands/closeout.js +13 -0
- package/dist/commands/coverage-audit.js +14 -0
- package/dist/commands/coverage-report.js +50 -0
- package/dist/commands/cursor-prompt.js +182 -0
- package/dist/commands/dag-approve.js +142 -0
- package/dist/commands/dag-final-verification.js +76 -0
- package/dist/commands/dag-init-hybrid.js +57 -0
- package/dist/commands/dag-reconcile-run.js +5 -0
- package/dist/commands/dag-reconcile-tasks.js +51 -0
- package/dist/commands/dag-reject.js +91 -0
- package/dist/commands/dag-report.js +76 -0
- package/dist/commands/dag-request-interrupt.js +20 -0
- package/dist/commands/dag-rerun-task.js +19 -0
- package/dist/commands/dag-rerun.js +111 -0
- package/dist/commands/dag-resume.js +35 -0
- package/dist/commands/dag-run-task.js +12 -0
- package/dist/commands/dag-validate.js +20 -0
- package/dist/commands/dag-workflow-compile.js +91 -0
- package/dist/commands/dag-workflow-plan.js +130 -0
- package/dist/commands/dag-workflow-validate.js +66 -0
- package/dist/commands/delegate.js +130 -0
- package/dist/commands/docs-archive.js +5 -0
- package/dist/commands/docs-audit.js +6 -0
- package/dist/commands/doctor.js +283 -0
- package/dist/commands/eval.js +1398 -0
- package/dist/commands/examples.js +90 -0
- package/dist/commands/goal.js +92 -0
- package/dist/commands/handoff-check.js +5 -0
- package/dist/commands/harvest.js +44 -0
- package/dist/commands/import-prd.js +81 -0
- package/dist/commands/init-upgrade.js +3085 -0
- package/dist/commands/init.js +3630 -0
- package/dist/commands/inspect.js +11 -0
- package/dist/commands/instructions.js +223 -0
- package/dist/commands/knowledge.js +162 -0
- package/dist/commands/loop-benchmark.js +72 -0
- package/dist/commands/loop.js +251 -0
- package/dist/commands/new-task.js +5 -0
- package/dist/commands/operator.js +44 -0
- package/dist/commands/pi-prompt.js +174 -0
- package/dist/commands/pi-reuse-benchmark.js +153 -0
- package/dist/commands/plan-list.js +5 -0
- package/dist/commands/plan.js +50 -0
- package/dist/commands/promote-run.js +29 -0
- package/dist/commands/reference-index.js +16 -0
- package/dist/commands/run-dag-progress.js +123 -0
- package/dist/commands/run-dag.js +22 -0
- package/dist/commands/spine.js +38 -0
- package/dist/commands/stats.js +113 -0
- package/dist/commands/status.js +57 -0
- package/dist/commands/study-init.js +192 -0
- package/dist/commands/task-advance.js +366 -0
- package/dist/commands/task-contract.js +269 -0
- package/dist/commands/task-source-prepare.js +479 -0
- package/dist/commands/task-status.js +133 -0
- package/dist/commands/workflow.js +259 -0
- package/dist/commands/worktree-create.js +31 -0
- package/dist/commands/worktree-list.js +5 -0
- package/dist/commands/worktree-remove.js +26 -0
- package/dist/executors/config-core.js +5 -0
- package/dist/executors/config.js +2 -0
- package/dist/executors/dag-pi-executor.js +1277 -0
- package/dist/executors/dag-static-executor.js +42 -0
- package/dist/executors/dag.js +3 -0
- package/dist/executors/index.js +6 -0
- package/dist/executors/model-routing.js +71 -0
- package/dist/executors/pi-defaults.js +9 -0
- package/dist/executors/pi-event-serializer.js +65 -0
- package/dist/executors/pi-executor.js +981 -0
- package/dist/executors/pi-playwright-cli-tool.js +1006 -0
- package/dist/executors/pi-prompt-transport.js +198 -0
- package/dist/executors/pi-reuse-benchmark.js +316 -0
- package/dist/executors/pi-runtime-reuse.js +29 -0
- package/dist/executors/pi-sdk-executor.js +722 -0
- package/dist/executors/pi-sdk.js +1 -0
- package/dist/executors/pi-writer-tool-policy.js +266 -0
- package/dist/executors/pi.js +3 -0
- package/dist/executors/playwright-cli-launcher.js +63 -0
- package/dist/executors/process-tree.js +33 -0
- package/dist/executors/shell-executor.js +3055 -0
- package/dist/executors/shell-presets.js +99 -0
- package/dist/executors/shell-verification.js +262 -0
- package/dist/executors/shell-write-guard.js +543 -0
- package/dist/executors/shell.js +3 -0
- package/dist/executors/static.js +1 -0
- package/dist/governance/checks.js +437 -0
- package/dist/governance/document-index-closure.js +164 -0
- package/dist/governance/exec-plans.js +549 -0
- package/dist/governance/harness.js +9 -0
- package/dist/governance/index.js +3 -0
- package/dist/governance/manifest-types.js +254 -0
- package/dist/governance/manifest.js +2 -0
- package/dist/governance/path-guard.js +69 -0
- package/dist/governance/path-guards.js +2 -0
- package/dist/governance/profiles.js +3 -0
- package/dist/governance/requirement-coverage.js +425 -0
- package/dist/governance/skill-safety.js +135 -0
- package/dist/governance/spine-audit.js +155 -0
- package/dist/infrastructure/evaluation/alias-store.js +199 -0
- package/dist/infrastructure/evaluation/campaign-store.js +154 -0
- package/dist/infrastructure/evaluation/candidate-store.js +439 -0
- package/dist/infrastructure/evaluation/corpus-store.js +181 -0
- package/dist/infrastructure/evaluation/experiment-store.js +124 -0
- package/dist/infrastructure/evaluation/ignition-store.js +82 -0
- package/dist/infrastructure/evaluation/private-verifier-store.js +145 -0
- package/dist/infrastructure/evaluation/proposer-store.js +78 -0
- package/dist/infrastructure/evaluation/store.js +40 -0
- package/dist/infrastructure/harness/active-residue-policy.js +73 -0
- package/dist/infrastructure/harness/artifact-store.js +72 -0
- package/dist/infrastructure/harness/atomic-write.js +50 -0
- package/dist/infrastructure/harness/completed-facts-guard.js +40 -0
- package/dist/infrastructure/harness/loop-action-store.js +20 -0
- package/dist/infrastructure/harness/loop-store.js +41 -0
- package/dist/infrastructure/harness/one-shot-run-store.js +94 -0
- package/dist/infrastructure/harness/task-store.js +108 -0
- package/dist/records/closeout.js +2 -0
- package/dist/records/harvest.js +215 -0
- package/dist/records/index.js +3 -0
- package/dist/records/one-shot-runs.js +386 -0
- package/dist/records/promotion.js +200 -0
- package/dist/shared/artifacts-core.js +107 -0
- package/dist/shared/artifacts.js +2 -0
- package/dist/shared/context-files.js +32 -0
- package/dist/shared/context.js +2 -0
- package/dist/shared/copy-dir.js +17 -0
- package/dist/shared/git-progress.js +172 -0
- package/dist/shared/index.js +5 -0
- package/dist/shared/logger.js +17 -0
- package/dist/shared/one-shot-prompt-args.js +98 -0
- package/dist/shared/openspec-spec.js +125 -0
- package/dist/shared/operator/capabilities.js +2659 -0
- package/dist/shared/operator/command-lifecycle.js +94 -0
- package/dist/shared/operator/envelope.js +59 -0
- package/dist/shared/operator/index.js +5 -0
- package/dist/shared/operator/registry.js +38 -0
- package/dist/shared/operator/types.js +5 -0
- package/dist/shared/output-truncation.js +37 -0
- package/dist/shared/package-metadata.js +530 -0
- package/dist/shared/path-refs.js +31 -0
- package/dist/shared/pi-retry-settings.js +23 -0
- package/dist/shared/playwright-cli-command-policy.js +41 -0
- package/dist/shared/preview.js +39 -0
- package/dist/shared/prompts.js +26 -0
- package/dist/shared/reference-context.js +264 -0
- package/dist/shared/resilient-git.js +133 -0
- package/dist/shared/runtime-activity.js +6 -0
- package/dist/shared/timeout-policy.js +24 -0
- package/dist/shared/timeout.js +1 -0
- package/dist/shared/types.js +5 -0
- package/dist/sidecars/cursor-prompt/executor.js +428 -0
- package/dist/sidecars/cursor-prompt/index.js +3 -0
- package/dist/sidecars/cursor-prompt/stream.js +121 -0
- package/dist/task/config-types.js +235 -0
- package/dist/task/config.js +2 -0
- package/dist/task/contract/adopt.js +166 -0
- package/dist/task/contract/apply.js +326 -0
- package/dist/task/contract/canonicalize.js +60 -0
- package/dist/task/contract/constants.js +30 -0
- package/dist/task/contract/diff.js +177 -0
- package/dist/task/contract/hash.js +42 -0
- package/dist/task/contract/import-revision.js +96 -0
- package/dist/task/contract/index.js +17 -0
- package/dist/task/contract/journal.js +155 -0
- package/dist/task/contract/lock.js +153 -0
- package/dist/task/contract/observe.js +296 -0
- package/dist/task/contract/paths.js +19 -0
- package/dist/task/contract/project.js +185 -0
- package/dist/task/contract/recover.js +312 -0
- package/dist/task/contract/request-ledger.js +37 -0
- package/dist/task/contract/schema.js +153 -0
- package/dist/task/contract/transaction.js +160 -0
- package/dist/task/contract/types.js +1 -0
- package/dist/task/contract/validate-draft.js +106 -0
- package/dist/task/dag-source-paths.js +50 -0
- package/dist/task/delegate.js +208 -0
- package/dist/task/frontend-preflight.js +131 -0
- package/dist/task/frontend-project-capability.js +570 -0
- package/dist/task/goal-audit.js +51 -0
- package/dist/task/goal-policy.js +8 -0
- package/dist/task/goal.js +3 -0
- package/dist/task/ids.js +1 -0
- package/dist/task/index.js +12 -0
- package/dist/task/lifecycle.js +1 -0
- package/dist/task/operator/capabilities.js +6 -0
- package/dist/task/operator/envelope.js +2 -0
- package/dist/task/operator/index.js +5 -0
- package/dist/task/operator/registry.js +2 -0
- package/dist/task/operator/types.js +1 -0
- package/dist/task/paths.js +1 -0
- package/dist/task/read-model.js +140 -0
- package/dist/task/runtime.js +708 -0
- package/dist/task/source-prepare/artifact-meta.js +137 -0
- package/dist/task/source-prepare/build-draft.js +232 -0
- package/dist/task/source-prepare/completeness.js +248 -0
- package/dist/task/source-prepare/index.js +10 -0
- package/dist/task/source-prepare/parse-intent.js +496 -0
- package/dist/task/source-prepare/path-policy.js +197 -0
- package/dist/task/source-prepare/placeholder-paths.js +77 -0
- package/dist/task/source-prepare/prepare.js +920 -0
- package/dist/task/source-prepare/reference-integrity.js +290 -0
- package/dist/task/source-prepare/semantic-intake.js +717 -0
- package/dist/task/source-prepare/types.js +7 -0
- package/dist/task/source-references.js +228 -0
- package/dist/task/source-state.js +1 -0
- package/dist/task/state.js +41 -0
- package/dist/task/subagent-guidance.js +1 -0
- package/dist/task/task-demand-routing.js +443 -0
- package/dist/task/workflow-state-types.js +92 -0
- package/dist/task/worktree-cleanup.js +140 -0
- package/dist/task/worktree.js +388 -0
- package/dist/verification/maven/cache.js +142 -0
- package/dist/verification/maven/index.js +120 -0
- package/dist/verification/maven/plan-commands.js +421 -0
- package/dist/verification/maven/pom-static.js +136 -0
- package/dist/verification/maven/scope-resolve.js +153 -0
- package/dist/verification/maven/stale.js +130 -0
- package/dist/verification/maven/types.js +23 -0
- package/dist/verification/maven/workspace-graph.js +322 -0
- package/dist/worker/cli.js +957 -0
- package/dist/worker/closeout/apply.js +73 -0
- package/dist/worker/closeout/preview.js +30 -0
- package/dist/worker/console/app-data.js +316 -0
- package/dist/worker/console/chat/artifact-card.js +30 -0
- package/dist/worker/console/chat/assistant-content.js +121 -0
- package/dist/worker/console/chat/chat-event-store.js +981 -0
- package/dist/worker/console/chat/chat-ui-policy.js +25 -0
- package/dist/worker/console/chat/compaction-errors.js +64 -0
- package/dist/worker/console/chat/composer-draft-store.js +45 -0
- package/dist/worker/console/chat/context-panel.js +54 -0
- package/dist/worker/console/chat/contract-apply-receipt-store.js +174 -0
- package/dist/worker/console/chat/explore-tools.js +299 -0
- package/dist/worker/console/chat/human-gate-card.js +45 -0
- package/dist/worker/console/chat/interview-adapter.js +149 -0
- package/dist/worker/console/chat/mcp-inventory.js +221 -0
- package/dist/worker/console/chat/model-resolver.js +215 -0
- package/dist/worker/console/chat/operation-card.js +92 -0
- package/dist/worker/console/chat/pi-console-config.js +376 -0
- package/dist/worker/console/chat/pi-runtime.js +2446 -0
- package/dist/worker/console/chat/repo-browser.js +140 -0
- package/dist/worker/console/chat/repo-walk.js +125 -0
- package/dist/worker/console/chat/resource-loader.js +59 -0
- package/dist/worker/console/chat/resource-preferences-store.js +152 -0
- package/dist/worker/console/chat/routes.js +3137 -0
- package/dist/worker/console/chat/runtime-context.js +102 -0
- package/dist/worker/console/chat/runtime-selection.js +96 -0
- package/dist/worker/console/chat/semantic-activity.js +489 -0
- package/dist/worker/console/chat/session-store.js +484 -0
- package/dist/worker/console/chat/shortcuts.js +217 -0
- package/dist/worker/console/chat/tool-adapter.js +131 -0
- package/dist/worker/console/chat/tool-preview.js +247 -0
- package/dist/worker/console/chat/tools.js +212 -0
- package/dist/worker/console/chat/turn-execution-registry.js +82 -0
- package/dist/worker/console/chat/turn-process.js +346 -0
- package/dist/worker/console/chat/usage.js +165 -0
- package/dist/worker/console/chat/workspace-landing.js +103 -0
- package/dist/worker/console/dag-confirmation.js +347 -0
- package/dist/worker/console/dag-execution-receipt.js +393 -0
- package/dist/worker/console/doctor.js +230 -0
- package/dist/worker/console/draft-store.js +158 -0
- package/dist/worker/console/harness-pi-model-matrix.js +137 -0
- package/dist/worker/console/human-gate-token.js +130 -0
- package/dist/worker/console/index.js +17 -0
- package/dist/worker/console/inspect-split.js +82 -0
- package/dist/worker/console/interview/assessment.js +67 -0
- package/dist/worker/console/interview/grill-me.js +277 -0
- package/dist/worker/console/interview/session.js +100 -0
- package/dist/worker/console/interview/tools.js +109 -0
- package/dist/worker/console/loopback.js +16 -0
- package/dist/worker/console/mutation-gate-receipt-store.js +184 -0
- package/dist/worker/console/night-aux-ticker.js +146 -0
- package/dist/worker/console/observe-health-match.js +179 -0
- package/dist/worker/console/observe-link.js +42 -0
- package/dist/worker/console/open-browser.js +134 -0
- package/dist/worker/console/operation-run-facts.js +190 -0
- package/dist/worker/console/operation-runner.js +353 -0
- package/dist/worker/console/operation-sse.js +184 -0
- package/dist/worker/console/operation-store.js +169 -0
- package/dist/worker/console/operation-wait.js +355 -0
- package/dist/worker/console/operator-actions.js +3768 -0
- package/dist/worker/console/operator-selection.js +105 -0
- package/dist/worker/console/operator-surface-health.js +24 -0
- package/dist/worker/console/operator-user-error.js +45 -0
- package/dist/worker/console/pi-readiness.js +288 -0
- package/dist/worker/console/prd-identity.js +102 -0
- package/dist/worker/console/prd-intake-bridge.js +441 -0
- package/dist/worker/console/prd-reference-discovery.js +124 -0
- package/dist/worker/console/recovery-cta.js +320 -0
- package/dist/worker/console/recovery-error-copy.js +198 -0
- package/dist/worker/console/recovery-selection.js +107 -0
- package/dist/worker/console/repo-fingerprint.js +35 -0
- package/dist/worker/console/resolve-dag-run-for-task.js +115 -0
- package/dist/worker/console/resource-loader.js +95 -0
- package/dist/worker/console/routes.js +475 -0
- package/dist/worker/console/security.js +170 -0
- package/dist/worker/console/server.js +357 -0
- package/dist/worker/console/sibling-controller.js +33 -0
- package/dist/worker/console/static/assets/abnfDiagram-N423BO3Z-BmOEx0gA.js +1 -0
- package/dist/worker/console/static/assets/arc-BGiVVe8b.js +1 -0
- package/dist/worker/console/static/assets/architectureDiagram-T3A2C74G-nDdu4rgJ.js +36 -0
- package/dist/worker/console/static/assets/blockDiagram-VBNYF7ZC-Bb5u0BWf.js +132 -0
- package/dist/worker/console/static/assets/c4Diagram-5PPSVZJV-BlhC0j94.js +10 -0
- package/dist/worker/console/static/assets/channel-Tl-FkUqr.js +1 -0
- package/dist/worker/console/static/assets/chunk-2GRJ4B5K-Ai8_qQXI.js +1 -0
- package/dist/worker/console/static/assets/chunk-2Q5K7J3B-B9gyCjqX.js +1 -0
- package/dist/worker/console/static/assets/chunk-5RXB4S5H-Bzbj3zGH.js +231 -0
- package/dist/worker/console/static/assets/chunk-5VM5RSS4-BT415PvT.js +15 -0
- package/dist/worker/console/static/assets/chunk-6Q2QTUOP-D_A5_5QL.js +88 -0
- package/dist/worker/console/static/assets/chunk-GF5L2VYU-srJkaP3f.js +206 -0
- package/dist/worker/console/static/assets/chunk-JWPE2WC7-UYGvEP4l.js +1 -0
- package/dist/worker/console/static/assets/chunk-KBJHAD2P-B8luBrrz.js +1 -0
- package/dist/worker/console/static/assets/chunk-RYQCIY6F-DN7wXZSq.js +1 -0
- package/dist/worker/console/static/assets/chunk-XXDRQBXY-DXKyMuKS.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BaehtRk4.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BaehtRk4.js +1 -0
- package/dist/worker/console/static/assets/cose-bilkent-JH36ORCC-dqvoynXj.js +1 -0
- package/dist/worker/console/static/assets/cynefin-VYW2F7L2--PcyL34A.js +166 -0
- package/dist/worker/console/static/assets/cynefinDiagram-MW4NZA55-DvWs_hTY.js +62 -0
- package/dist/worker/console/static/assets/cytoscape.esm-yzknjiTM.js +321 -0
- package/dist/worker/console/static/assets/dagre-VZM6K2ZE-DxeIxbyO.js +4 -0
- package/dist/worker/console/static/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/dist/worker/console/static/assets/diagram-7IWD3JNH-DPCG61Of.js +30 -0
- package/dist/worker/console/static/assets/diagram-B4RE2ZJO-CmuU50-U.js +3 -0
- package/dist/worker/console/static/assets/diagram-LBJQPF4R-B61XLD_v.js +24 -0
- package/dist/worker/console/static/assets/diagram-Q27KOJAE-BB7HTRRY.js +24 -0
- package/dist/worker/console/static/assets/diagram-UB23O5K3-DvkboAIp.js +41 -0
- package/dist/worker/console/static/assets/ebnfDiagram-BXEA7PRR-BdJ_y1fL.js +1 -0
- package/dist/worker/console/static/assets/erDiagram-JOGREHBK-9bUBZTRi.js +85 -0
- package/dist/worker/console/static/assets/flowDiagram-UKHOOZJN-Dw4L4RBa.js +156 -0
- package/dist/worker/console/static/assets/ganttDiagram-PKOTCBZU-Jr82N_QP.js +292 -0
- package/dist/worker/console/static/assets/gitGraphDiagram-DS77QQ5N-DAz9wFLr.js +106 -0
- package/dist/worker/console/static/assets/graph-DOmOIIwC.js +1 -0
- package/dist/worker/console/static/assets/index-B7Y-0g3J.css +1 -0
- package/dist/worker/console/static/assets/index-DkCrqaYg.js +325 -0
- package/dist/worker/console/static/assets/infoDiagram-6WML65LV-CBB5Np7y.js +2 -0
- package/dist/worker/console/static/assets/init-Gi6I4Gst.js +1 -0
- package/dist/worker/console/static/assets/ishikawaDiagram-WSZJBQD7-D8IOp94y.js +70 -0
- package/dist/worker/console/static/assets/journeyDiagram-NVQOT4AX-Dgd3AHGG.js +139 -0
- package/dist/worker/console/static/assets/kanban-definition-27J2QSJJ-BmYOxLE9.js +89 -0
- package/dist/worker/console/static/assets/layout-D-LzfAck.js +1 -0
- package/dist/worker/console/static/assets/linear-DfYTEF6J.js +1 -0
- package/dist/worker/console/static/assets/map-DxJ2ADlA.js +1 -0
- package/dist/worker/console/static/assets/mermaid.core-BjuFPRqa.js +308 -0
- package/dist/worker/console/static/assets/mindmap-definition-FAOFIHXS-CLyUViGd.js +96 -0
- package/dist/worker/console/static/assets/ordinal-Cboi1Yqb.js +1 -0
- package/dist/worker/console/static/assets/pegDiagram-VL7TDLO6-BvX2Di4-.js +1 -0
- package/dist/worker/console/static/assets/pieDiagram-7S7Q4E2Y-BFj0_OLv.js +39 -0
- package/dist/worker/console/static/assets/quadrantDiagram-CIZ2JOQS-BIUrD_hx.js +7 -0
- package/dist/worker/console/static/assets/railroadDiagram-AXF67PYL-CHBB48fj.js +1 -0
- package/dist/worker/console/static/assets/requirementDiagram-LRYGKXZP-sJrYw2Sf.js +84 -0
- package/dist/worker/console/static/assets/sankeyDiagram-W5VNT64P-BzHH6fPR.js +40 -0
- package/dist/worker/console/static/assets/sequenceDiagram-SI44F4Z6-7QCnelCt.js +162 -0
- package/dist/worker/console/static/assets/sizeCapture-X5ZJPWSS-sDYSxn72.js +1 -0
- package/dist/worker/console/static/assets/stateDiagram-OKZ733FA-XpiUdwzm.js +1 -0
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-eEhq2A8Y.js +1 -0
- package/dist/worker/console/static/assets/swimlanes-SLNWSIFB-DvqgNPzD.js +2 -0
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-la8AC7fo.js +8 -0
- package/dist/worker/console/static/assets/timeline-definition-Z64GVDOM-2TyOYNx0.js +120 -0
- package/dist/worker/console/static/assets/vennDiagram-T6HMQDX7-B-nM6wM2.js +34 -0
- package/dist/worker/console/static/assets/wardleyDiagram-T6FBY63Y-B86wPdbr.js +78 -0
- package/dist/worker/console/static/assets/xychartDiagram-ELKLHX3M-Dr1C4AQz.js +7 -0
- package/dist/worker/console/static/favicon.svg +37 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_AMS-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-Italic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Math-Italic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Script-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size1-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size2-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size3-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size4-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/dist/worker/console/static/index.html +15 -0
- package/dist/worker/console/static-src/app/console-types.js +184 -0
- package/dist/worker/console/static-src/app/useConsoleShell.js +101 -0
- package/dist/worker/console/static-src/app/useOperatorActions.js +324 -0
- package/dist/worker/console/static-src/app/usePrdImport.js +172 -0
- package/dist/worker/console/static-src/app/useRecoveryActions.js +289 -0
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +425 -0
- package/dist/worker/console/static-src/app/useTaskWizard.js +296 -0
- package/dist/worker/console/static-src/chat-markdown-security.js +38 -0
- package/dist/worker/console/static-src/chat-view-types.js +2 -0
- package/dist/worker/console/static-src/night/night-types.js +24 -0
- package/dist/worker/console/static-src/night/useNightBoard.js +94 -0
- package/dist/worker/console/static-src/night/useNightWizard.js +171 -0
- package/dist/worker/console/static-src/night-prepare-result.js +118 -0
- package/dist/worker/console/static-src/operator-chat/activity-journey.js +125 -0
- package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +73 -0
- package/dist/worker/console/static-src/operator-chat/activity-references.js +20 -0
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +420 -0
- package/dist/worker/console/static-src/operator-chat/format.js +71 -0
- package/dist/worker/console/static-src/operator-chat/landing-density.js +23 -0
- package/dist/worker/console/static-src/operator-chat/mutation-gate.js +45 -0
- package/dist/worker/console/static-src/operator-chat/refs.js +86 -0
- package/dist/worker/console/static-src/operator-chat/resource-auto-invocation.js +91 -0
- package/dist/worker/console/static-src/operator-chat/resource-diagnostics.js +204 -0
- package/dist/worker/console/static-src/operator-chat/runtime-snapshot-store.js +257 -0
- package/dist/worker/console/static-src/operator-chat/session-title-watcher.js +128 -0
- package/dist/worker/console/static-src/operator-chat/sidebar-split.js +90 -0
- package/dist/worker/console/static-src/operator-chat/slash-palette-layout.js +24 -0
- package/dist/worker/console/static-src/operator-chat/slash-palette-nav.js +141 -0
- package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +38 -0
- package/dist/worker/console/static-src/operator-chat/tools-catalog.js +77 -0
- package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +690 -0
- package/dist/worker/console/static-src/operator-chat/turn-submission.js +158 -0
- package/dist/worker/console/static-src/operator-chat/types.js +1 -0
- package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +59 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +495 -0
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +780 -0
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +280 -0
- package/dist/worker/console/static-src/operator-chat/useComposer.js +257 -0
- package/dist/worker/console/static-src/operator-chat/useInterview.js +108 -0
- package/dist/worker/console/static-src/operator-chat/useOverlayFocus.js +84 -0
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +148 -0
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +358 -0
- package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +196 -0
- package/dist/worker/console/static-src/operator-chat/useWorkspaceLayout.js +58 -0
- package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +35 -0
- package/dist/worker/console/static-src/prd-file-import.js +218 -0
- package/dist/worker/console/vite.config.js +27 -0
- package/dist/worker/console/workflow-kinds.js +46 -0
- package/dist/worker/delivery/final-verification.js +302 -0
- package/dist/worker/delivery/git-transaction.js +497 -0
- package/dist/worker/delivery/package.js +537 -0
- package/dist/worker/delivery/verification-bundle.js +544 -0
- package/dist/worker/feature/acceptance-policy.js +227 -0
- package/dist/worker/feature/advance.js +301 -0
- package/dist/worker/feature/decision-loader.js +99 -0
- package/dist/worker/feature/discover.js +14 -0
- package/dist/worker/feature/doctor.js +223 -0
- package/dist/worker/feature/fullstack-validate.js +337 -0
- package/dist/worker/feature/next-action.js +140 -0
- package/dist/worker/feature/profile-schema.js +47 -0
- package/dist/worker/feature/ready-plan-projection.js +82 -0
- package/dist/worker/feature/reducer.js +137 -0
- package/dist/worker/feature/review.js +678 -0
- package/dist/worker/feature/run.js +390 -0
- package/dist/worker/feature/scaffold.js +812 -0
- package/dist/worker/feature/types.js +1 -0
- package/dist/worker/follow-up/approve.js +273 -0
- package/dist/worker/follow-up/factory.js +234 -0
- package/dist/worker/follow-up/paths.js +25 -0
- package/dist/worker/follow-up/policy.js +26 -0
- package/dist/worker/follow-up/schema.js +93 -0
- package/dist/worker/follow-up/store.js +96 -0
- package/dist/worker/loop-agent/command-result.js +1 -0
- package/dist/worker/loop-agent/controller-protocol.js +143 -0
- package/dist/worker/loop-agent/loop-agent-client.js +489 -0
- package/dist/worker/loop-agent/parse-json.js +14 -0
- package/dist/worker/materialize/harness-task-lifecycle-probe.js +126 -0
- package/dist/worker/materialize/harness-task-lineage.js +220 -0
- package/dist/worker/materialize/harness-task-materializer.js +648 -0
- package/dist/worker/metrics/projector.js +139 -0
- package/dist/worker/observability/dag-execution-trajectory.js +591 -0
- package/dist/worker/observability/event-history.js +216 -0
- package/dist/worker/observability/event-store.js +83 -0
- package/dist/worker/observability/events.js +79 -0
- package/dist/worker/observability/interrupt-eligibility.js +264 -0
- package/dist/worker/observability/progress-composite.js +34 -0
- package/dist/worker/observability/read-model.js +2499 -0
- package/dist/worker/observability/snapshot-store.js +43 -0
- package/dist/worker/observability/types.js +1 -0
- package/dist/worker/observe/dag-node-execution-output.js +180 -0
- package/dist/worker/observe/dag-run-artifacts.js +90 -0
- package/dist/worker/observe/health.js +58 -0
- package/dist/worker/observe/night-jobs.js +104 -0
- package/dist/worker/observe/node-input.js +441 -0
- package/dist/worker/observe/paths.js +174 -0
- package/dist/worker/observe/routes.js +1077 -0
- package/dist/worker/observe/server.js +117 -0
- package/dist/worker/observe/spec-evidence.js +398 -0
- package/dist/worker/observe/static/api.js +117 -0
- package/dist/worker/observe/static/app.js +169 -0
- package/dist/worker/observe/static/constants.js +182 -0
- package/dist/worker/observe/static/copy.js +67 -0
- package/dist/worker/observe/static/custom-select.js +567 -0
- package/dist/worker/observe/static/dag-edge-routing.js +368 -0
- package/dist/worker/observe/static/dag-helpers.js +161 -0
- package/dist/worker/observe/static/dag-history-labels.js +95 -0
- package/dist/worker/observe/static/dag-layout.d.ts +36 -0
- package/dist/worker/observe/static/dag-layout.js +163 -0
- package/dist/worker/observe/static/dag-model.js +145 -0
- package/dist/worker/observe/static/dom.js +220 -0
- package/dist/worker/observe/static/favicon.svg +37 -0
- package/dist/worker/observe/static/format-pool.d.ts +71 -0
- package/dist/worker/observe/static/format-pool.js +153 -0
- package/dist/worker/observe/static/format.js +347 -0
- package/dist/worker/observe/static/index.html +409 -0
- package/dist/worker/observe/static/kpi.js +78 -0
- package/dist/worker/observe/static/markdown-render.js +124 -0
- package/dist/worker/observe/static/operator-chrome.css +517 -0
- package/dist/worker/observe/static/operator-chrome.d.ts +83 -0
- package/dist/worker/observe/static/operator-chrome.js +623 -0
- package/dist/worker/observe/static/relations.js +133 -0
- package/dist/worker/observe/static/router.js +156 -0
- package/dist/worker/observe/static/run-processing.js +148 -0
- package/dist/worker/observe/static/shell-chrome.js +65 -0
- package/dist/worker/observe/static/state.js +496 -0
- package/dist/worker/observe/static/styles.css +3567 -0
- package/dist/worker/observe/static/views/batch.js +227 -0
- package/dist/worker/observe/static/views/dag-graph.js +508 -0
- package/dist/worker/observe/static/views/dag-inspector.js +1722 -0
- package/dist/worker/observe/static/views/dag-trajectory.js +313 -0
- package/dist/worker/observe/static/views/dag.js +483 -0
- package/dist/worker/observe/static/views/dags.js +877 -0
- package/dist/worker/observe/static/views/dashboard.js +747 -0
- package/dist/worker/observe/static/views/failures.js +143 -0
- package/dist/worker/observe/static/views/feature.js +492 -0
- package/dist/worker/observe/static/views/night.js +201 -0
- package/dist/worker/observe/static/views/pool.js +708 -0
- package/dist/worker/observe/static/views/run.js +453 -0
- package/dist/worker/observe/static/views/session-timeline.js +771 -0
- package/dist/worker/observe/static/views/shell.js +7 -0
- package/dist/worker/observe/static/views/task.js +315 -0
- package/dist/worker/observe/static/views/timeline.js +163 -0
- package/dist/worker/outcomes/adapters.js +153 -0
- package/dist/worker/outcomes/declared-artifacts.js +103 -0
- package/dist/worker/outcomes/evidence-tokens.js +29 -0
- package/dist/worker/outcomes/gate.js +40 -0
- package/dist/worker/outcomes/projector.js +227 -0
- package/dist/worker/outcomes/registry.js +1 -0
- package/dist/worker/outcomes/store.js +131 -0
- package/dist/worker/outcomes/types.js +79 -0
- package/dist/worker/pool/attempt-identity.js +41 -0
- package/dist/worker/pool/attempt-lease.js +184 -0
- package/dist/worker/pool/attempt-transition.js +210 -0
- package/dist/worker/pool/begin-attempt-with-lease.js +26 -0
- package/dist/worker/pool/begin-attempt.js +35 -0
- package/dist/worker/pool/doctor.js +165 -0
- package/dist/worker/pool/failure-routing.js +182 -0
- package/dist/worker/pool/migrate-state.js +303 -0
- package/dist/worker/pool/reconcile.js +285 -0
- package/dist/worker/pool/recovery-decision.js +163 -0
- package/dist/worker/pool/run-owner-store.js +126 -0
- package/dist/worker/pool/run-store.js +360 -0
- package/dist/worker/pool/runtime-reconcile-inventory.js +127 -0
- package/dist/worker/pool/state-projection.js +57 -0
- package/dist/worker/pool/types.js +17 -0
- package/dist/worker/pool/validation.js +144 -0
- package/dist/worker/preflight.js +157 -0
- package/dist/worker/profile-mapping.js +76 -0
- package/dist/worker/progress-reporter.js +63 -0
- package/dist/worker/report/morning-report.js +155 -0
- package/dist/worker/repos/repo-resolver.js +23 -0
- package/dist/worker/run-task/execute-prepared-task.js +178 -0
- package/dist/worker/run-task/run-task.js +912 -0
- package/dist/worker/runner/run-ready.js +561 -0
- package/dist/worker/runner/single-task-attempt.js +176 -0
- package/dist/worker/scheduler/admission.js +547 -0
- package/dist/worker/scheduler/auto-followup.js +99 -0
- package/dist/worker/scheduler/cli.js +644 -0
- package/dist/worker/scheduler/clock-install/darwin-launchd.js +238 -0
- package/dist/worker/scheduler/clock-install/linux-systemd-user.js +224 -0
- package/dist/worker/scheduler/clock-install/types.js +1 -0
- package/dist/worker/scheduler/clock-install/win32-schtasks.js +227 -0
- package/dist/worker/scheduler/clock-install.js +284 -0
- package/dist/worker/scheduler/clock.js +420 -0
- package/dist/worker/scheduler/dispatcher.js +503 -0
- package/dist/worker/scheduler/doctor.js +431 -0
- package/dist/worker/scheduler/evidence.js +170 -0
- package/dist/worker/scheduler/git-base.js +90 -0
- package/dist/worker/scheduler/index.js +25 -0
- package/dist/worker/scheduler/lease.js +114 -0
- package/dist/worker/scheduler/lifecycle.js +348 -0
- package/dist/worker/scheduler/lock.js +80 -0
- package/dist/worker/scheduler/morning-window.js +191 -0
- package/dist/worker/scheduler/night-git-finalizer.js +114 -0
- package/dist/worker/scheduler/night-harvest.js +412 -0
- package/dist/worker/scheduler/paths.js +92 -0
- package/dist/worker/scheduler/prepared-attempt-recovery.js +471 -0
- package/dist/worker/scheduler/recovery.js +277 -0
- package/dist/worker/scheduler/reservation.js +146 -0
- package/dist/worker/scheduler/retry.js +199 -0
- package/dist/worker/scheduler/scheduler-loop.js +272 -0
- package/dist/worker/scheduler/store.js +275 -0
- package/dist/worker/scheduler/traceability.js +54 -0
- package/dist/worker/scheduler/trigger.js +258 -0
- package/dist/worker/scheduler/types.js +369 -0
- package/dist/worker/scheduler/workspace-adapter.js +128 -0
- package/dist/worker/task-graph/acceptance-schema.js +40 -0
- package/dist/worker/task-graph/ready-planner.js +267 -0
- package/dist/worker/task-graph/ready-queue.js +23 -0
- package/dist/worker/task-graph/task-graph-schema.js +59 -0
- package/dist/worker/task-graph/types.js +1 -0
- package/dist/worker/task-graph/validate.js +293 -0
- package/dist/worker/task-spec/complexity-mapping.js +8 -0
- package/dist/worker/task-spec/schema.js +125 -0
- package/dist/worker/task-spec/types.js +1 -0
- package/dist/worker/task-spec/validate.js +391 -0
- package/dist/worker/task-spec/workflow-routing.js +149 -0
- package/dist/workflows/dag/authoring.js +8 -0
- package/dist/workflows/dag/authority-surface.js +138 -0
- package/dist/workflows/dag/backend-test-analysis-contract.js +507 -0
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +1985 -0
- package/dist/workflows/dag/backend-test-case-manifest.js +638 -0
- package/dist/workflows/dag/backend-test-classification-contract.js +38 -0
- package/dist/workflows/dag/backend-test-contract-envelope.js +205 -0
- package/dist/workflows/dag/backend-test-coverage-contract.js +202 -0
- package/dist/workflows/dag/backend-test-execution-contract.js +715 -0
- package/dist/workflows/dag/backend-test-gap-fill.js +205 -0
- package/dist/workflows/dag/backend-test-intake-context.js +178 -0
- package/dist/workflows/dag/backend-test-layout.js +133 -0
- package/dist/workflows/dag/backend-test-markdown-workflow.js +1984 -0
- package/dist/workflows/dag/backend-test-module-stem.js +26 -0
- package/dist/workflows/dag/backend-test-pytest-collection.js +859 -0
- package/dist/workflows/dag/backend-test-result-contract.js +1030 -0
- package/dist/workflows/dag/backend-test-scenario-param.js +1670 -0
- package/dist/workflows/dag/backend-test-scenario-partitions.js +249 -0
- package/dist/workflows/dag/backend-test-semantic-review-contract.js +36 -0
- package/dist/workflows/dag/backend-test-stability-contract.js +57 -0
- package/dist/workflows/dag/backend-test-writer-completeness.js +1058 -0
- package/dist/workflows/dag/budget-enforcement.js +67 -0
- package/dist/workflows/dag/canvas-observer.js +474 -0
- package/dist/workflows/dag/context-policy.js +137 -0
- package/dist/workflows/dag/contract-output-registry.js +14 -0
- package/dist/workflows/dag/contract-validator-registrations.js +10 -0
- package/dist/workflows/dag/controller-identity.js +104 -0
- package/dist/workflows/dag/convergence/controller.js +596 -0
- package/dist/workflows/dag/decision-envelope.js +557 -0
- package/dist/workflows/dag/decision-evidence.js +153 -0
- package/dist/workflows/dag/decision-gates.js +1 -0
- package/dist/workflows/dag/dynamic-runtime/condition.js +48 -0
- package/dist/workflows/dag/dynamic-runtime/loop-until.js +161 -0
- package/dist/workflows/dag/dynamic-runtime/map.js +435 -0
- package/dist/workflows/dag/dynamic-runtime/reduction.js +72 -0
- package/dist/workflows/dag/dynamic-runtime/shared.js +203 -0
- package/dist/workflows/dag/event-observer.js +132 -0
- package/dist/workflows/dag/executor-registry.js +23 -0
- package/dist/workflows/dag/facts.js +4 -0
- package/dist/workflows/dag/failure-category.js +118 -0
- package/dist/workflows/dag/failure-routing.js +105 -0
- package/dist/workflows/dag/final-verification.js +180 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +1911 -0
- package/dist/workflows/dag/frontend-lint-baseline.js +474 -0
- package/dist/workflows/dag/frontend-plan-render.js +90 -0
- package/dist/workflows/dag/frontend-prewrite-gate.js +1011 -0
- package/dist/workflows/dag/frontend-project-capability.js +1 -0
- package/dist/workflows/dag/frontend-recovery-plan.js +73 -0
- package/dist/workflows/dag/frontend-recovery-root-manifest.js +123 -0
- package/dist/workflows/dag/frontend-recovery-run.js +539 -0
- package/dist/workflows/dag/frontend-repair.js +541 -0
- package/dist/workflows/dag/frontend-review-context.js +116 -0
- package/dist/workflows/dag/frontend-risk.js +161 -0
- package/dist/workflows/dag/frontend-test-case-checklist.js +343 -0
- package/dist/workflows/dag/frontend-test-case-manifest.js +104 -0
- package/dist/workflows/dag/frontend-test-case-quality.js +105 -0
- package/dist/workflows/dag/frontend-test-html-report.js +174 -0
- package/dist/workflows/dag/frontend-test-l5-report.js +177 -0
- package/dist/workflows/dag/frontend-test-result-contract.js +487 -0
- package/dist/workflows/dag/frontend-verification-trace.js +275 -0
- package/dist/workflows/dag/frontend-worktree-diff.js +201 -0
- package/dist/workflows/dag/frontend-writer-recovery.js +106 -0
- package/dist/workflows/dag/frontend-writer-rollback.js +821 -0
- package/dist/workflows/dag/governance-constants.js +5 -0
- package/dist/workflows/dag/governance-profile.js +413 -0
- package/dist/workflows/dag/index.js +6 -0
- package/dist/workflows/dag/init-hybrid.js +7345 -0
- package/dist/workflows/dag/interrupt-request.js +559 -0
- package/dist/workflows/dag/knowledge-curator.js +165 -0
- package/dist/workflows/dag/l5-report-metrics.js +33 -0
- package/dist/workflows/dag/lifecycle.js +855 -0
- package/dist/workflows/dag/liveness-policy.js +251 -0
- package/dist/workflows/dag/node-execution.js +1081 -0
- package/dist/workflows/dag/observer-compose.js +52 -0
- package/dist/workflows/dag/output-protocol.js +356 -0
- package/dist/workflows/dag/project-governance-context.js +514 -0
- package/dist/workflows/dag/prompt-source.js +88 -0
- package/dist/workflows/dag/prompt.js +348 -0
- package/dist/workflows/dag/reconcile-run.js +145 -0
- package/dist/workflows/dag/reconcile-tasks.js +404 -0
- package/dist/workflows/dag/recovery-recommendation.js +329 -0
- package/dist/workflows/dag/repair-artifact.js +363 -0
- package/dist/workflows/dag/report.js +1229 -0
- package/dist/workflows/dag/rerun-plan.js +614 -0
- package/dist/workflows/dag/rerun-run.js +556 -0
- package/dist/workflows/dag/rerun-task.js +370 -0
- package/dist/workflows/dag/retry-policy.js +263 -0
- package/dist/workflows/dag/run-store.js +41 -0
- package/dist/workflows/dag/runner.js +1069 -0
- package/dist/workflows/dag/runtime-contract.js +87 -0
- package/dist/workflows/dag/runtime.js +5 -0
- package/dist/workflows/dag/scheduler.js +497 -0
- package/dist/workflows/dag/sdd-embedded.js +128 -0
- package/dist/workflows/dag/skill-instructions.js +478 -0
- package/dist/workflows/dag/skill-snapshot.js +553 -0
- package/dist/workflows/dag/skills.js +41 -0
- package/dist/workflows/dag/spec.js +3 -0
- package/dist/workflows/dag/task-contract-binding.js +138 -0
- package/dist/workflows/dag/task-demand-routing.js +1 -0
- package/dist/workflows/dag/topo.js +30 -0
- package/dist/workflows/dag/types.js +1145 -0
- package/dist/workflows/dag/upstream-artifacts.js +98 -0
- package/dist/workflows/dag/validate.js +998 -0
- package/dist/workflows/dag/workspace-checkpoint.js +186 -0
- package/dist/workflows/dynamic/artifacts.js +65 -0
- package/dist/workflows/dynamic/compile.js +371 -0
- package/dist/workflows/dynamic/compileTypes.js +1 -0
- package/dist/workflows/dynamic/errors.js +5 -0
- package/dist/workflows/dynamic/index.js +7 -0
- package/dist/workflows/dynamic/profiles.js +156 -0
- package/dist/workflows/dynamic/spec.js +115 -0
- package/dist/workflows/dynamic/validate.js +275 -0
- package/dist/workflows/loop/actions/dag-action.js +129 -0
- package/dist/workflows/loop/actions/pi-review.js +267 -0
- package/dist/workflows/loop/actions/shared.js +157 -0
- package/dist/workflows/loop/actions/shell-verify.js +82 -0
- package/dist/workflows/loop/actions/types.js +1 -0
- package/dist/workflows/loop/actions/workflow-action.js +255 -0
- package/dist/workflows/loop/actions.js +160 -0
- package/dist/workflows/loop/benchmark.js +510 -0
- package/dist/workflows/loop/closeout.js +135 -0
- package/dist/workflows/loop/context.js +47 -0
- package/dist/workflows/loop/events.js +26 -0
- package/dist/workflows/loop/hash.js +32 -0
- package/dist/workflows/loop/index.js +8 -0
- package/dist/workflows/loop/paths.js +17 -0
- package/dist/workflows/loop/policy/auto-policy.js +112 -0
- package/dist/workflows/loop/policy/path-patterns.js +13 -0
- package/dist/workflows/loop/rounds.js +81 -0
- package/dist/workflows/loop/signals.js +52 -0
- package/dist/workflows/loop/state.js +116 -0
- package/dist/workflows/loop/templates.js +54 -0
- package/dist/workflows/loop/types.js +28 -0
- package/docs/README.md +82 -0
- package/docs/architecture/README.md +35 -0
- package/docs/architecture/dag-execution.md +165 -0
- package/docs/architecture/evolution.md +108 -0
- package/docs/architecture/facts-and-state.md +74 -0
- package/docs/architecture/runtime-boundaries.md +226 -0
- package/docs/architecture/system-overview.md +100 -0
- package/docs/architecture/worker-and-feature.md +122 -0
- package/docs/governance/README.md +18 -0
- package/docs/governance/harness-methodology-debugging.md +177 -0
- package/docs/governance/harness-methodology-tdd.md +130 -0
- package/docs/governance/harness-methodology-verification.md +27 -0
- package/docs/init-surface.manifest.json +358 -0
- package/docs/operations/README.md +15 -0
- package/docs/operations/local-development-environment.md +77 -0
- package/docs/skills/README.md +7 -0
- package/docs/skills/vetted-skill-registry.md +49 -0
- package/docs/templates/README.md +67 -0
- package/docs/templates/adr.md +60 -0
- package/docs/templates/agent-dag-authority-surface-audit.prompt.md +94 -0
- package/docs/templates/agent-dag-decision-envelope.schema.json +213 -0
- package/docs/templates/agent-dag-decision-gate-dogfood-report.md +117 -0
- package/docs/templates/agent-dag-decision-gate.prompt.md +246 -0
- package/docs/templates/agent-dag-process-supervisor.prompt.md +98 -0
- package/docs/templates/agent-dag-report.schema.json +473 -0
- package/docs/templates/agent-dag-review-verdict.prompt.md +68 -0
- package/docs/templates/agent-dag.base.json +190 -0
- package/docs/templates/agent-dag.final-verification.json +185 -0
- package/docs/templates/agent-dag.schema.json +590 -0
- package/docs/templates/agent-dag.supervised-implementation.json +640 -0
- package/docs/templates/agent-worker-production-readiness-checklist.md +47 -0
- package/docs/templates/backend-test-analysis.schema.json +37 -0
- package/docs/templates/backend-test-case-manifest.schema.json +223 -0
- package/docs/templates/backend-test-dag.classify.prompt.md +75 -0
- package/docs/templates/backend-test-dag.generate-pytest.prompt.md +41 -0
- package/docs/templates/backend-test-dag.json +671 -0
- package/docs/templates/backend-test-dag.retrospect.prompt.md +165 -0
- package/docs/templates/backend-test-dag.review-cases.prompt.md +36 -0
- package/docs/templates/backend-test-execution.schema.json +138 -0
- package/docs/templates/backend-test-result.schema.json +99 -0
- package/docs/templates/branch-merge-report.md +115 -0
- package/docs/templates/evaluation/agents-map-slim-v1.candidate.json +9 -0
- package/docs/templates/evaluation/agents-map-slim-v1.md +87 -0
- package/docs/templates/evaluation/agents-map-verbose-v0.candidate.json +9 -0
- package/docs/templates/evaluation/agents-map-verbose-v0.md +153 -0
- package/docs/templates/evaluation/campaign-budget-v1.json +12 -0
- package/docs/templates/evaluation/campaign-dogfood-v0.json +24 -0
- package/docs/templates/evaluation/campaign-evidence-v1.json +44 -0
- package/docs/templates/evaluation/context-policy-baseline-v1.json +17 -0
- package/docs/templates/evaluation/context-policy-role-specialized-v1.json +28 -0
- package/docs/templates/evaluation/corpus-dogfood-v0.manifest.json +118 -0
- package/docs/templates/evaluation/matrix-dag-dry-run-v1.json +21 -0
- package/docs/templates/evaluation/matrix-fixture-v1.json +10 -0
- package/docs/templates/evaluation/private-verifier-dogfood-v0.json +16 -0
- package/docs/templates/exec-plan.md +66 -0
- package/docs/templates/feature-spec.md +53 -0
- package/docs/templates/frontend-design-contract.md +68 -0
- package/docs/templates/frontend-eval/fixtures/failures/01-type-build-error.md +17 -0
- package/docs/templates/frontend-eval/fixtures/failures/02-unit-component-test-fail.md +16 -0
- package/docs/templates/frontend-eval/fixtures/failures/03-fixture-schema-drift.md +16 -0
- package/docs/templates/frontend-eval/fixtures/failures/04-missing-loading-empty-error-state.md +16 -0
- package/docs/templates/frontend-eval/fixtures/failures/05-forbidden-write-writeset-expansion.md +16 -0
- package/docs/templates/frontend-eval/fixtures/failures/06-unapproved-dependency-add.md +16 -0
- package/docs/templates/frontend-eval/fixtures/failures/07-mock-production-on.md +21 -0
- package/docs/templates/frontend-eval/fixtures/functional/01-simple-component-style.md +29 -0
- package/docs/templates/frontend-eval/fixtures/functional/02-form-validation.md +28 -0
- package/docs/templates/frontend-eval/fixtures/functional/03-list-detail-page.md +28 -0
- package/docs/templates/frontend-eval/fixtures/functional/04-api-mock.md +29 -0
- package/docs/templates/frontend-eval/fixtures/functional/05-permission-auth-gated-ui.md +27 -0
- package/docs/templates/frontend-eval/fixtures/functional/06-ssr-server-client-boundary.md +28 -0
- package/docs/templates/frontend-eval/fixtures/functional/07-shared-public-component-api.md +28 -0
- package/docs/templates/frontend-eval/fixtures/functional/08-pure-local-no-remote.md +27 -0
- package/docs/templates/frontend-eval/metrics.md +138 -0
- package/docs/templates/frontend-eval/smoke-targets.md +53 -0
- package/docs/templates/frontend-implementation-contract.schema.json +35 -0
- package/docs/templates/frontend-task-constraints.md +62 -0
- package/docs/templates/frontend-task-requirement.md +104 -0
- package/docs/templates/frontend-test-case-checklist.md +44 -0
- package/docs/templates/frontend-test-case.example.md +143 -0
- package/docs/templates/frontend-test-dag.generate-cases.prompt.md +63 -0
- package/docs/templates/frontend-test-dag.json +409 -0
- package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +21 -0
- package/docs/templates/frontend-test-dag.retrospect.prompt.md +5 -0
- package/docs/templates/frontend-test-dag.review-cases.prompt.md +5 -0
- package/docs/templates/frontend-test-dag.review-execution.prompt.md +3 -0
- package/docs/templates/frontend-test-standard-scenarios.v1.json +159 -0
- package/docs/templates/harness.schema.json +351 -0
- package/docs/templates/hybrid-dag.json +188 -0
- package/docs/templates/init-evolution-review.md +35 -0
- package/docs/templates/init-managed-agents.md +157 -0
- package/docs/templates/interactive-ui-round2-experiment.md +66 -0
- package/docs/templates/knowledge-graph-bootstrap-dag.json +118 -0
- package/docs/templates/knowledge-sync-dag.json +178 -0
- package/docs/templates/knowledge-sync-draft.schema.json +71 -0
- package/docs/templates/product-line/AGENTS.md +9 -0
- package/docs/templates/product-line/README.md +42 -0
- package/docs/templates/product-line/acceptance.yaml +23 -0
- package/docs/templates/product-line/closeout.yaml +9 -0
- package/docs/templates/product-line/design.md +13 -0
- package/docs/templates/product-line/feature-scaffold-batch.example.yaml +31 -0
- package/docs/templates/product-line/feature.yaml +11 -0
- package/docs/templates/product-line/links.md +10 -0
- package/docs/templates/product-line/requirement.md +17 -0
- package/docs/templates/product-line/scaffold-samples/README.md +36 -0
- package/docs/templates/product-line/scaffold-samples/backend-only/acceptance.yaml +16 -0
- package/docs/templates/product-line/scaffold-samples/backend-only/design.md +14 -0
- package/docs/templates/product-line/scaffold-samples/backend-only/feature.yaml +14 -0
- package/docs/templates/product-line/scaffold-samples/backend-only/requirement.md +6 -0
- package/docs/templates/product-line/scaffold-samples/backend-only/tasks/BE-IMPL-001.yaml +83 -0
- package/docs/templates/product-line/scaffold-samples/backend-only/tasks/task-graph.yaml +14 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/acceptance.yaml +18 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/design.md +18 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/feature.yaml +14 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/requirement.md +6 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/BE-IMPL-001.yaml +84 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/CONTRACT-001.yaml +84 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/FE-IMPL-001.yaml +86 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/task-graph.yaml +40 -0
- package/docs/templates/product-line/scaffold-samples/frontend-only/acceptance.yaml +16 -0
- package/docs/templates/product-line/scaffold-samples/frontend-only/design.md +14 -0
- package/docs/templates/product-line/scaffold-samples/frontend-only/feature.yaml +14 -0
- package/docs/templates/product-line/scaffold-samples/frontend-only/requirement.md +6 -0
- package/docs/templates/product-line/scaffold-samples/frontend-only/tasks/FE-IMPL-001.yaml +85 -0
- package/docs/templates/product-line/scaffold-samples/frontend-only/tasks/task-graph.yaml +14 -0
- package/docs/templates/product-line/task-graph.yaml +23 -0
- package/docs/templates/product-line/task.yaml +68 -0
- package/docs/templates/product-line/test-plan.md +7 -0
- package/docs/templates/production-readiness-checklist.md +57 -0
- package/docs/templates/progress-log.md +27 -0
- package/docs/templates/project-start-checklist.md +9 -0
- package/docs/templates/qa-report.md +48 -0
- package/docs/templates/sprint-contract.md +29 -0
- package/docs/templates/worker-dogfood-evidence.md +80 -0
- package/docs/templates/worker-dogfood-setup.md +68 -0
- package/examples/decision-gate-agent-dag.json +177 -0
- package/examples/example-dag.json +46 -0
- package/examples/hybrid-loop-agent-dag.json +189 -0
- package/examples/l5-report-coms-process-definition.html +322 -0
- package/examples/l5-report-demo.html +79 -0
- package/harness.json +90 -0
- package/package.json +98 -0
- package/scripts/kb-bootstrap-init-skeleton.sh +241 -0
- package/scripts/kb-graph-incremental-prepare.mjs +386 -0
- package/scripts/kb-graph-materialize.mjs +105 -0
- package/scripts/kb-graph-promote.mjs +164 -0
- package/scripts/kb-query.mjs +554 -0
- package/skills/agent-worker/SKILL.md +48 -0
- package/skills/agent-worker/references/agent-worker-operator.md +159 -0
- package/skills/ai-engineering-context/SKILL.md +48 -0
- package/skills/analyze-product-dependencies/SKILL.md +108 -0
- package/skills/analyze-product-dependencies/references/api-documentation-schema.md +35 -0
- package/skills/analyze-product-dependencies/references/dependency-analysis-schema.md +38 -0
- package/skills/analyze-product-dependencies/references/example.md +76 -0
- package/skills/analyze-product-dependencies/references/forward-test-cases.md +110 -0
- package/skills/analyze-product-dependencies/references/input-contract.md +34 -0
- package/skills/analyze-product-dependencies/references/kb-integration.md +64 -0
- package/skills/analyze-product-dependencies/references/scouting-rules.md +76 -0
- package/skills/analyze-product-dependencies/scripts/test-validators.mjs +460 -0
- package/skills/analyze-product-dependencies/scripts/validate-api-documentation.mjs +194 -0
- package/skills/analyze-product-dependencies/scripts/validate-dependency-analysis.mjs +200 -0
- package/skills/analyze-product-dependencies/scripts/validate-product-requirement-input.mjs +90 -0
- package/skills/analyze-product-dependencies/scripts/validation-helpers.mjs +138 -0
- package/skills/analyze-product-requirements/SKILL.md +145 -0
- package/skills/analyze-product-requirements/references/acceptance-criteria.md +87 -0
- package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +70 -0
- package/skills/analyze-product-requirements/references/example.md +104 -0
- package/skills/analyze-product-requirements/references/forward-test-cases.md +144 -0
- package/skills/analyze-product-requirements/references/kb-integration.md +56 -0
- package/skills/analyze-product-requirements/references/product-analysis-schema.md +41 -0
- package/skills/analyze-product-requirements/references/product-requirement-schema.md +42 -0
- package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +66 -0
- package/skills/analyze-product-requirements/scripts/compute-source-identity.mjs +35 -0
- package/skills/analyze-product-requirements/scripts/test-validators.mjs +501 -0
- package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +100 -0
- package/skills/analyze-product-requirements/scripts/validate-product-requirement.mjs +109 -0
- package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +109 -0
- package/skills/analyze-product-requirements/scripts/validation-helpers.mjs +175 -0
- package/skills/browser-tools/SKILL.md +196 -0
- package/skills/browser-tools/browser-content.js +103 -0
- package/skills/browser-tools/browser-cookies.js +35 -0
- package/skills/browser-tools/browser-eval.js +53 -0
- package/skills/browser-tools/browser-hn-scraper.js +108 -0
- package/skills/browser-tools/browser-nav.js +44 -0
- package/skills/browser-tools/browser-pick.js +162 -0
- package/skills/browser-tools/browser-screenshot.js +34 -0
- package/skills/browser-tools/browser-start.js +86 -0
- package/skills/browser-tools/package-lock.json +2556 -0
- package/skills/browser-tools/package.json +19 -0
- package/skills/code-review-core/SKILL.md +20 -0
- package/skills/codebase-scout/SKILL.md +19 -0
- package/skills/frontend-bounded-implement/SKILL.md +53 -0
- package/skills/frontend-design-review/SKILL.md +79 -0
- package/skills/frontend-design-review/references/review-checklist.md +43 -0
- package/skills/frontend-implementation/SKILL.md +52 -0
- package/skills/frontend-implementation/references/code-standards.md +33 -0
- package/skills/frontend-implementation/references/design-spec.md +56 -0
- package/skills/frontend-implementation/references/node-contracts.md +31 -0
- package/skills/frontend-review/SKILL.md +55 -0
- package/skills/frontend-review/references/review-findings.md +50 -0
- package/skills/frontend-verification/SKILL.md +61 -0
- package/skills/frontend-verification/references/verification-checklist.md +48 -0
- package/skills/grill-me/SKILL.md +10 -0
- package/skills/grill-with-docs/SKILL.md +80 -0
- package/skills/grill-with-docs/adr-format.md +58 -0
- package/skills/grill-with-docs/context-format.md +52 -0
- package/skills/init-capability-evolution/SKILL.md +70 -0
- package/skills/local-jacoco-coverage/SKILL.md +281 -0
- package/skills/local-jacoco-coverage/references/requirement-to-source-mapping.md +85 -0
- package/skills/local-jacoco-coverage/references/runtime-alignment.md +106 -0
- package/skills/local-jacoco-coverage/scripts/run-coverage-analysis.sh +148 -0
- package/skills/local-jacoco-coverage/scripts/start-jacoco-agent.sh +110 -0
- package/skills/loop-agent/SKILL.md +68 -0
- package/skills/loop-agent/references/README.md +67 -0
- package/skills/loop-agent/references/command-reference.md +645 -0
- package/skills/loop-agent/references/docs-converge.md +126 -0
- package/skills/loop-agent/references/harness-policy.md +289 -0
- package/skills/loop-agent/references/hybrid-dag.md +245 -0
- package/skills/loop-agent/references/learned/README.md +21 -0
- package/skills/loop-agent/references/long-running-loop.md +57 -0
- package/skills/loop-agent/references/model-routing.md +45 -0
- package/skills/loop-agent/references/multi-worktree.md +54 -0
- package/skills/loop-agent/references/one-shot-runs.md +84 -0
- package/skills/loop-agent/references/orchestrator-and-interventions.md +175 -0
- package/skills/loop-agent/references/pi-prompt.md +23 -0
- package/skills/loop-agent/references/pi-subagent-assisted-mode.md +84 -0
- package/skills/loop-agent/references/post-implementation-and-patterns.md +54 -0
- package/skills/loop-agent/references/source-and-plan-practice.md +169 -0
- package/skills/loop-agent/references/task-workflow.md +104 -0
- package/skills/loop-agent/references/verification-and-failure-handling.md +148 -0
- package/skills/playwright-cli/SKILL.md +93 -0
- package/skills/playwright-cli/references/element-attributes.md +23 -0
- package/skills/playwright-cli/references/playwright-tests.md +39 -0
- package/skills/playwright-cli/references/request-mocking.md +87 -0
- package/skills/playwright-cli/references/running-code.md +241 -0
- package/skills/playwright-cli/references/session-management.md +225 -0
- package/skills/playwright-cli/references/storage-state.md +275 -0
- package/skills/playwright-cli/references/test-generation.md +433 -0
- package/skills/playwright-cli/references/tracing.md +5 -0
- package/skills/playwright-cli/references/video-recording.md +5 -0
- package/skills/playwright-cli-case-generator/SKILL.md +108 -0
- package/skills/requesting-code-review/SKILL.md +101 -0
- package/skills/requesting-code-review/code-reviewer.md +168 -0
- package/skills/systematic-debugging/CREATION-LOG.md +119 -0
- package/skills/systematic-debugging/SKILL.md +312 -0
- package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
- package/skills/systematic-debugging/condition-based-waiting.md +115 -0
- package/skills/systematic-debugging/defense-in-depth.md +122 -0
- package/skills/systematic-debugging/find-polluter.sh +63 -0
- package/skills/systematic-debugging/root-cause-tracing.md +169 -0
- package/skills/systematic-debugging/test-academic.md +14 -0
- package/skills/systematic-debugging/test-pressure-1.md +58 -0
- package/skills/systematic-debugging/test-pressure-2.md +68 -0
- package/skills/systematic-debugging/test-pressure-3.md +69 -0
- package/skills/test-driven-development/SKILL.md +27 -0
- package/skills/using-git-worktrees/SKILL.md +215 -0
- package/skills/verification-before-completion/SKILL.md +154 -0
- package/skills/webapp-testing/SKILL.md +19 -0
|
@@ -0,0 +1,2446 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Operator Chat — Pi SDK runtime wrapper (plan W1 / M1).
|
|
3
|
+
*
|
|
4
|
+
* Wraps the Pi SDK's services-mode session API
|
|
5
|
+
* (createAgentSessionServices + createAgentSessionFromServices + SessionManager)
|
|
6
|
+
* into a small, testable surface that the Console HTTP layer can drive.
|
|
7
|
+
*
|
|
8
|
+
* Isolation guarantees (plan D4 / V12 / V14):
|
|
9
|
+
* - Chat sessions are persisted to a DEDICATED sessionDir (not the user's
|
|
10
|
+
* default ~/.pi/agent/sessions). Naming prefix `operator-chat-` makes them
|
|
11
|
+
* auditable and cleanable (plan Q4 / Q8).
|
|
12
|
+
* - The ResourceLoader uses the SDK's standard OPEN surface (ADR 0012):
|
|
13
|
+
* user/project AGENTS.md context files, user (~/.pi/agent/skills/) and
|
|
14
|
+
* project (.agents/skills/ init projection) skills, and user/project
|
|
15
|
+
* extensions all load via the SDK DefaultResourceLoader; credential/model
|
|
16
|
+
* plane is shared via the same agentDir auth.json/models.json.
|
|
17
|
+
* - Active tools are pinned to the operator-chat surface at session create
|
|
18
|
+
* AND re-pinned before each prompt (three-gate, design §7.5 / ADR 0011):
|
|
19
|
+
* the FULL operator action set PLUS full Pi builtins
|
|
20
|
+
* (read/write/edit/bash/grep/find/ls) and optional safe-*, PLUS extension
|
|
21
|
+
* tools registered by user/project extensions (ADR 0012). Non-Pi write
|
|
22
|
+
* channels (apply_patch/full-tools/shell/coding-chat) stay excluded.
|
|
23
|
+
*
|
|
24
|
+
* The actual SDK calls are injected via `PiSdkBindings` so this module is
|
|
25
|
+
* unit-testable without a live Pi install. Production bindings come from
|
|
26
|
+
* `createDefaultPiSdkBindings()`.
|
|
27
|
+
*/
|
|
28
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
29
|
+
import { access } from "node:fs/promises";
|
|
30
|
+
import path from "node:path";
|
|
31
|
+
import { extractAssistantContentBlocks, extractAssistantText, isAssistantSdkMessage, } from "./assistant-content.js";
|
|
32
|
+
import { projectCompactSnapshot, } from "./chat-event-store.js";
|
|
33
|
+
import { extractUsageSample } from "./usage.js";
|
|
34
|
+
import { OPERATOR_CHAT_ALLOWED_TOOLS, authorizeOperatorChatTool, assertNoWriteToolInList, } from "./tools.js";
|
|
35
|
+
import { createOperatorChatResourceLoader, } from "./resource-loader.js";
|
|
36
|
+
import { applySkillAutoInvocationPreferences, isAutoInvocationEnabled, loadResourcePreferences, skillResourceId, } from "./resource-preferences-store.js";
|
|
37
|
+
import { buildModelCallableToolSchemas } from "./tool-adapter.js";
|
|
38
|
+
import { resolveDefaultChatModel, resolveLowChatModel, } from "./model-resolver.js";
|
|
39
|
+
import { filterActiveInterviewTools } from "../interview/tools.js";
|
|
40
|
+
import { RUNTIME_CONTEXT_TEXT_MAX, redactRuntimeText, } from "./runtime-context.js";
|
|
41
|
+
/**
|
|
42
|
+
* Base system prompt fragment every General Operator Chat session receives.
|
|
43
|
+
*
|
|
44
|
+
* 2026-08-11 (ADR 0013): this is a SHORT runtime responsibility layer only.
|
|
45
|
+
* Project governance (operator-only policy, allowedPaths/forbiddenPaths,
|
|
46
|
+
* writer writeSet, taskKind routing, verification matrix, DAG recovery
|
|
47
|
+
* commands, closeout) lives in the loaded target-project AGENTS.md. The
|
|
48
|
+
* prompt below covers: Operator identity, the real tool surface (typed
|
|
49
|
+
* operator tools first + Pi tools, obey AGENTS.md), denied non-Pi runtimes,
|
|
50
|
+
* safe-* probes, init/init-upgrade fallback for uninitialized repos, DAG
|
|
51
|
+
* supervision with 60/90/120/180s adaptive polling
|
|
52
|
+
* (operationGet/status/dagReport/dagDoctor/operationWait), R2 recovery
|
|
53
|
+
* (dagReport → dagRerunPlan → dagRerun; ineligible → standaloneTaskRerun or
|
|
54
|
+
* workerTaskRetry, never dagResume) and G2 bounded writeSet auto-approval.
|
|
55
|
+
*
|
|
56
|
+
* The old Human Gate tutorial paragraphs (prepare + browser Human Gate,
|
|
57
|
+
* "confirmDagConfirmation is a human-only action", the long "Recovery default
|
|
58
|
+
* order" table) are REMOVED — bounded DAGs start and recover autonomously
|
|
59
|
+
* (AC-01: ≤ 2500 chars; 2498-slimming ratio retired 2026-08-18).
|
|
60
|
+
*/
|
|
61
|
+
export const OPERATOR_CHAT_SYSTEM_PROMPT_BASE = [
|
|
62
|
+
"You are General Operator Chat.",
|
|
63
|
+
"Use operator tools first (dagReport, dagRerunPlan, dagRerun). Pi read/write/edit/bash/grep/find/ls are available; obey repository AGENTS.md. apply_patch, full-tools, shell and coding-chat are denied. Prefer safe-read/safe-grep for probes.",
|
|
64
|
+
"No harness and asked to init: `loop-agent init instructions --repo-root .`, then `loop-agent init --repo-root . --profile full --merge`; verify with doctor, inspect and docs audit. Updates: `loop-agent init upgrade --repo-root . --json` until stable; `loop-agent init check-update` is read-only.",
|
|
65
|
+
"Long DAGs: prepareDagExecution → runDag → operationId. Never foreground-Bash task advance, tail/head pipes or nohup/Start-Process/start; do not end on accepted/queued/running or an operationId — NOT completion. Supervise operationGet/status/dagReport/dagDoctor/operationWait with 60s → 90s → 120s → 180s backoff; reset to 60s on state change; 30-60s re-checks when stall suspected. Every operationWait continuation MUST use afterSeq=previous nextSeq (prefer recommendedNextCall); never reuse an older cursor. Heartbeats are not meaningful progress.",
|
|
66
|
+
"On failure consume terminal runSummary first. If progress exists, wait; else dagReport then dagRerunPlan(fromNode=primaryFailure.nodeId); eligible → dagRerun (node rerun only: reruns the failed subgraph, keeps prior successes). If plan ineligible (unsafe-shell subgraph, binding/workspace drift): Worker Failed → workerTaskRetry; standalone → standaloneTaskRerun after contract adopt (regenerates the whole task, not node continue). Do not call dagResume. A run-failed task without an open gate is terminal: do not re-advance a run-failed task. Before run start, two identical lifecycleState/blocker/revision/DAG-hash observations mean no progress: stop and report after one bounded probe; never loop identical bash/read/grep. Auto-approve only bounded writeSet inside allowedPaths, outside forbiddenPaths, with structured verification.",
|
|
67
|
+
"Iterate toward the goal in bounded rounds: first summarize the previous round's error in one line (wrong fix, wrong node, or environment), fix its root cause — not the symptom — then rerun the failed node with a fresh plan. Never resubmit an unchanged fix; after two rounds failing the same way, stop and report evidence and remaining routes.",
|
|
68
|
+
"Continue until success, user stop, or no safe recovery remains; then stop and report failure/evidence. Never create a new task for a transient failure or replace repair-pi with direct edits.",
|
|
69
|
+
].join("\n");
|
|
70
|
+
/** Compose the inspectable system prompt actually injected into Operator Chat. */
|
|
71
|
+
export function composeOperatorChatSystemPrompt(input) {
|
|
72
|
+
return [
|
|
73
|
+
OPERATOR_CHAT_SYSTEM_PROMPT_BASE,
|
|
74
|
+
...(input.systemPromptSuffix?.trim()
|
|
75
|
+
? [input.systemPromptSuffix.trim()]
|
|
76
|
+
: []),
|
|
77
|
+
].join("\n\n");
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Built-in SDK tool names registered for every Chat session (ADR 0011).
|
|
81
|
+
* Full Pi builtins: read/write/edit/bash/grep/find/ls. safe-* custom tools
|
|
82
|
+
* remain available in parallel for bounded sensitive probes.
|
|
83
|
+
*/
|
|
84
|
+
export const OPERATOR_CHAT_BUILTIN_EXPLORE_TOOLS = Object.freeze([
|
|
85
|
+
"read",
|
|
86
|
+
"write",
|
|
87
|
+
"edit",
|
|
88
|
+
"bash",
|
|
89
|
+
"grep",
|
|
90
|
+
"find",
|
|
91
|
+
"ls",
|
|
92
|
+
]);
|
|
93
|
+
/**
|
|
94
|
+
* Canonical active tool-set handed to setActiveToolsByName at every gate:
|
|
95
|
+
* every model-callable operator action (dynamic, from the registry) PLUS full
|
|
96
|
+
* Pi builtins (ADR 0011) PLUS safe explore custom tools (safe-read /
|
|
97
|
+
* safe-grep / git-status / git-diff). Non-Pi write channels (apply_patch /
|
|
98
|
+
* full-tools / shell / coding-chat) stay excluded.
|
|
99
|
+
*/
|
|
100
|
+
export const OPERATOR_CHAT_SAFE_EXPLORE_TOOL_IDS = Object.freeze([
|
|
101
|
+
"safe-read",
|
|
102
|
+
"safe-grep",
|
|
103
|
+
"git-status",
|
|
104
|
+
"git-diff",
|
|
105
|
+
]);
|
|
106
|
+
export function OPERATOR_CHAT_ACTIVE_TOOL_NAMES() {
|
|
107
|
+
return [
|
|
108
|
+
...OPERATOR_CHAT_ALLOWED_TOOLS,
|
|
109
|
+
...OPERATOR_CHAT_BUILTIN_EXPLORE_TOOLS,
|
|
110
|
+
...OPERATOR_CHAT_SAFE_EXPLORE_TOOL_IDS,
|
|
111
|
+
];
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Non-Pi write / coding channels that must never be activated in Operator
|
|
115
|
+
* Chat even when a user/project extension registers them (ADR 0011 / 0012).
|
|
116
|
+
* Mirrors the NON_PI_WRITE_CHANNELS guard in tools.ts for the Gate 2 pin.
|
|
117
|
+
*/
|
|
118
|
+
const OPERATOR_CHAT_EXTENSION_DENIED_TOOL_IDS = new Set([
|
|
119
|
+
"apply_patch",
|
|
120
|
+
"apply-patch",
|
|
121
|
+
"full-tools",
|
|
122
|
+
"full_tools",
|
|
123
|
+
"coding-chat",
|
|
124
|
+
"coding_chat",
|
|
125
|
+
"shell",
|
|
126
|
+
].map((t) => t.toLowerCase()));
|
|
127
|
+
/**
|
|
128
|
+
* Gate 2 active-tool set for a live session (ADR 0012): the operator-chat
|
|
129
|
+
* baseline (operator actions + Pi builtins + safe explore) PLUS extension
|
|
130
|
+
* tools registered by user/project extensions. SDK setActiveToolsByName is
|
|
131
|
+
* REPLACEMENT-style, so extension tools would be disabled by the fixed
|
|
132
|
+
* baseline; this helper re-adds every registry tool that is not part of the
|
|
133
|
+
* baseline and not on the deny surface (apply_patch/full-tools/shell/
|
|
134
|
+
* coding-chat). Unknown/denied names are never activated; Gate 3 still
|
|
135
|
+
* re-authorizes every tool call per invocation.
|
|
136
|
+
*/
|
|
137
|
+
export function computeOperatorChatActiveToolNames(session) {
|
|
138
|
+
const baseline = OPERATOR_CHAT_ACTIVE_TOOL_NAMES();
|
|
139
|
+
if (!session?.getAllTools)
|
|
140
|
+
return baseline;
|
|
141
|
+
const baselineLower = new Set(baseline.map((t) => t.toLowerCase()));
|
|
142
|
+
const extras = [];
|
|
143
|
+
for (const tool of session.getAllTools()) {
|
|
144
|
+
const name = tool?.name;
|
|
145
|
+
if (!name || typeof name !== "string")
|
|
146
|
+
continue;
|
|
147
|
+
const lower = name.toLowerCase();
|
|
148
|
+
if (baselineLower.has(lower))
|
|
149
|
+
continue;
|
|
150
|
+
if (OPERATOR_CHAT_EXTENSION_DENIED_TOOL_IDS.has(lower))
|
|
151
|
+
continue;
|
|
152
|
+
extras.push(name);
|
|
153
|
+
}
|
|
154
|
+
return extras.length > 0 ? [...baseline, ...extras] : baseline;
|
|
155
|
+
}
|
|
156
|
+
const OPERATOR_CHAT_SESSION_EXCLUDE_TOOLS = Object.freeze([
|
|
157
|
+
"apply_patch",
|
|
158
|
+
"apply-patch",
|
|
159
|
+
"full-tools",
|
|
160
|
+
"full_tools",
|
|
161
|
+
"coding-chat",
|
|
162
|
+
"coding_chat",
|
|
163
|
+
"shell",
|
|
164
|
+
]);
|
|
165
|
+
function createConsoleExtensionBindings() {
|
|
166
|
+
return {
|
|
167
|
+
mode: "rpc",
|
|
168
|
+
onError: (error) => {
|
|
169
|
+
const message = error instanceof Error
|
|
170
|
+
? error.message
|
|
171
|
+
: typeof error === "object" &&
|
|
172
|
+
error &&
|
|
173
|
+
"error" in error &&
|
|
174
|
+
typeof error.error === "string"
|
|
175
|
+
? String(error.error)
|
|
176
|
+
: String(error);
|
|
177
|
+
process.stderr.write(`[console] extension: ${message}\n`);
|
|
178
|
+
},
|
|
179
|
+
uiContext: {
|
|
180
|
+
select: async () => undefined,
|
|
181
|
+
confirm: async () => false,
|
|
182
|
+
input: async () => undefined,
|
|
183
|
+
editor: async () => undefined,
|
|
184
|
+
notify: () => undefined,
|
|
185
|
+
onTerminalInput: () => () => undefined,
|
|
186
|
+
setStatus: () => undefined,
|
|
187
|
+
setWorkingMessage: () => undefined,
|
|
188
|
+
setWorkingVisible: () => undefined,
|
|
189
|
+
setWorkingIndicator: () => undefined,
|
|
190
|
+
setHiddenThinkingLabel: () => undefined,
|
|
191
|
+
setWidget: () => undefined,
|
|
192
|
+
setFooter: () => undefined,
|
|
193
|
+
setHeader: () => undefined,
|
|
194
|
+
setTitle: () => undefined,
|
|
195
|
+
custom: async () => undefined,
|
|
196
|
+
pasteToEditor: () => undefined,
|
|
197
|
+
setEditorText: () => undefined,
|
|
198
|
+
getEditorText: () => "",
|
|
199
|
+
addAutocompleteProvider: () => undefined,
|
|
200
|
+
setEditorComponent: () => undefined,
|
|
201
|
+
getEditorComponent: () => undefined,
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
async function bindSessionExtensions(session) {
|
|
206
|
+
if (typeof session.bindExtensions !== "function")
|
|
207
|
+
return;
|
|
208
|
+
await session.bindExtensions(createConsoleExtensionBindings());
|
|
209
|
+
}
|
|
210
|
+
function pinOperatorChatTools(session) {
|
|
211
|
+
session.setActiveToolsByName(computeOperatorChatActiveToolNames(session));
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Build Pi custom ToolDefinition objects for every whitelisted operator action.
|
|
215
|
+
* Each tool's execute() routes into the operator action dispatcher (Gate 3).
|
|
216
|
+
* Requires the `typebox` + `defineTool` packages (both bundled with the SDK).
|
|
217
|
+
*/
|
|
218
|
+
async function buildCustomOperatorTools(options) {
|
|
219
|
+
// typebox is a transitive dep of @earendil-works/pi-coding-agent; Node's
|
|
220
|
+
// resolver hoists to the SDK's nested copy. defineTool is re-exported by SDK.
|
|
221
|
+
const [{ Type }, { defineTool }] = await Promise.all([
|
|
222
|
+
import("typebox"),
|
|
223
|
+
import("@earendil-works/pi-coding-agent"),
|
|
224
|
+
]);
|
|
225
|
+
const schemas = buildModelCallableToolSchemas();
|
|
226
|
+
const ctx = options.actionContext;
|
|
227
|
+
return schemas.map((schema) => {
|
|
228
|
+
// TypeBox schema is built dynamically; cast through unknown to the builder
|
|
229
|
+
// since Static<TParams> is structural and cannot be statically inferred here.
|
|
230
|
+
const parameters = Type.Object(schema.inputParams.reduce((acc, param) => {
|
|
231
|
+
// Carry the registry's param description into the tool schema so
|
|
232
|
+
// the model sees parameter semantics, not just names/types.
|
|
233
|
+
const desc = param.description?.trim()
|
|
234
|
+
? { description: param.description }
|
|
235
|
+
: {};
|
|
236
|
+
let t;
|
|
237
|
+
switch (param.type) {
|
|
238
|
+
case "string":
|
|
239
|
+
t = Type.String(desc);
|
|
240
|
+
break;
|
|
241
|
+
case "boolean":
|
|
242
|
+
t = Type.Boolean(desc);
|
|
243
|
+
break;
|
|
244
|
+
case "number":
|
|
245
|
+
t = Type.Number(desc);
|
|
246
|
+
break;
|
|
247
|
+
case "array":
|
|
248
|
+
t = Type.Array(Type.String({}), Object.keys(desc).length ? desc : {});
|
|
249
|
+
break;
|
|
250
|
+
default:
|
|
251
|
+
t = Type.Object({}, { additionalProperties: true });
|
|
252
|
+
}
|
|
253
|
+
acc[param.name] = param.required ? t : Type.Optional(t);
|
|
254
|
+
return acc;
|
|
255
|
+
}, {}), { additionalProperties: false });
|
|
256
|
+
return defineTool({
|
|
257
|
+
name: schema.toolId,
|
|
258
|
+
label: schema.toolId,
|
|
259
|
+
description: `${schema.description} (kind=${schema.kind}; cli: ${schema.cli})`,
|
|
260
|
+
promptSnippet: schema.description,
|
|
261
|
+
parameters: parameters,
|
|
262
|
+
async execute(toolCallId, params) {
|
|
263
|
+
const decision = authorizeOperatorChatTool(schema.toolId);
|
|
264
|
+
if (!decision.ok) {
|
|
265
|
+
return {
|
|
266
|
+
content: [
|
|
267
|
+
{
|
|
268
|
+
type: "text",
|
|
269
|
+
text: JSON.stringify({
|
|
270
|
+
error: decision.message,
|
|
271
|
+
code: decision.code,
|
|
272
|
+
}),
|
|
273
|
+
},
|
|
274
|
+
],
|
|
275
|
+
details: { error: decision.message, code: decision.code },
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
// Defer require to avoid circular import at module load.
|
|
279
|
+
const { dispatchChatToolCall } = await import("./session-store.js");
|
|
280
|
+
const result = await dispatchChatToolCall(ctx, decision.toolId, params, toolCallId);
|
|
281
|
+
const payload = result.ok
|
|
282
|
+
? result.result
|
|
283
|
+
: { error: result.message, code: result.errorCode };
|
|
284
|
+
return {
|
|
285
|
+
content: [{ type: "text", text: JSON.stringify(payload) }],
|
|
286
|
+
details: payload,
|
|
287
|
+
};
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Build the safe explore custom tools (roadmap M0-A / G16): safe-read,
|
|
294
|
+
* safe-grep, gitStatus, gitDiff. These replace the bare SDK bash/read/grep so
|
|
295
|
+
* the model can probe the repo WITHOUT write escapes (echo redirect, tee,
|
|
296
|
+
* sed -i, node -e) and WITHOUT reading sensitive files (env files, key/pem,
|
|
297
|
+
* auth.json, sessions dir) — and every returned byte is secret-scrubbed
|
|
298
|
+
* before reaching the model context.
|
|
299
|
+
*
|
|
300
|
+
* gitStatus/gitDiff use a FIXED argv (typed, no shell) so they cannot be
|
|
301
|
+
* turned into an arbitrary-shell vector.
|
|
302
|
+
*/
|
|
303
|
+
async function buildExploreCustomTools(options) {
|
|
304
|
+
const [{ Type }, { defineTool }] = await Promise.all([
|
|
305
|
+
import("typebox"),
|
|
306
|
+
import("@earendil-works/pi-coding-agent"),
|
|
307
|
+
]);
|
|
308
|
+
const { execFileSync } = await import("node:child_process");
|
|
309
|
+
const repoRoot = options.repoRoot;
|
|
310
|
+
const { safeReadFile, safeGrep, scrubSecrets } = await import("./explore-tools.js");
|
|
311
|
+
function safeGit(args) {
|
|
312
|
+
try {
|
|
313
|
+
const stdout = execFileSync("git", args, {
|
|
314
|
+
cwd: repoRoot,
|
|
315
|
+
encoding: "utf8",
|
|
316
|
+
// Fixed argv, no shell, bounded output.
|
|
317
|
+
maxBuffer: 512 * 1024,
|
|
318
|
+
timeout: 10_000,
|
|
319
|
+
windowsHide: true,
|
|
320
|
+
});
|
|
321
|
+
return { ok: true, stdout };
|
|
322
|
+
}
|
|
323
|
+
catch (e) {
|
|
324
|
+
return {
|
|
325
|
+
ok: false,
|
|
326
|
+
code: "GIT_FAILED",
|
|
327
|
+
message: e instanceof Error ? e.message : String(e),
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
const safeReadTool = defineTool({
|
|
332
|
+
name: "safe-read",
|
|
333
|
+
label: "safe-read",
|
|
334
|
+
description: "Read a repo-relative file with sensitive-file denylist (.env*/*.key/*.pem/.git/**/auth.json/sessions/**) and secret scrubbing. Absolute paths and .. are rejected.",
|
|
335
|
+
promptSnippet: "safe-read: read a repo file (safe; secrets scrubbed)",
|
|
336
|
+
parameters: Type.Object({
|
|
337
|
+
path: Type.String({ description: "repo-relative file path" }),
|
|
338
|
+
}, { additionalProperties: false }),
|
|
339
|
+
async execute(_toolCallId, params) {
|
|
340
|
+
const raw = String(params.path ?? "");
|
|
341
|
+
try {
|
|
342
|
+
const result = await safeReadFile(repoRoot, raw);
|
|
343
|
+
return {
|
|
344
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
345
|
+
details: result,
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
catch (e) {
|
|
349
|
+
const err = e;
|
|
350
|
+
return {
|
|
351
|
+
content: [
|
|
352
|
+
{
|
|
353
|
+
type: "text",
|
|
354
|
+
text: JSON.stringify({
|
|
355
|
+
error: err.message ?? String(e),
|
|
356
|
+
code: err.code ?? "READ_FAILED",
|
|
357
|
+
}),
|
|
358
|
+
},
|
|
359
|
+
],
|
|
360
|
+
details: {
|
|
361
|
+
error: err.message ?? String(e),
|
|
362
|
+
code: err.code ?? "READ_FAILED",
|
|
363
|
+
},
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
},
|
|
367
|
+
});
|
|
368
|
+
const safeGrepTool = defineTool({
|
|
369
|
+
name: "safe-grep",
|
|
370
|
+
label: "safe-grep",
|
|
371
|
+
description: "Line search across the repo working tree with sensitive-file denylist and secret scrubbing. pattern is a JS regex source; optional glob filters paths.",
|
|
372
|
+
promptSnippet: "safe-grep: search repo files (safe; secrets scrubbed)",
|
|
373
|
+
parameters: Type.Object({
|
|
374
|
+
pattern: Type.String({ description: "regex source (no shell)" }),
|
|
375
|
+
caseInsensitive: Type.Optional(Type.Boolean()),
|
|
376
|
+
glob: Type.Optional(Type.String({ description: "optional glob filter e.g. *.ts" })),
|
|
377
|
+
}, { additionalProperties: false }),
|
|
378
|
+
async execute(_toolCallId, params) {
|
|
379
|
+
try {
|
|
380
|
+
const result = await safeGrep(repoRoot, String(params.pattern ?? ""), {
|
|
381
|
+
caseInsensitive: Boolean(params.caseInsensitive),
|
|
382
|
+
glob: params.glob ? String(params.glob) : undefined,
|
|
383
|
+
});
|
|
384
|
+
return {
|
|
385
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
386
|
+
details: result,
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
catch (e) {
|
|
390
|
+
const err = e;
|
|
391
|
+
return {
|
|
392
|
+
content: [
|
|
393
|
+
{
|
|
394
|
+
type: "text",
|
|
395
|
+
text: JSON.stringify({
|
|
396
|
+
error: err.message ?? String(e),
|
|
397
|
+
code: err.code ?? "GREP_FAILED",
|
|
398
|
+
}),
|
|
399
|
+
},
|
|
400
|
+
],
|
|
401
|
+
details: {
|
|
402
|
+
error: err.message ?? String(e),
|
|
403
|
+
code: err.code ?? "GREP_FAILED",
|
|
404
|
+
},
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
},
|
|
408
|
+
});
|
|
409
|
+
const gitStatusTool = defineTool({
|
|
410
|
+
name: "git-status",
|
|
411
|
+
label: "git-status",
|
|
412
|
+
description: "Typed `git status --porcelain` (fixed argv, no shell). Returns scrubbed working-tree status.",
|
|
413
|
+
promptSnippet: "git-status: working tree status (read-only)",
|
|
414
|
+
parameters: Type.Object({}, { additionalProperties: false }),
|
|
415
|
+
async execute() {
|
|
416
|
+
const r = safeGit(["status", "--porcelain"]);
|
|
417
|
+
if (!r.ok) {
|
|
418
|
+
return {
|
|
419
|
+
content: [
|
|
420
|
+
{
|
|
421
|
+
type: "text",
|
|
422
|
+
text: JSON.stringify({ error: r.message, code: r.code }),
|
|
423
|
+
},
|
|
424
|
+
],
|
|
425
|
+
details: { error: r.message, code: r.code },
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
const { scrubbed, redactions } = scrubSecrets(r.stdout);
|
|
429
|
+
const payload = { status: scrubbed, redactions };
|
|
430
|
+
return {
|
|
431
|
+
content: [{ type: "text", text: JSON.stringify(payload) }],
|
|
432
|
+
details: payload,
|
|
433
|
+
};
|
|
434
|
+
},
|
|
435
|
+
});
|
|
436
|
+
const gitDiffTool = defineTool({
|
|
437
|
+
name: "git-diff",
|
|
438
|
+
label: "git-diff",
|
|
439
|
+
description: "Typed `git diff` (fixed argv: --stat or a single -- path; no shell). Returns scrubbed diff. path must be repo-relative and not sensitive.",
|
|
440
|
+
promptSnippet: "git-diff: diff (read-only, scrubbed)",
|
|
441
|
+
parameters: Type.Object({
|
|
442
|
+
path: Type.Optional(Type.String({
|
|
443
|
+
description: "optional repo-relative path to diff (default: whole tree --stat)",
|
|
444
|
+
})),
|
|
445
|
+
}, { additionalProperties: false }),
|
|
446
|
+
async execute(_toolCallId, params) {
|
|
447
|
+
let rawPath = params.path ? String(params.path) : undefined;
|
|
448
|
+
// If a path is supplied, validate it via the read-path guard so git diff
|
|
449
|
+
// cannot be aimed at sensitive files (.env* / auth.json / sessions).
|
|
450
|
+
if (rawPath) {
|
|
451
|
+
const { guardReadPath } = await import("./explore-tools.js");
|
|
452
|
+
const g = await guardReadPath(repoRoot, rawPath);
|
|
453
|
+
if (!g.ok) {
|
|
454
|
+
return {
|
|
455
|
+
content: [
|
|
456
|
+
{
|
|
457
|
+
type: "text",
|
|
458
|
+
text: JSON.stringify({ error: g.message, code: g.code }),
|
|
459
|
+
},
|
|
460
|
+
],
|
|
461
|
+
details: { error: g.message, code: g.code },
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
rawPath = g.repoRelative;
|
|
465
|
+
}
|
|
466
|
+
const args = rawPath ? ["diff", "--", rawPath] : ["diff", "--stat"];
|
|
467
|
+
const r = safeGit(args);
|
|
468
|
+
if (!r.ok) {
|
|
469
|
+
return {
|
|
470
|
+
content: [
|
|
471
|
+
{
|
|
472
|
+
type: "text",
|
|
473
|
+
text: JSON.stringify({ error: r.message, code: r.code }),
|
|
474
|
+
},
|
|
475
|
+
],
|
|
476
|
+
details: { error: r.message, code: r.code },
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
const { scrubbed, redactions } = scrubSecrets(r.stdout);
|
|
480
|
+
const payload = { diff: scrubbed, redactions };
|
|
481
|
+
return {
|
|
482
|
+
content: [{ type: "text", text: JSON.stringify(payload) }],
|
|
483
|
+
details: payload,
|
|
484
|
+
};
|
|
485
|
+
},
|
|
486
|
+
});
|
|
487
|
+
return [safeReadTool, safeGrepTool, gitStatusTool, gitDiffTool];
|
|
488
|
+
}
|
|
489
|
+
export const THINKING_LEVELS = [
|
|
490
|
+
"off",
|
|
491
|
+
"minimal",
|
|
492
|
+
"low",
|
|
493
|
+
"medium",
|
|
494
|
+
"high",
|
|
495
|
+
"xhigh",
|
|
496
|
+
"max",
|
|
497
|
+
];
|
|
498
|
+
/**
|
|
499
|
+
* Structured critical-failure error for snapshot projection (RF-02). Model
|
|
500
|
+
* enumeration or system prompt projection failures throw this so consuming
|
|
501
|
+
* routes can return 503 PI_RUNTIME_UNAVAILABLE instead of a fake-empty 200.
|
|
502
|
+
* Partial ResourceLoader / package-inventory failures are NOT critical — they
|
|
503
|
+
* keep the 200 snapshot and surface as structured diagnostics instead.
|
|
504
|
+
*/
|
|
505
|
+
export class RuntimeSnapshotUnavailableError extends Error {
|
|
506
|
+
code = "PI_RUNTIME_UNAVAILABLE";
|
|
507
|
+
constructor(message) {
|
|
508
|
+
super(message);
|
|
509
|
+
this.name = "RuntimeSnapshotUnavailableError";
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Browser-safe projection of a live runtime snapshot (design §2.2 / RF-03).
|
|
514
|
+
*
|
|
515
|
+
* Every browser-visible string runs the shared secret-scrub chain
|
|
516
|
+
* (scrubSecrets + ghp_/Bearer redaction via redactRuntimeText) and a fixed
|
|
517
|
+
* per-field length limit: 200 (names/spec/source/origin), 400 (paths/errors/
|
|
518
|
+
* diagnostic messages), 600 (skill descriptions), 80 (package versions);
|
|
519
|
+
* truncation uses the fixed `…[truncated N chars …]` marker. The server-only
|
|
520
|
+
* `raw` prompt and the RAW-based `sha256`/`characters` pass through untouched,
|
|
521
|
+
* and real empty lists stay empty.
|
|
522
|
+
*
|
|
523
|
+
* Pure + deterministic and exported so the route harness reuses the exact
|
|
524
|
+
* production implementation (same code path as getRuntimeSnapshot).
|
|
525
|
+
*/
|
|
526
|
+
export function projectRuntimeSnapshot(input) {
|
|
527
|
+
const field = (value, max) => value === undefined ? undefined : redactRuntimeText(value, max);
|
|
528
|
+
const projectSkill = (skill) => ({
|
|
529
|
+
name: field(skill.name, 200) ?? "",
|
|
530
|
+
description: field(skill.description, 600) ?? "",
|
|
531
|
+
path: field(skill.path, 400) ?? "",
|
|
532
|
+
...(skill.baseDir ? { baseDir: field(skill.baseDir, 400) } : {}),
|
|
533
|
+
scope: skill.scope,
|
|
534
|
+
source: field(skill.source, 200) ?? "",
|
|
535
|
+
resourceId: field(skill.resourceId, 200) ?? "",
|
|
536
|
+
autoInvocationEnabled: skill.autoInvocationEnabled,
|
|
537
|
+
diagnostics: skill.diagnostics.map((diag) => field(diag, 400) ?? ""),
|
|
538
|
+
});
|
|
539
|
+
const projectExtension = (ext) => ({
|
|
540
|
+
name: field(ext.name, 200) ?? "",
|
|
541
|
+
path: field(ext.path, 400) ?? "",
|
|
542
|
+
...(ext.resolvedPath
|
|
543
|
+
? { resolvedPath: field(ext.resolvedPath, 400) }
|
|
544
|
+
: {}),
|
|
545
|
+
source: field(ext.source, 200) ?? "",
|
|
546
|
+
scope: ext.scope,
|
|
547
|
+
...(ext.origin ? { origin: field(ext.origin, 200) } : {}),
|
|
548
|
+
enabled: ext.enabled,
|
|
549
|
+
...(ext.error ? { error: field(ext.error, 400) } : {}),
|
|
550
|
+
});
|
|
551
|
+
const projectPackage = (pkg) => ({
|
|
552
|
+
spec: field(pkg.spec, 200) ?? "",
|
|
553
|
+
scope: pkg.scope,
|
|
554
|
+
enabled: pkg.enabled,
|
|
555
|
+
...(pkg.version ? { version: field(pkg.version, 80) } : {}),
|
|
556
|
+
...(pkg.configuredVersion
|
|
557
|
+
? { configuredVersion: field(pkg.configuredVersion, 80) }
|
|
558
|
+
: {}),
|
|
559
|
+
...(pkg.packageName ? { packageName: field(pkg.packageName, 200) } : {}),
|
|
560
|
+
...(pkg.path ? { path: field(pkg.path, 400) } : {}),
|
|
561
|
+
...(pkg.installedPath ? { installedPath: field(pkg.installedPath, 400) } : {}),
|
|
562
|
+
...(pkg.cwd ? { cwd: field(pkg.cwd, 400) } : {}),
|
|
563
|
+
resolved: pkg.resolved.map((resource) => ({
|
|
564
|
+
type: resource.type,
|
|
565
|
+
name: field(resource.name, 200) ?? "",
|
|
566
|
+
path: field(resource.path, 400) ?? "",
|
|
567
|
+
source: field(resource.source, 200) ?? "",
|
|
568
|
+
scope: resource.scope,
|
|
569
|
+
})),
|
|
570
|
+
diagnostics: pkg.diagnostics.map((diag) => field(diag, 400) ?? ""),
|
|
571
|
+
});
|
|
572
|
+
const projectMcpTool = (tool) => ({
|
|
573
|
+
name: field(tool.name, 200) ?? "",
|
|
574
|
+
description: field(tool.description, 600) ?? "",
|
|
575
|
+
...(tool.server ? { server: field(tool.server, 200) } : {}),
|
|
576
|
+
source: field(tool.source, 200) ?? "",
|
|
577
|
+
scope: tool.scope,
|
|
578
|
+
...(tool.path ? { path: field(tool.path, 400) } : {}),
|
|
579
|
+
active: tool.active,
|
|
580
|
+
});
|
|
581
|
+
const projectMcp = (mcp) => ({
|
|
582
|
+
servers: mcp.servers.map((server) => ({
|
|
583
|
+
name: field(server.name, 200) ?? "",
|
|
584
|
+
scope: server.scope,
|
|
585
|
+
status: server.status,
|
|
586
|
+
enabled: server.enabled,
|
|
587
|
+
...(server.transport ? { transport: field(server.transport, 80) } : {}),
|
|
588
|
+
...(server.command ? { command: field(server.command, 400) } : {}),
|
|
589
|
+
...(server.url ? { url: field(server.url, 400) } : {}),
|
|
590
|
+
...(server.configPath ? { configPath: field(server.configPath, 400) } : {}),
|
|
591
|
+
tools: server.tools.map(projectMcpTool),
|
|
592
|
+
})),
|
|
593
|
+
unassignedTools: mcp.unassignedTools.map(projectMcpTool),
|
|
594
|
+
});
|
|
595
|
+
const projectDiagnostic = (diag) => ({
|
|
596
|
+
type: diag.type,
|
|
597
|
+
...(diag.code ? { code: diag.code } : {}),
|
|
598
|
+
message: field(diag.message, 400) ?? "",
|
|
599
|
+
...(diag.path ? { path: field(diag.path, 400) } : {}),
|
|
600
|
+
...(diag.retryable !== undefined
|
|
601
|
+
? { retryable: diag.retryable }
|
|
602
|
+
: {}),
|
|
603
|
+
});
|
|
604
|
+
return {
|
|
605
|
+
sessionId: input.sessionId,
|
|
606
|
+
revision: input.revision,
|
|
607
|
+
loadedAt: input.loadedAt,
|
|
608
|
+
selection: {
|
|
609
|
+
...(input.selection.model
|
|
610
|
+
? {
|
|
611
|
+
model: {
|
|
612
|
+
provider: input.selection.model.provider,
|
|
613
|
+
id: input.selection.model.id,
|
|
614
|
+
...(input.selection.model.name
|
|
615
|
+
? { name: field(input.selection.model.name, 200) }
|
|
616
|
+
: {}),
|
|
617
|
+
},
|
|
618
|
+
}
|
|
619
|
+
: {}),
|
|
620
|
+
thinkingSelection: input.selection.thinkingSelection,
|
|
621
|
+
...(input.selection.effectiveThinkingLevel
|
|
622
|
+
? { effectiveThinkingLevel: input.selection.effectiveThinkingLevel }
|
|
623
|
+
: {}),
|
|
624
|
+
},
|
|
625
|
+
models: input.models.map((model) => ({
|
|
626
|
+
provider: model.provider,
|
|
627
|
+
id: model.id,
|
|
628
|
+
...(model.name ? { name: field(model.name, 200) } : {}),
|
|
629
|
+
...(model.hasCredentials !== undefined
|
|
630
|
+
? { hasCredentials: model.hasCredentials }
|
|
631
|
+
: {}),
|
|
632
|
+
})),
|
|
633
|
+
thinkingLevels: input.thinkingLevels.map((option) => ({ ...option })),
|
|
634
|
+
systemPrompt: {
|
|
635
|
+
...(input.systemPrompt.raw !== undefined
|
|
636
|
+
? { raw: input.systemPrompt.raw }
|
|
637
|
+
: {}),
|
|
638
|
+
text: redactRuntimeText(input.systemPrompt.text, RUNTIME_CONTEXT_TEXT_MAX),
|
|
639
|
+
sha256: input.systemPrompt.sha256,
|
|
640
|
+
characters: input.systemPrompt.characters,
|
|
641
|
+
},
|
|
642
|
+
activeTools: [...input.activeTools],
|
|
643
|
+
contextFiles: input.contextFiles.map((file) => ({
|
|
644
|
+
path: field(file.path, 400) ?? "",
|
|
645
|
+
characters: file.characters,
|
|
646
|
+
})),
|
|
647
|
+
skills: input.skills.map(projectSkill),
|
|
648
|
+
extensions: input.extensions.map(projectExtension),
|
|
649
|
+
packages: input.packages.map(projectPackage),
|
|
650
|
+
mcp: projectMcp(input.mcp ?? { servers: [], unassignedTools: [] }),
|
|
651
|
+
diagnostics: input.diagnostics.map(projectDiagnostic),
|
|
652
|
+
hasBash: input.hasBash,
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
export { extractAssistantContentBlocks, extractAssistantText, isAssistantSdkMessage };
|
|
656
|
+
function readSessionManagerId(manager) {
|
|
657
|
+
if (!manager || typeof manager !== "object")
|
|
658
|
+
return undefined;
|
|
659
|
+
const record = manager;
|
|
660
|
+
if (typeof record.getSessionId === "function") {
|
|
661
|
+
try {
|
|
662
|
+
const value = record.getSessionId();
|
|
663
|
+
if (typeof value === "string" && value.trim())
|
|
664
|
+
return value;
|
|
665
|
+
}
|
|
666
|
+
catch {
|
|
667
|
+
// ignore
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
if (typeof record.sessionId === "string" && record.sessionId.trim()) {
|
|
671
|
+
return record.sessionId;
|
|
672
|
+
}
|
|
673
|
+
return undefined;
|
|
674
|
+
}
|
|
675
|
+
function readSessionManagerFile(manager) {
|
|
676
|
+
if (!manager || typeof manager !== "object")
|
|
677
|
+
return undefined;
|
|
678
|
+
const record = manager;
|
|
679
|
+
if (typeof record.getSessionFile === "function") {
|
|
680
|
+
try {
|
|
681
|
+
const value = record.getSessionFile();
|
|
682
|
+
if (typeof value === "string" && value.trim())
|
|
683
|
+
return value;
|
|
684
|
+
}
|
|
685
|
+
catch {
|
|
686
|
+
// ignore
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
if (typeof record.sessionFile === "string" && record.sessionFile.trim()) {
|
|
690
|
+
return record.sessionFile;
|
|
691
|
+
}
|
|
692
|
+
return undefined;
|
|
693
|
+
}
|
|
694
|
+
/** Structured materialization failure for dependent Chat endpoints. */
|
|
695
|
+
export class PiSessionInitFailedError extends Error {
|
|
696
|
+
code = "PI_SESSION_INIT_FAILED";
|
|
697
|
+
constructor(message) {
|
|
698
|
+
super(message);
|
|
699
|
+
this.name = "PiSessionInitFailedError";
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Console Pi runtime — holds Chat sessions and drives prompt() with three-gate
|
|
704
|
+
* tool enforcement. Session-per-sessionId; cross-request reuse via open().
|
|
705
|
+
*/
|
|
706
|
+
export class ConsolePiRuntime {
|
|
707
|
+
options;
|
|
708
|
+
loader;
|
|
709
|
+
sessions = new Map();
|
|
710
|
+
modelRuntimes = new Map();
|
|
711
|
+
sessionManagers = new Map();
|
|
712
|
+
/** Per-session SDK service scope (resourceLoader / settingsManager) used by the live runtime snapshot. */
|
|
713
|
+
serviceScopes = new Map();
|
|
714
|
+
/** Snapshot revision: bumped on create / reopen / model / thinking / reload. */
|
|
715
|
+
revisions = new Map();
|
|
716
|
+
/** RF-01: per-session reload in-flight lock (concurrent second call → PI_SESSION_BUSY). */
|
|
717
|
+
reloadInFlight = new Set();
|
|
718
|
+
/** Synchronous prompt ownership per session (admission before first await). */
|
|
719
|
+
promptOwners = new Set();
|
|
720
|
+
/** At most one detached automatic-title task per durable Console session. */
|
|
721
|
+
titleInFlight = new Set();
|
|
722
|
+
/** Composer thinking selection ("auto" = no explicit override). */
|
|
723
|
+
thinkingSelections = new Map();
|
|
724
|
+
mainlineLeaves = new Map();
|
|
725
|
+
/** Per-sessionId single-flight materialization (create fast-path / readiness fence). */
|
|
726
|
+
sessionInit = new Map();
|
|
727
|
+
/** Resolved model binding after materialization (create may return before this is known). */
|
|
728
|
+
sessionModels = new Map();
|
|
729
|
+
/** SessionIds disposed while materialization was still in flight. */
|
|
730
|
+
disposedSessions = new Set();
|
|
731
|
+
/** Session-less tool definition caches (no AgentSession state). */
|
|
732
|
+
operatorCustomToolsPromise;
|
|
733
|
+
exploreCustomToolsPromise;
|
|
734
|
+
bindings;
|
|
735
|
+
constructor(options) {
|
|
736
|
+
this.options = options;
|
|
737
|
+
this.loader = createOperatorChatResourceLoader();
|
|
738
|
+
this.bindings = options.bindings ?? createDefaultPiSdkBindings();
|
|
739
|
+
}
|
|
740
|
+
/** Absolute path for repo-scoped resource preferences (may be undefined in unit tests). */
|
|
741
|
+
get resourcePreferencesPath() {
|
|
742
|
+
return this.options.resourcePreferencesPath;
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* Load repo-scoped skill auto-invocation preferences (fail closed on malformed).
|
|
746
|
+
* Missing path / missing file → empty prefs (all skills auto-invocation default on).
|
|
747
|
+
*/
|
|
748
|
+
async loadSkillAutoInvocationPreferences() {
|
|
749
|
+
const filePath = this.options.resourcePreferencesPath;
|
|
750
|
+
if (!filePath) {
|
|
751
|
+
const empty = { schemaVersion: 1, skills: {} };
|
|
752
|
+
this.cachedSkillPrefs = empty;
|
|
753
|
+
return empty;
|
|
754
|
+
}
|
|
755
|
+
const prefs = await loadResourcePreferences(filePath);
|
|
756
|
+
this.cachedSkillPrefs = prefs;
|
|
757
|
+
return prefs;
|
|
758
|
+
}
|
|
759
|
+
/**
|
|
760
|
+
* Build resourceLoaderOptions that inject skillsOverride from stored prefs.
|
|
761
|
+
* Skills remain in inventory; only disableModelInvocation is forced for
|
|
762
|
+
* auto-invocation-off preferences. Manual /skill:name is preserved.
|
|
763
|
+
*
|
|
764
|
+
* The override re-reads preferences on EVERY invocation so session.reload()
|
|
765
|
+
* after a PATCH sees the latest resource-preferences.json (not create-time
|
|
766
|
+
* closure state).
|
|
767
|
+
*/
|
|
768
|
+
async buildResourceLoaderOptions(base = {}) {
|
|
769
|
+
return {
|
|
770
|
+
...base,
|
|
771
|
+
skillsOverride: (current) => {
|
|
772
|
+
// Synchronous override surface: use last-known prefs via a sync
|
|
773
|
+
// fail-closed path. Prefer async cache filled by ensurePrefs.
|
|
774
|
+
const prefs = this.cachedSkillPrefs ?? {
|
|
775
|
+
schemaVersion: 1,
|
|
776
|
+
skills: {},
|
|
777
|
+
};
|
|
778
|
+
return {
|
|
779
|
+
skills: applySkillAutoInvocationPreferences(current.skills, prefs),
|
|
780
|
+
diagnostics: current.diagnostics,
|
|
781
|
+
};
|
|
782
|
+
},
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
/** Last successfully loaded skill prefs (sync override + snapshot projection). */
|
|
786
|
+
cachedSkillPrefs;
|
|
787
|
+
/** Refresh cached prefs before materialization / reload so override sees latest. */
|
|
788
|
+
async refreshSkillPrefsCache() {
|
|
789
|
+
const prefs = await this.loadSkillAutoInvocationPreferences();
|
|
790
|
+
this.cachedSkillPrefs = prefs;
|
|
791
|
+
return prefs;
|
|
792
|
+
}
|
|
793
|
+
/**
|
|
794
|
+
* Resolve a skill in the current session inventory by server-issued resourceId.
|
|
795
|
+
* Returns undefined when the id is unknown (caller maps to PI_SKILL_NOT_FOUND).
|
|
796
|
+
*/
|
|
797
|
+
async findSkillByResourceId(sessionId, resourceId) {
|
|
798
|
+
const snapshot = await this.getRuntimeSnapshot(sessionId);
|
|
799
|
+
if (!snapshot)
|
|
800
|
+
return undefined;
|
|
801
|
+
const skill = snapshot.skills.find((entry) => entry.resourceId === resourceId);
|
|
802
|
+
if (!skill)
|
|
803
|
+
return undefined;
|
|
804
|
+
return {
|
|
805
|
+
resourceId: skill.resourceId,
|
|
806
|
+
name: skill.name,
|
|
807
|
+
path: skill.path,
|
|
808
|
+
autoInvocationEnabled: skill.autoInvocationEnabled,
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
get sessionDir() {
|
|
812
|
+
return this.options.sessionDir;
|
|
813
|
+
}
|
|
814
|
+
/** Resolve the Pi agent directory for bounded Console configuration APIs. */
|
|
815
|
+
async getAgentDir() {
|
|
816
|
+
return safeGetAgentDir(this.bindings);
|
|
817
|
+
}
|
|
818
|
+
async testConfiguredModel(model) {
|
|
819
|
+
const { services } = await this.bindings.createServices({
|
|
820
|
+
cwd: this.options.cwd,
|
|
821
|
+
agentDir: await this.getAgentDir(),
|
|
822
|
+
});
|
|
823
|
+
return {
|
|
824
|
+
available: Boolean(this.bindings.resolveModel({
|
|
825
|
+
modelRuntime: services.modelRuntime,
|
|
826
|
+
provider: model.provider,
|
|
827
|
+
modelId: model.modelId,
|
|
828
|
+
})),
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
/** Operator action context used by custom-tool dispatchers (Gate 3). */
|
|
832
|
+
get actionContext() {
|
|
833
|
+
return this.options.actionContext;
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* Resolve the Chat default model descriptor from harness.json
|
|
837
|
+
* executors.pi.MED + the SDK available-model list. Returns undefined when
|
|
838
|
+
* harness is unset or no provider surfaces the model. No hardcoded fallback.
|
|
839
|
+
*
|
|
840
|
+
* Prefer reusing an already-built modelRuntime (same services object as
|
|
841
|
+
* formal materialization) so create does not construct throwaway services.
|
|
842
|
+
*/
|
|
843
|
+
async resolveDefaultModel(modelRuntime) {
|
|
844
|
+
let available;
|
|
845
|
+
try {
|
|
846
|
+
const runtime = modelRuntime ??
|
|
847
|
+
(await this.bindings.createServices({
|
|
848
|
+
cwd: this.options.cwd,
|
|
849
|
+
agentDir: await safeGetAgentDir(this.bindings),
|
|
850
|
+
})).services.modelRuntime;
|
|
851
|
+
available = this.bindings.listAvailableModels({
|
|
852
|
+
modelRuntime: runtime,
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
catch {
|
|
856
|
+
return undefined;
|
|
857
|
+
}
|
|
858
|
+
const descriptor = await resolveDefaultChatModel(this.options.cwd, available);
|
|
859
|
+
if (!descriptor)
|
|
860
|
+
return undefined;
|
|
861
|
+
return {
|
|
862
|
+
provider: descriptor.provider,
|
|
863
|
+
modelId: descriptor.modelId,
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* Fast create: durable SessionManager/JSONL shell first, then single-flight
|
|
868
|
+
* AgentSession materialization in the background. 201 may report
|
|
869
|
+
* `initializing: true`; dependent routes wait on the same promise.
|
|
870
|
+
*/
|
|
871
|
+
async createSession(init) {
|
|
872
|
+
const sessionId = `operator-chat-${randomBytes(12).toString("hex")}`;
|
|
873
|
+
this.disposedSessions.delete(sessionId);
|
|
874
|
+
const sessionManager = await this.bindings.createSessionManager({
|
|
875
|
+
cwd: this.options.cwd,
|
|
876
|
+
sessionDir: this.options.sessionDir,
|
|
877
|
+
sessionId,
|
|
878
|
+
});
|
|
879
|
+
const sessionFile = readSessionManagerFile(sessionManager);
|
|
880
|
+
if (!sessionFile) {
|
|
881
|
+
throw new Error(`chat session shell missing sessionFile for ${sessionId}`);
|
|
882
|
+
}
|
|
883
|
+
this.sessionManagers.set(sessionId, sessionManager);
|
|
884
|
+
this.thinkingSelections.set(sessionId, "auto");
|
|
885
|
+
// Detached single-flight materialization; dependent routes await the same promise.
|
|
886
|
+
void this.startMaterialization(sessionId, {
|
|
887
|
+
sessionManager,
|
|
888
|
+
model: init?.model,
|
|
889
|
+
systemPromptSuffix: init?.systemPromptSuffix,
|
|
890
|
+
// Create path must resolve default model from the SAME services object
|
|
891
|
+
// used for formal materialization (no throwaway createServices).
|
|
892
|
+
resolveDefaultModel: !init?.model,
|
|
893
|
+
});
|
|
894
|
+
return {
|
|
895
|
+
sessionId,
|
|
896
|
+
sessionFile,
|
|
897
|
+
createdAt: new Date().toISOString(),
|
|
898
|
+
model: init?.model,
|
|
899
|
+
activeTools: OPERATOR_CHAT_ACTIVE_TOOL_NAMES(),
|
|
900
|
+
initializing: true,
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
/**
|
|
904
|
+
* Wait for single-flight materialization. Ready sessions resolve immediately;
|
|
905
|
+
* failed materialization throws PiSessionInitFailedError.
|
|
906
|
+
*/
|
|
907
|
+
async ensureSessionReady(sessionId) {
|
|
908
|
+
const state = this.sessionInit.get(sessionId);
|
|
909
|
+
if (!state) {
|
|
910
|
+
if (this.sessions.has(sessionId))
|
|
911
|
+
return;
|
|
912
|
+
throw new Error(`chat session not found: ${sessionId}`);
|
|
913
|
+
}
|
|
914
|
+
if (state.status === "ready")
|
|
915
|
+
return;
|
|
916
|
+
if (state.status === "failed")
|
|
917
|
+
throw state.error;
|
|
918
|
+
await state.promise;
|
|
919
|
+
const next = this.sessionInit.get(sessionId);
|
|
920
|
+
if (next?.status === "failed")
|
|
921
|
+
throw next.error;
|
|
922
|
+
if (this.disposedSessions.has(sessionId) || !this.sessions.has(sessionId)) {
|
|
923
|
+
throw new Error(`chat session not found: ${sessionId}`);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
startMaterialization(sessionId, init) {
|
|
927
|
+
const existing = this.sessionInit.get(sessionId);
|
|
928
|
+
if (existing?.status === "initializing")
|
|
929
|
+
return existing.promise;
|
|
930
|
+
if (existing?.status === "ready")
|
|
931
|
+
return Promise.resolve();
|
|
932
|
+
if (existing?.status === "failed")
|
|
933
|
+
return Promise.reject(existing.error);
|
|
934
|
+
const promise = this.materializeSession(sessionId, init)
|
|
935
|
+
.then(() => {
|
|
936
|
+
if (this.disposedSessions.has(sessionId)) {
|
|
937
|
+
// Late materialization after delete: never register ready state.
|
|
938
|
+
this.sessionInit.delete(sessionId);
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
this.sessionInit.set(sessionId, { status: "ready" });
|
|
942
|
+
})
|
|
943
|
+
.catch((error) => {
|
|
944
|
+
const failed = new PiSessionInitFailedError(error instanceof Error ? error.message : String(error));
|
|
945
|
+
if (!this.disposedSessions.has(sessionId)) {
|
|
946
|
+
this.sessionInit.set(sessionId, { status: "failed", error: failed });
|
|
947
|
+
}
|
|
948
|
+
else {
|
|
949
|
+
this.sessionInit.delete(sessionId);
|
|
950
|
+
}
|
|
951
|
+
// Observe rejection so detached create path has no unhandled rejection.
|
|
952
|
+
process.stderr.write(`[console] chat session init failed (${sessionId}): ${failed.message}\n`);
|
|
953
|
+
});
|
|
954
|
+
this.sessionInit.set(sessionId, { status: "initializing", promise });
|
|
955
|
+
return promise;
|
|
956
|
+
}
|
|
957
|
+
async materializeSession(sessionId, init) {
|
|
958
|
+
if (this.disposedSessions.has(sessionId))
|
|
959
|
+
return;
|
|
960
|
+
const agentDir = await safeGetAgentDir(this.bindings);
|
|
961
|
+
const appendSystemPrompt = [
|
|
962
|
+
OPERATOR_CHAT_SYSTEM_PROMPT_BASE,
|
|
963
|
+
...(init.systemPromptSuffix ? [init.systemPromptSuffix] : []),
|
|
964
|
+
];
|
|
965
|
+
// Single services construction for formal materialization (+ optional
|
|
966
|
+
// default-model resolve from the same modelRuntime). Repo-scoped skill
|
|
967
|
+
// auto-invocation prefs inject skillsOverride without touching SKILL.md.
|
|
968
|
+
await this.refreshSkillPrefsCache().catch(() => undefined);
|
|
969
|
+
const { services } = await this.bindings.createServices({
|
|
970
|
+
cwd: this.options.cwd,
|
|
971
|
+
agentDir,
|
|
972
|
+
resourceLoaderOptions: await this.buildResourceLoaderOptions({
|
|
973
|
+
appendSystemPrompt,
|
|
974
|
+
}),
|
|
975
|
+
});
|
|
976
|
+
if (this.disposedSessions.has(sessionId))
|
|
977
|
+
return;
|
|
978
|
+
const modelRuntime = services.modelRuntime;
|
|
979
|
+
let model = init.model;
|
|
980
|
+
if (!model && init.resolveDefaultModel) {
|
|
981
|
+
model = await this.resolveDefaultModel(modelRuntime);
|
|
982
|
+
}
|
|
983
|
+
const resolvedModel = model
|
|
984
|
+
? (this.bindings.resolveModel({
|
|
985
|
+
modelRuntime,
|
|
986
|
+
provider: model.provider,
|
|
987
|
+
modelId: model.modelId,
|
|
988
|
+
}) ?? undefined)
|
|
989
|
+
: undefined;
|
|
990
|
+
assertNoWriteToolInList(OPERATOR_CHAT_ALLOWED_TOOLS);
|
|
991
|
+
const customTools = await this.loadCustomTools();
|
|
992
|
+
if (this.disposedSessions.has(sessionId))
|
|
993
|
+
return;
|
|
994
|
+
// pi-web parity: do NOT pass `tools`. A closed allowlist becomes
|
|
995
|
+
// SDK allowedToolNames and filters extension MCP tools out of the
|
|
996
|
+
// registry before Gate 2 can re-add them. Operator/safe customTools
|
|
997
|
+
// still register via customTools; non-Pi write channels stay blocked
|
|
998
|
+
// by excludeTools. After create, bindExtensions emits session_start
|
|
999
|
+
// so MCP adapters can register, then pin the active set.
|
|
1000
|
+
const { session } = await this.bindings.createSessionFromServices({
|
|
1001
|
+
services,
|
|
1002
|
+
sessionManager: init.sessionManager,
|
|
1003
|
+
excludeTools: [...OPERATOR_CHAT_SESSION_EXCLUDE_TOOLS],
|
|
1004
|
+
...(customTools.length > 0 ? { customTools } : {}),
|
|
1005
|
+
...(resolvedModel ? { model: resolvedModel } : {}),
|
|
1006
|
+
});
|
|
1007
|
+
if (this.disposedSessions.has(sessionId)) {
|
|
1008
|
+
try {
|
|
1009
|
+
session.dispose();
|
|
1010
|
+
}
|
|
1011
|
+
catch {
|
|
1012
|
+
// ignore late dispose cleanup
|
|
1013
|
+
}
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
await bindSessionExtensions(session);
|
|
1017
|
+
pinOperatorChatTools(session);
|
|
1018
|
+
// Register then re-check dispose race: a concurrent dispose must win.
|
|
1019
|
+
this.sessions.set(sessionId, session);
|
|
1020
|
+
this.sessionManagers.set(sessionId, init.sessionManager);
|
|
1021
|
+
this.modelRuntimes.set(sessionId, modelRuntime);
|
|
1022
|
+
this.sessionModels.set(sessionId, model);
|
|
1023
|
+
this.serviceScopes.set(sessionId, {
|
|
1024
|
+
resourceLoader: services.resourceLoader,
|
|
1025
|
+
settingsManager: services.settingsManager,
|
|
1026
|
+
});
|
|
1027
|
+
this.revisions.set(sessionId, 1);
|
|
1028
|
+
if (!this.thinkingSelections.has(sessionId)) {
|
|
1029
|
+
this.thinkingSelections.set(sessionId, "auto");
|
|
1030
|
+
}
|
|
1031
|
+
if (this.disposedSessions.has(sessionId)) {
|
|
1032
|
+
try {
|
|
1033
|
+
session.dispose();
|
|
1034
|
+
}
|
|
1035
|
+
catch {
|
|
1036
|
+
// ignore
|
|
1037
|
+
}
|
|
1038
|
+
this.sessions.delete(sessionId);
|
|
1039
|
+
this.sessionManagers.delete(sessionId);
|
|
1040
|
+
this.modelRuntimes.delete(sessionId);
|
|
1041
|
+
this.sessionModels.delete(sessionId);
|
|
1042
|
+
this.serviceScopes.delete(sessionId);
|
|
1043
|
+
this.revisions.delete(sessionId);
|
|
1044
|
+
this.thinkingSelections.delete(sessionId);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
/** Cached session-less custom tool definitions (operator + explore). */
|
|
1048
|
+
async loadCustomTools() {
|
|
1049
|
+
const [operatorCustomTools, exploreCustomTools] = await Promise.all([
|
|
1050
|
+
this.loadOperatorCustomTools(),
|
|
1051
|
+
this.loadExploreCustomTools(),
|
|
1052
|
+
]);
|
|
1053
|
+
return [...operatorCustomTools, ...exploreCustomTools];
|
|
1054
|
+
}
|
|
1055
|
+
loadOperatorCustomTools() {
|
|
1056
|
+
if (!this.options.actionContext)
|
|
1057
|
+
return Promise.resolve([]);
|
|
1058
|
+
if (!this.operatorCustomToolsPromise) {
|
|
1059
|
+
this.operatorCustomToolsPromise = buildCustomOperatorTools({
|
|
1060
|
+
actionContext: this.options.actionContext,
|
|
1061
|
+
}).catch((error) => {
|
|
1062
|
+
process.stderr.write(`[console] chat custom-tools build failed: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
1063
|
+
// Allow a later create/reopen to retry after a transient failure.
|
|
1064
|
+
this.operatorCustomToolsPromise = undefined;
|
|
1065
|
+
return [];
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
return this.operatorCustomToolsPromise;
|
|
1069
|
+
}
|
|
1070
|
+
loadExploreCustomTools() {
|
|
1071
|
+
if (!this.exploreCustomToolsPromise) {
|
|
1072
|
+
this.exploreCustomToolsPromise = buildExploreCustomTools({
|
|
1073
|
+
repoRoot: this.options.cwd,
|
|
1074
|
+
}).catch((error) => {
|
|
1075
|
+
process.stderr.write(`[console] chat explore-tools build failed: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
1076
|
+
this.exploreCustomToolsPromise = undefined;
|
|
1077
|
+
return [];
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
1080
|
+
return this.exploreCustomToolsPromise;
|
|
1081
|
+
}
|
|
1082
|
+
async forkSession(init) {
|
|
1083
|
+
if (!this.bindings.forkSessionManager)
|
|
1084
|
+
throw new Error("SESSION_FORK_UNSUPPORTED");
|
|
1085
|
+
const sessionId = `operator-chat-${randomBytes(12).toString("hex")}`;
|
|
1086
|
+
const sessionManager = await this.bindings.forkSessionManager({
|
|
1087
|
+
sourcePath: init.sourceSessionFile,
|
|
1088
|
+
targetCwd: this.options.cwd,
|
|
1089
|
+
targetSessionDir: this.options.sessionDir,
|
|
1090
|
+
targetSessionId: sessionId,
|
|
1091
|
+
});
|
|
1092
|
+
const built = await this.buildSessionWithServices({
|
|
1093
|
+
sessionManager,
|
|
1094
|
+
model: init.model,
|
|
1095
|
+
});
|
|
1096
|
+
built.session.setActiveToolsByName(computeOperatorChatActiveToolNames(built.session));
|
|
1097
|
+
this.sessions.set(sessionId, built.session);
|
|
1098
|
+
this.sessionManagers.set(sessionId, sessionManager);
|
|
1099
|
+
this.modelRuntimes.set(sessionId, built.modelRuntime);
|
|
1100
|
+
this.sessionModels.set(sessionId, init.model);
|
|
1101
|
+
this.serviceScopes.set(sessionId, built.services);
|
|
1102
|
+
this.revisions.set(sessionId, 1);
|
|
1103
|
+
this.thinkingSelections.set(sessionId, "auto");
|
|
1104
|
+
this.sessionInit.set(sessionId, { status: "ready" });
|
|
1105
|
+
return {
|
|
1106
|
+
sessionId,
|
|
1107
|
+
sessionFile: built.session.sessionFile,
|
|
1108
|
+
createdAt: new Date().toISOString(),
|
|
1109
|
+
model: init.model,
|
|
1110
|
+
activeTools: built.session.getActiveToolNames(),
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
async branchSession(input) {
|
|
1114
|
+
const session = this.sessions.get(input.sessionId);
|
|
1115
|
+
const manager = this.sessionManagers.get(input.sessionId);
|
|
1116
|
+
if (!session || !manager)
|
|
1117
|
+
throw new Error(`chat session not active: ${input.sessionId}`);
|
|
1118
|
+
if (session.isStreaming === true || session.isIdle === false)
|
|
1119
|
+
throw new Error("CHAT_TURN_ACTIVE");
|
|
1120
|
+
if (!manager.getEntry?.(input.branchFromEntryId))
|
|
1121
|
+
throw new Error("BRANCH_ENTRY_NOT_FOUND");
|
|
1122
|
+
if (!this.mainlineLeaves.has(input.sessionId))
|
|
1123
|
+
this.mainlineLeaves.set(input.sessionId, manager.getLeafId?.());
|
|
1124
|
+
const entry = input.summary
|
|
1125
|
+
? manager.branchWithSummary?.(input.branchFromEntryId, input.summary)
|
|
1126
|
+
: (manager.branch(input.branchFromEntryId), undefined);
|
|
1127
|
+
return {
|
|
1128
|
+
branchedFromEntryId: input.branchFromEntryId,
|
|
1129
|
+
...(entry?.id ? { branchSummaryEntryId: entry.id } : {}),
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
switchToMainline(sessionId) {
|
|
1133
|
+
const manager = this.sessionManagers.get(sessionId);
|
|
1134
|
+
if (!manager)
|
|
1135
|
+
throw new Error(`chat session not active: ${sessionId}`);
|
|
1136
|
+
manager.branch(this.mainlineLeaves.get(sessionId));
|
|
1137
|
+
this.mainlineLeaves.delete(sessionId);
|
|
1138
|
+
}
|
|
1139
|
+
getBranchContext(sessionId) {
|
|
1140
|
+
const manager = this.sessionManagers.get(sessionId);
|
|
1141
|
+
if (!manager)
|
|
1142
|
+
throw new Error(`chat session not active: ${sessionId}`);
|
|
1143
|
+
return {
|
|
1144
|
+
leafId: manager.getLeafId?.() ?? null,
|
|
1145
|
+
entries: manager.getBranch?.() ?? [],
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
async listModels(sessionId) {
|
|
1149
|
+
await this.ensureSessionReady(sessionId);
|
|
1150
|
+
const modelRuntime = this.modelRuntimes.get(sessionId);
|
|
1151
|
+
if (!this.sessions.has(sessionId) || !modelRuntime)
|
|
1152
|
+
throw new Error(`chat session not active: ${sessionId}`);
|
|
1153
|
+
return this.bindings.listAvailableModels({ modelRuntime });
|
|
1154
|
+
}
|
|
1155
|
+
async applyModel(sessionId, model) {
|
|
1156
|
+
await this.ensureSessionReady(sessionId);
|
|
1157
|
+
const session = this.sessions.get(sessionId);
|
|
1158
|
+
const modelRuntime = this.modelRuntimes.get(sessionId);
|
|
1159
|
+
if (!session || !modelRuntime)
|
|
1160
|
+
throw new Error(`chat session not active: ${sessionId}`);
|
|
1161
|
+
if (!session.setModel)
|
|
1162
|
+
throw new Error("MODEL_SWITCH_UNSUPPORTED");
|
|
1163
|
+
const resolved = this.bindings.resolveModel({
|
|
1164
|
+
modelRuntime,
|
|
1165
|
+
provider: model.provider,
|
|
1166
|
+
modelId: model.modelId,
|
|
1167
|
+
});
|
|
1168
|
+
if (!resolved)
|
|
1169
|
+
throw new Error("MODEL_NOT_AVAILABLE");
|
|
1170
|
+
await session.setModel(resolved);
|
|
1171
|
+
this.bumpRevision(sessionId);
|
|
1172
|
+
return {
|
|
1173
|
+
provider: session.model?.provider ?? model.provider,
|
|
1174
|
+
modelId: session.model?.id ?? session.model?.modelId ?? model.modelId,
|
|
1175
|
+
};
|
|
1176
|
+
}
|
|
1177
|
+
/**
|
|
1178
|
+
* Apply a thinking-level selection. `auto` records the selection WITHOUT
|
|
1179
|
+
* sending any explicit override (the SDK keeps its own default); an explicit
|
|
1180
|
+
* level calls `session.setThinkingLevel`. Returns the effective value.
|
|
1181
|
+
*/
|
|
1182
|
+
applyThinkingLevel(sessionId, level) {
|
|
1183
|
+
const state = this.sessionInit.get(sessionId);
|
|
1184
|
+
if (state?.status === "failed")
|
|
1185
|
+
throw state.error;
|
|
1186
|
+
if (state?.status === "initializing") {
|
|
1187
|
+
throw new Error(`chat session not active: ${sessionId}`);
|
|
1188
|
+
}
|
|
1189
|
+
const session = this.sessions.get(sessionId);
|
|
1190
|
+
if (!session)
|
|
1191
|
+
throw new Error(`chat session not active: ${sessionId}`);
|
|
1192
|
+
if (level === "auto") {
|
|
1193
|
+
this.thinkingSelections.set(sessionId, "auto");
|
|
1194
|
+
this.bumpRevision(sessionId);
|
|
1195
|
+
return "auto";
|
|
1196
|
+
}
|
|
1197
|
+
if (!THINKING_LEVELS.includes(level) || !session.setThinkingLevel)
|
|
1198
|
+
throw new Error("THINKING_LEVEL_UNSUPPORTED");
|
|
1199
|
+
session.setThinkingLevel(level);
|
|
1200
|
+
this.thinkingSelections.set(sessionId, level);
|
|
1201
|
+
this.bumpRevision(sessionId);
|
|
1202
|
+
return session.thinkingLevel ?? level;
|
|
1203
|
+
}
|
|
1204
|
+
getRuntimeSelection(sessionId) {
|
|
1205
|
+
const session = this.sessions.get(sessionId);
|
|
1206
|
+
if (!session)
|
|
1207
|
+
return { activeTools: [] };
|
|
1208
|
+
const stored = this.sessionModels.get(sessionId);
|
|
1209
|
+
const provider = session.model?.provider ?? stored?.provider;
|
|
1210
|
+
const modelId = session.model?.id ?? session.model?.modelId ?? stored?.modelId;
|
|
1211
|
+
return {
|
|
1212
|
+
...(provider && modelId ? { model: { provider, modelId } } : {}),
|
|
1213
|
+
...(session.thinkingLevel
|
|
1214
|
+
? { thinkingLevel: session.thinkingLevel }
|
|
1215
|
+
: {}),
|
|
1216
|
+
activeTools: session.getActiveToolNames(),
|
|
1217
|
+
};
|
|
1218
|
+
}
|
|
1219
|
+
bumpRevision(sessionId) {
|
|
1220
|
+
this.revisions.set(sessionId, (this.revisions.get(sessionId) ?? 0) + 1);
|
|
1221
|
+
}
|
|
1222
|
+
/**
|
|
1223
|
+
* Resolve the RAW system prompt actually injected into the Pi session
|
|
1224
|
+
* (design §2.2 / AC-03). Fallback chain: agent.state.systemPrompt →
|
|
1225
|
+
* session.systemPrompt getter → ResourceLoader.getSystemPrompt() → the
|
|
1226
|
+
* composed operator-chat base prompt. Never returns empty.
|
|
1227
|
+
*/
|
|
1228
|
+
async getRuntimeSystemPrompt(sessionId) {
|
|
1229
|
+
await this.ensureSessionReady(sessionId).catch(() => undefined);
|
|
1230
|
+
const session = this.sessions.get(sessionId);
|
|
1231
|
+
if (!session)
|
|
1232
|
+
return composeOperatorChatSystemPrompt({});
|
|
1233
|
+
const fromState = session.state?.systemPrompt;
|
|
1234
|
+
const fromGetter = session.systemPrompt;
|
|
1235
|
+
const loader = this.serviceScopes.get(sessionId)?.resourceLoader;
|
|
1236
|
+
return (fromState ||
|
|
1237
|
+
fromGetter ||
|
|
1238
|
+
loader?.getSystemPrompt?.() ||
|
|
1239
|
+
composeOperatorChatSystemPrompt({}));
|
|
1240
|
+
}
|
|
1241
|
+
/**
|
|
1242
|
+
* Read the RAW system prompt for the snapshot, converting any projection
|
|
1243
|
+
* failure into the structured critical error (RF-02): a broken system prompt
|
|
1244
|
+
* source must fail the whole snapshot with 503 PI_RUNTIME_UNAVAILABLE rather
|
|
1245
|
+
* than silently fabricate prompt text.
|
|
1246
|
+
*/
|
|
1247
|
+
async readRawSystemPrompt(sessionId) {
|
|
1248
|
+
try {
|
|
1249
|
+
return await this.getRuntimeSystemPrompt(sessionId);
|
|
1250
|
+
}
|
|
1251
|
+
catch (error) {
|
|
1252
|
+
throw new RuntimeSnapshotUnavailableError(`system prompt projection failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
/**
|
|
1256
|
+
* Official Pi session reload (AC-05 / AC-06). Rejects with a structured
|
|
1257
|
+
* code while the session is busy (streaming / compacting / bash in flight /
|
|
1258
|
+
* duplicate reload) — never aborts, never silently queues.
|
|
1259
|
+
*/
|
|
1260
|
+
async reloadSession(sessionId) {
|
|
1261
|
+
try {
|
|
1262
|
+
await this.ensureSessionReady(sessionId);
|
|
1263
|
+
}
|
|
1264
|
+
catch (error) {
|
|
1265
|
+
if (error instanceof PiSessionInitFailedError) {
|
|
1266
|
+
return { ok: false, code: error.code, message: error.message };
|
|
1267
|
+
}
|
|
1268
|
+
return {
|
|
1269
|
+
ok: false,
|
|
1270
|
+
code: "PI_SESSION_NOT_FOUND",
|
|
1271
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
const session = this.sessions.get(sessionId);
|
|
1275
|
+
if (!session) {
|
|
1276
|
+
return {
|
|
1277
|
+
ok: false,
|
|
1278
|
+
code: "PI_SESSION_NOT_FOUND",
|
|
1279
|
+
message: `chat session not active: ${sessionId}`,
|
|
1280
|
+
};
|
|
1281
|
+
}
|
|
1282
|
+
// RF2-03: the per-sessionId in-flight lock is checked FIRST — before the
|
|
1283
|
+
// streaming/compacting/bash/support checks. JS runs check-then-add in one
|
|
1284
|
+
// synchronous segment, so two concurrent calls cannot both pass; the lock
|
|
1285
|
+
// is released in `finally` on BOTH the success and the error path, and
|
|
1286
|
+
// different sessions never block each other. Priority matters: the first
|
|
1287
|
+
// reload() may synchronously flip isIdle/isCompacting/isBashRunning/
|
|
1288
|
+
// hasPendingBashMessages to busy, and a second concurrent call must still
|
|
1289
|
+
// report the reload-in-flight busy (not the streaming/compacting one).
|
|
1290
|
+
if (this.reloadInFlight.has(sessionId)) {
|
|
1291
|
+
return {
|
|
1292
|
+
ok: false,
|
|
1293
|
+
code: "PI_SESSION_BUSY",
|
|
1294
|
+
message: "session is busy (reload in flight); reload rejected without abort",
|
|
1295
|
+
};
|
|
1296
|
+
}
|
|
1297
|
+
const busyReasons = [];
|
|
1298
|
+
if (session.isStreaming === true || session.isIdle === false)
|
|
1299
|
+
busyReasons.push("streaming");
|
|
1300
|
+
if (session.isCompacting === true)
|
|
1301
|
+
busyReasons.push("compacting");
|
|
1302
|
+
if (session.isBashRunning === true)
|
|
1303
|
+
busyReasons.push("bash running");
|
|
1304
|
+
if (session.hasPendingBashMessages === true)
|
|
1305
|
+
busyReasons.push("bash messages pending");
|
|
1306
|
+
if (busyReasons.length > 0) {
|
|
1307
|
+
return {
|
|
1308
|
+
ok: false,
|
|
1309
|
+
code: "PI_SESSION_BUSY",
|
|
1310
|
+
message: `session is busy (${busyReasons.join(", ")}); reload rejected without abort`,
|
|
1311
|
+
};
|
|
1312
|
+
}
|
|
1313
|
+
if (typeof session.reload !== "function") {
|
|
1314
|
+
return {
|
|
1315
|
+
ok: false,
|
|
1316
|
+
code: "PI_RELOAD_FAILED",
|
|
1317
|
+
message: "this Pi runtime does not support session.reload()",
|
|
1318
|
+
};
|
|
1319
|
+
}
|
|
1320
|
+
this.reloadInFlight.add(sessionId);
|
|
1321
|
+
try {
|
|
1322
|
+
// Refresh prefs cache so skillsOverride applied during reload sees
|
|
1323
|
+
// the latest resource-preferences.json written by PATCH.
|
|
1324
|
+
await this.refreshSkillPrefsCache().catch(() => undefined);
|
|
1325
|
+
await session.reload();
|
|
1326
|
+
}
|
|
1327
|
+
catch (error) {
|
|
1328
|
+
return {
|
|
1329
|
+
ok: false,
|
|
1330
|
+
code: "PI_RELOAD_FAILED",
|
|
1331
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1334
|
+
finally {
|
|
1335
|
+
this.reloadInFlight.delete(sessionId);
|
|
1336
|
+
}
|
|
1337
|
+
this.bumpRevision(sessionId);
|
|
1338
|
+
return { ok: true, revision: this.revisions.get(sessionId) ?? 0 };
|
|
1339
|
+
}
|
|
1340
|
+
/**
|
|
1341
|
+
* Project the unified live runtime snapshot (design §2.2). The current Pi
|
|
1342
|
+
* session is the single source of truth: real system prompt, real model /
|
|
1343
|
+
* thinking selection (auto + effective), the session modelRuntime model
|
|
1344
|
+
* list, and the session ResourceLoader inventories. Undefined when the
|
|
1345
|
+
* session is not active — callers fail closed instead of fabricating data.
|
|
1346
|
+
*/
|
|
1347
|
+
/**
|
|
1348
|
+
* ConsolePiRuntime.listSlashCommands
|
|
1349
|
+
* Browser-safe slash command projection for UI-11.
|
|
1350
|
+
* Sources: current Session extension commands, prompt templates, skills.
|
|
1351
|
+
* Never returns prompt/skill file bodies.
|
|
1352
|
+
*/
|
|
1353
|
+
async listSlashCommands(sessionId) {
|
|
1354
|
+
const scrub = (value) => {
|
|
1355
|
+
const raw = String(value ?? "");
|
|
1356
|
+
return raw
|
|
1357
|
+
.replace(/\/Users\/[^\s]+/gi, "~")
|
|
1358
|
+
.replace(/[A-Za-z]:\\Users\\[^\s]+/gi, "~")
|
|
1359
|
+
.slice(0, 240);
|
|
1360
|
+
};
|
|
1361
|
+
// Fail soft at the HTTP layer: bubble readiness errors so routes can attach
|
|
1362
|
+
// a compact warning while the UI keeps local Operator/Pi Web commands.
|
|
1363
|
+
await this.ensureSessionReady(sessionId);
|
|
1364
|
+
const session = this.sessions.get(sessionId);
|
|
1365
|
+
if (!session)
|
|
1366
|
+
return { commands: [] };
|
|
1367
|
+
// Same-name conflict priority (AC-3): extension > skill > prompt.
|
|
1368
|
+
// Enum order must not decide the winner — prompt-before-skill still loses.
|
|
1369
|
+
const SOURCE_RANK = {
|
|
1370
|
+
extension: 0,
|
|
1371
|
+
skill: 1,
|
|
1372
|
+
prompt: 2,
|
|
1373
|
+
};
|
|
1374
|
+
const byKey = new Map();
|
|
1375
|
+
const sourceWarnings = [];
|
|
1376
|
+
const push = (command, label, description, source) => {
|
|
1377
|
+
const name = command.startsWith("/") ? command : `/${command}`;
|
|
1378
|
+
const key = name.toLowerCase();
|
|
1379
|
+
if (!key || key === "/")
|
|
1380
|
+
return;
|
|
1381
|
+
const next = {
|
|
1382
|
+
command: name,
|
|
1383
|
+
label: (label || name).slice(0, 80),
|
|
1384
|
+
description: scrub(description),
|
|
1385
|
+
source,
|
|
1386
|
+
};
|
|
1387
|
+
const existing = byKey.get(key);
|
|
1388
|
+
if (!existing || SOURCE_RANK[source] < SOURCE_RANK[existing.source]) {
|
|
1389
|
+
byKey.set(key, next);
|
|
1390
|
+
}
|
|
1391
|
+
};
|
|
1392
|
+
try {
|
|
1393
|
+
const cmds = session.extensionRunner?.getRegisteredCommands?.() ?? [];
|
|
1394
|
+
for (const cmd of cmds) {
|
|
1395
|
+
const name = cmd.invocationName || cmd.name;
|
|
1396
|
+
if (!name)
|
|
1397
|
+
continue;
|
|
1398
|
+
push(String(name), String(cmd.name || name), String(cmd.description || ""), "extension");
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
catch (error) {
|
|
1402
|
+
// Source isolation: keep other sources; surface compact warning (AC-3).
|
|
1403
|
+
sourceWarnings.push(`extension: ${scrub(error instanceof Error ? error.message : String(error))}`);
|
|
1404
|
+
}
|
|
1405
|
+
try {
|
|
1406
|
+
// Enumerate prompts before skills on purpose: priority must still let skill win.
|
|
1407
|
+
for (const tpl of session.promptTemplates ?? []) {
|
|
1408
|
+
if (!tpl?.name)
|
|
1409
|
+
continue;
|
|
1410
|
+
push(String(tpl.name), String(tpl.name), String(tpl.description || ""), "prompt");
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
catch (error) {
|
|
1414
|
+
sourceWarnings.push(`prompt: ${scrub(error instanceof Error ? error.message : String(error))}`);
|
|
1415
|
+
}
|
|
1416
|
+
try {
|
|
1417
|
+
const services = this.serviceScopes.get(sessionId);
|
|
1418
|
+
const resourceLoader = services?.resourceLoader;
|
|
1419
|
+
const skills = resourceLoader?.getSkills?.()?.skills ?? [];
|
|
1420
|
+
for (const skill of skills) {
|
|
1421
|
+
if (!skill?.name)
|
|
1422
|
+
continue;
|
|
1423
|
+
const command = skill.name.startsWith("skill:")
|
|
1424
|
+
? `/${skill.name}`
|
|
1425
|
+
: `/skill:${skill.name}`;
|
|
1426
|
+
push(command, skill.name, String(skill.description || ""), "skill");
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
catch (error) {
|
|
1430
|
+
sourceWarnings.push(`skill: ${scrub(error instanceof Error ? error.message : String(error))}`);
|
|
1431
|
+
}
|
|
1432
|
+
const warning = sourceWarnings.length > 0
|
|
1433
|
+
? sourceWarnings.join("; ").slice(0, 160)
|
|
1434
|
+
: undefined;
|
|
1435
|
+
return {
|
|
1436
|
+
commands: [...byKey.values()],
|
|
1437
|
+
...(warning ? { warning } : {}),
|
|
1438
|
+
};
|
|
1439
|
+
}
|
|
1440
|
+
async getRuntimeSnapshot(sessionId, options) {
|
|
1441
|
+
try {
|
|
1442
|
+
await this.ensureSessionReady(sessionId);
|
|
1443
|
+
}
|
|
1444
|
+
catch (error) {
|
|
1445
|
+
if (error instanceof PiSessionInitFailedError)
|
|
1446
|
+
throw error;
|
|
1447
|
+
// Inactive / unknown session: fail closed with undefined (no fabricated snapshot).
|
|
1448
|
+
return undefined;
|
|
1449
|
+
}
|
|
1450
|
+
const session = this.sessions.get(sessionId);
|
|
1451
|
+
if (!session)
|
|
1452
|
+
return undefined;
|
|
1453
|
+
const services = this.serviceScopes.get(sessionId);
|
|
1454
|
+
const modelRuntime = this.modelRuntimes.get(sessionId);
|
|
1455
|
+
const resourceLoader = services?.resourceLoader;
|
|
1456
|
+
const settingsManager = services?.settingsManager;
|
|
1457
|
+
const thinkingSelection = this.thinkingSelections.get(sessionId) ?? "auto";
|
|
1458
|
+
const effectiveThinkingLevel = session.thinkingLevel ??
|
|
1459
|
+
settingsManager?.getDefaultThinkingLevel?.() ??
|
|
1460
|
+
(thinkingSelection === "auto" ? undefined : thinkingSelection);
|
|
1461
|
+
const rawSystemPrompt = await this.readRawSystemPrompt(sessionId);
|
|
1462
|
+
// Models come from the session modelRuntime. A model-enumeration failure
|
|
1463
|
+
// is a CRITICAL projection failure (RF-02): it throws
|
|
1464
|
+
// RuntimeSnapshotUnavailableError so consuming routes return 503
|
|
1465
|
+
// PI_RUNTIME_UNAVAILABLE — never a fabricated empty model list.
|
|
1466
|
+
let models = [];
|
|
1467
|
+
if (modelRuntime) {
|
|
1468
|
+
try {
|
|
1469
|
+
models = this.bindings
|
|
1470
|
+
.listAvailableModels({ modelRuntime })
|
|
1471
|
+
.map((entry) => ({
|
|
1472
|
+
provider: entry.provider,
|
|
1473
|
+
id: entry.id,
|
|
1474
|
+
...(entry.name ? { name: entry.name } : {}),
|
|
1475
|
+
hasCredentials: entry.hasCredentials,
|
|
1476
|
+
}));
|
|
1477
|
+
}
|
|
1478
|
+
catch (error) {
|
|
1479
|
+
throw new RuntimeSnapshotUnavailableError(`model enumeration failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
// Thinking options: auto is always offered; explicit levels only when the
|
|
1483
|
+
// runtime session supports setThinkingLevel (xhigh/max ship with the SDK
|
|
1484
|
+
// and therefore surface only when this runtime provides them).
|
|
1485
|
+
const supportedLevels = typeof session.setThinkingLevel === "function"
|
|
1486
|
+
? [...THINKING_LEVELS]
|
|
1487
|
+
: [];
|
|
1488
|
+
const thinkingLevels = [
|
|
1489
|
+
{ value: "auto" },
|
|
1490
|
+
...supportedLevels.map((value) => ({ value })),
|
|
1491
|
+
];
|
|
1492
|
+
// Resource inventories from the session ResourceLoader (ADR 0012 open
|
|
1493
|
+
// surface). Missing loader → real empty lists, never fabricated entries.
|
|
1494
|
+
// Each subface is isolated (RF-02): a throwing subface keeps the other
|
|
1495
|
+
// inventories AND surfaces a structured retryable diagnostic instead of
|
|
1496
|
+
// silently dropping the whole resource projection.
|
|
1497
|
+
const contextFiles = [];
|
|
1498
|
+
const skills = [];
|
|
1499
|
+
const extensions = [];
|
|
1500
|
+
const diagnostics = [];
|
|
1501
|
+
const resolvedResources = [];
|
|
1502
|
+
const resourceFailure = (subface, error) => {
|
|
1503
|
+
diagnostics.push({
|
|
1504
|
+
type: "error",
|
|
1505
|
+
code: "RESOURCE_LOAD_FAILED",
|
|
1506
|
+
message: `${subface} inventory failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
1507
|
+
retryable: true,
|
|
1508
|
+
});
|
|
1509
|
+
};
|
|
1510
|
+
if (resourceLoader) {
|
|
1511
|
+
try {
|
|
1512
|
+
const agentsFiles = resourceLoader.getAgentsFiles?.();
|
|
1513
|
+
for (const file of agentsFiles?.agentsFiles ?? []) {
|
|
1514
|
+
if (!file.path)
|
|
1515
|
+
continue;
|
|
1516
|
+
contextFiles.push({
|
|
1517
|
+
path: file.path,
|
|
1518
|
+
characters: file.content?.length ?? 0,
|
|
1519
|
+
});
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
catch (error) {
|
|
1523
|
+
resourceFailure("context files", error);
|
|
1524
|
+
}
|
|
1525
|
+
// Preferences are the source of truth for UI auto-invocation state;
|
|
1526
|
+
// loader disableModelInvocation may also reflect frontmatter. Prefer
|
|
1527
|
+
// explicit preference (default on) when projecting autoInvocationEnabled.
|
|
1528
|
+
let skillPrefs = { schemaVersion: 1, skills: {} };
|
|
1529
|
+
try {
|
|
1530
|
+
skillPrefs = await this.loadSkillAutoInvocationPreferences();
|
|
1531
|
+
}
|
|
1532
|
+
catch (error) {
|
|
1533
|
+
diagnostics.push({
|
|
1534
|
+
type: "error",
|
|
1535
|
+
code: "PI_RESOURCE_PREFERENCES_MALFORMED",
|
|
1536
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1537
|
+
retryable: true,
|
|
1538
|
+
});
|
|
1539
|
+
}
|
|
1540
|
+
try {
|
|
1541
|
+
const skillsResult = resourceLoader.getSkills?.();
|
|
1542
|
+
for (const skill of skillsResult?.skills ?? []) {
|
|
1543
|
+
const resourceId = skillResourceId({
|
|
1544
|
+
name: skill.name,
|
|
1545
|
+
filePath: skill.filePath,
|
|
1546
|
+
});
|
|
1547
|
+
// Preference off always means autoInvocationEnabled=false even if
|
|
1548
|
+
// loader already applied disableModelInvocation. Frontmatter-only
|
|
1549
|
+
// disable (no preference) also surfaces as autoInvocationEnabled=false.
|
|
1550
|
+
const prefEnabled = isAutoInvocationEnabled(skillPrefs, resourceId);
|
|
1551
|
+
const autoInvocationEnabled = prefEnabled && skill.disableModelInvocation !== true;
|
|
1552
|
+
skills.push({
|
|
1553
|
+
name: skill.name,
|
|
1554
|
+
description: skill.description ?? "",
|
|
1555
|
+
path: skill.filePath,
|
|
1556
|
+
...(skill.baseDir ? { baseDir: skill.baseDir } : {}),
|
|
1557
|
+
scope: skill.sourceInfo?.scope ?? "unknown",
|
|
1558
|
+
source: skill.sourceInfo?.source ?? "",
|
|
1559
|
+
resourceId,
|
|
1560
|
+
autoInvocationEnabled,
|
|
1561
|
+
diagnostics: [],
|
|
1562
|
+
});
|
|
1563
|
+
resolvedResources.push({
|
|
1564
|
+
type: "skill",
|
|
1565
|
+
name: skill.name,
|
|
1566
|
+
path: skill.filePath,
|
|
1567
|
+
source: skill.sourceInfo?.source ?? "",
|
|
1568
|
+
scope: skill.sourceInfo?.scope ?? "unknown",
|
|
1569
|
+
});
|
|
1570
|
+
}
|
|
1571
|
+
for (const diag of skillsResult?.diagnostics ?? []) {
|
|
1572
|
+
if (!diag?.message)
|
|
1573
|
+
continue;
|
|
1574
|
+
diagnostics.push({
|
|
1575
|
+
type: diag.type ?? "warning",
|
|
1576
|
+
message: diag.message,
|
|
1577
|
+
...(diag.path ? { path: diag.path } : {}),
|
|
1578
|
+
});
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
catch (error) {
|
|
1582
|
+
resourceFailure("skills", error);
|
|
1583
|
+
}
|
|
1584
|
+
try {
|
|
1585
|
+
const extResult = resourceLoader.getExtensions?.();
|
|
1586
|
+
for (const ext of extResult?.extensions ?? []) {
|
|
1587
|
+
const path = ext.resolvedPath ?? ext.path ?? "";
|
|
1588
|
+
extensions.push({
|
|
1589
|
+
name: ext.sourceInfo?.source ?? ext.path ?? "extension",
|
|
1590
|
+
path,
|
|
1591
|
+
...(ext.resolvedPath ? { resolvedPath: ext.resolvedPath } : {}),
|
|
1592
|
+
source: ext.sourceInfo?.source ?? "",
|
|
1593
|
+
scope: ext.sourceInfo?.scope ?? "unknown",
|
|
1594
|
+
...(ext.sourceInfo?.origin ? { origin: ext.sourceInfo.origin } : {}),
|
|
1595
|
+
enabled: true,
|
|
1596
|
+
});
|
|
1597
|
+
resolvedResources.push({
|
|
1598
|
+
type: "extension",
|
|
1599
|
+
name: ext.sourceInfo?.source ?? ext.path ?? "extension",
|
|
1600
|
+
path,
|
|
1601
|
+
source: ext.sourceInfo?.source ?? "",
|
|
1602
|
+
scope: ext.sourceInfo?.scope ?? "unknown",
|
|
1603
|
+
});
|
|
1604
|
+
}
|
|
1605
|
+
for (const err of extResult?.errors ?? []) {
|
|
1606
|
+
if (!err?.error)
|
|
1607
|
+
continue;
|
|
1608
|
+
diagnostics.push({
|
|
1609
|
+
type: "error",
|
|
1610
|
+
message: err.error,
|
|
1611
|
+
...(err.path ? { path: err.path } : {}),
|
|
1612
|
+
});
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
catch (error) {
|
|
1616
|
+
resourceFailure("extensions", error);
|
|
1617
|
+
}
|
|
1618
|
+
try {
|
|
1619
|
+
const promptsResult = resourceLoader.getPrompts?.();
|
|
1620
|
+
for (const prompt of promptsResult?.prompts ?? []) {
|
|
1621
|
+
resolvedResources.push({
|
|
1622
|
+
type: "prompt",
|
|
1623
|
+
name: prompt.name,
|
|
1624
|
+
path: prompt.filePath,
|
|
1625
|
+
source: prompt.sourceInfo?.source ?? "",
|
|
1626
|
+
scope: prompt.sourceInfo?.scope ?? "unknown",
|
|
1627
|
+
});
|
|
1628
|
+
}
|
|
1629
|
+
for (const diag of promptsResult?.diagnostics ?? []) {
|
|
1630
|
+
if (!diag?.message)
|
|
1631
|
+
continue;
|
|
1632
|
+
diagnostics.push({
|
|
1633
|
+
type: diag.type ?? "warning",
|
|
1634
|
+
message: diag.message,
|
|
1635
|
+
...(diag.path ? { path: diag.path } : {}),
|
|
1636
|
+
});
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
catch (error) {
|
|
1640
|
+
resourceFailure("prompts", error);
|
|
1641
|
+
}
|
|
1642
|
+
try {
|
|
1643
|
+
const themesResult = resourceLoader.getThemes?.();
|
|
1644
|
+
for (const theme of themesResult?.themes ?? []) {
|
|
1645
|
+
if (!theme.path)
|
|
1646
|
+
continue;
|
|
1647
|
+
resolvedResources.push({
|
|
1648
|
+
type: "theme",
|
|
1649
|
+
name: theme.name ?? theme.path,
|
|
1650
|
+
path: theme.path,
|
|
1651
|
+
source: "",
|
|
1652
|
+
scope: "unknown",
|
|
1653
|
+
});
|
|
1654
|
+
}
|
|
1655
|
+
for (const diag of themesResult?.diagnostics ?? []) {
|
|
1656
|
+
if (!diag?.message)
|
|
1657
|
+
continue;
|
|
1658
|
+
diagnostics.push({
|
|
1659
|
+
type: diag.type ?? "warning",
|
|
1660
|
+
message: diag.message,
|
|
1661
|
+
...(diag.path ? { path: diag.path } : {}),
|
|
1662
|
+
});
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
catch (error) {
|
|
1666
|
+
resourceFailure("themes", error);
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
// Packages: Pi settings specs + resolved resource metadata. Fields the SDK
|
|
1670
|
+
// does not expose stay absent (real missing, never fabricated). A
|
|
1671
|
+
// readPackageInventory failure is a PARTIAL failure (RF-02): the snapshot
|
|
1672
|
+
// stays 200 with a structured retryable diagnostic — never a silent
|
|
1673
|
+
// fake-empty package list.
|
|
1674
|
+
const { readPackageInventory } = await import("./pi-console-config.js");
|
|
1675
|
+
let packages = [];
|
|
1676
|
+
try {
|
|
1677
|
+
packages = (await readPackageInventory(await safeGetAgentDir(this.bindings), this.options.cwd, resolvedResources));
|
|
1678
|
+
}
|
|
1679
|
+
catch (error) {
|
|
1680
|
+
packages = [];
|
|
1681
|
+
diagnostics.push({
|
|
1682
|
+
type: "error",
|
|
1683
|
+
code: "PACKAGE_INVENTORY_FAILED",
|
|
1684
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1685
|
+
retryable: true,
|
|
1686
|
+
});
|
|
1687
|
+
}
|
|
1688
|
+
const { readMcpInventory } = await import("./mcp-inventory.js");
|
|
1689
|
+
let mcp = { servers: [], unassignedTools: [] };
|
|
1690
|
+
try {
|
|
1691
|
+
mcp = await readMcpInventory({
|
|
1692
|
+
agentDir: await safeGetAgentDir(this.bindings),
|
|
1693
|
+
cwd: this.options.cwd,
|
|
1694
|
+
tools: session.getAllTools?.() ?? [],
|
|
1695
|
+
activeToolNames: session.getActiveToolNames(),
|
|
1696
|
+
});
|
|
1697
|
+
}
|
|
1698
|
+
catch (error) {
|
|
1699
|
+
diagnostics.push({
|
|
1700
|
+
type: "error",
|
|
1701
|
+
code: "MCP_INVENTORY_FAILED",
|
|
1702
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1703
|
+
retryable: true,
|
|
1704
|
+
});
|
|
1705
|
+
}
|
|
1706
|
+
return projectRuntimeSnapshot({
|
|
1707
|
+
sessionId,
|
|
1708
|
+
revision: this.revisions.get(sessionId) ?? 0,
|
|
1709
|
+
loadedAt: new Date().toISOString(),
|
|
1710
|
+
selection: {
|
|
1711
|
+
...(session.model?.provider &&
|
|
1712
|
+
(session.model?.id ?? session.model?.modelId)
|
|
1713
|
+
? {
|
|
1714
|
+
model: {
|
|
1715
|
+
provider: session.model.provider,
|
|
1716
|
+
id: session.model.id ?? session.model.modelId,
|
|
1717
|
+
...(session.model.name ? { name: session.model.name } : {}),
|
|
1718
|
+
},
|
|
1719
|
+
}
|
|
1720
|
+
: {}),
|
|
1721
|
+
thinkingSelection,
|
|
1722
|
+
...(effectiveThinkingLevel
|
|
1723
|
+
? { effectiveThinkingLevel: effectiveThinkingLevel }
|
|
1724
|
+
: {}),
|
|
1725
|
+
},
|
|
1726
|
+
models,
|
|
1727
|
+
thinkingLevels,
|
|
1728
|
+
systemPrompt: {
|
|
1729
|
+
...(options?.includeRaw ? { raw: rawSystemPrompt } : {}),
|
|
1730
|
+
// The RAW prompt flows into the projection: text is scrubbed +
|
|
1731
|
+
// length-limited while sha256/characters stay RAW-based (design §2.2).
|
|
1732
|
+
text: rawSystemPrompt,
|
|
1733
|
+
sha256: createHash("sha256").update(rawSystemPrompt).digest("hex"),
|
|
1734
|
+
characters: rawSystemPrompt.length,
|
|
1735
|
+
},
|
|
1736
|
+
activeTools: session.getActiveToolNames(),
|
|
1737
|
+
contextFiles,
|
|
1738
|
+
skills,
|
|
1739
|
+
extensions,
|
|
1740
|
+
packages,
|
|
1741
|
+
mcp,
|
|
1742
|
+
diagnostics,
|
|
1743
|
+
hasBash: true,
|
|
1744
|
+
});
|
|
1745
|
+
}
|
|
1746
|
+
/**
|
|
1747
|
+
* Re-open a previously persisted Chat session after Console restart or
|
|
1748
|
+
* browser refresh (roadmap M1-S04 / S05).
|
|
1749
|
+
*
|
|
1750
|
+
* Unlike `createSession`, this reuses the EXISTING Pi JSONL session file so
|
|
1751
|
+
* the conversation history is preserved. The SDK's `SessionManager.open(path)`
|
|
1752
|
+
* loads the file; we then rebuild the same services + pinned tool surface so
|
|
1753
|
+
* the operator-chat three-gate policy still applies after a restart.
|
|
1754
|
+
*
|
|
1755
|
+
* Fail-closed contract: if `sessionFile` is missing, or the SDK cannot open
|
|
1756
|
+
* it (file deleted/corrupt), returns an `{ ok: false, code }` result rather
|
|
1757
|
+
* than silently creating a fresh empty session and losing prior context.
|
|
1758
|
+
*/
|
|
1759
|
+
async reopenSession(init) {
|
|
1760
|
+
const sessionId = init.sessionId;
|
|
1761
|
+
// Wait for in-flight create materialization rather than racing reopen.
|
|
1762
|
+
const initState = this.sessionInit.get(sessionId);
|
|
1763
|
+
if (initState?.status === "initializing") {
|
|
1764
|
+
try {
|
|
1765
|
+
await initState.promise;
|
|
1766
|
+
}
|
|
1767
|
+
catch {
|
|
1768
|
+
// failure recorded on sessionInit
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
const failed = this.sessionInit.get(sessionId);
|
|
1772
|
+
if (failed?.status === "failed") {
|
|
1773
|
+
return {
|
|
1774
|
+
ok: false,
|
|
1775
|
+
code: "PI_SESSION_INIT_FAILED",
|
|
1776
|
+
message: failed.error.message,
|
|
1777
|
+
};
|
|
1778
|
+
}
|
|
1779
|
+
// Already active in-memory: idempotent reopen is a no-op success.
|
|
1780
|
+
if (this.sessions.has(sessionId)) {
|
|
1781
|
+
return { ok: true, handle: await this.snapshotActive(sessionId) };
|
|
1782
|
+
}
|
|
1783
|
+
if (!init.sessionFile) {
|
|
1784
|
+
return {
|
|
1785
|
+
ok: false,
|
|
1786
|
+
code: "NO_SESSION_FILE",
|
|
1787
|
+
message: `cannot reopen chat session ${sessionId}: no persisted Pi session file`,
|
|
1788
|
+
};
|
|
1789
|
+
}
|
|
1790
|
+
let sessionManager;
|
|
1791
|
+
try {
|
|
1792
|
+
sessionManager = await this.bindings.openSessionManager({
|
|
1793
|
+
sessionFile: init.sessionFile,
|
|
1794
|
+
});
|
|
1795
|
+
}
|
|
1796
|
+
catch (error) {
|
|
1797
|
+
return {
|
|
1798
|
+
ok: false,
|
|
1799
|
+
code: "REOPEN_FAILED",
|
|
1800
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1801
|
+
};
|
|
1802
|
+
}
|
|
1803
|
+
if (!sessionManager) {
|
|
1804
|
+
return {
|
|
1805
|
+
ok: false,
|
|
1806
|
+
code: "REOPEN_FAILED",
|
|
1807
|
+
message: `Pi session file missing or not openable: ${init.sessionFile}`,
|
|
1808
|
+
};
|
|
1809
|
+
}
|
|
1810
|
+
const managerSessionId = readSessionManagerId(sessionManager);
|
|
1811
|
+
if (managerSessionId && managerSessionId !== sessionId) {
|
|
1812
|
+
return {
|
|
1813
|
+
ok: false,
|
|
1814
|
+
code: "REOPEN_FAILED",
|
|
1815
|
+
message: `reopened Pi sessionId mismatch: expected ${sessionId}, got ${managerSessionId}`,
|
|
1816
|
+
};
|
|
1817
|
+
}
|
|
1818
|
+
try {
|
|
1819
|
+
const built = await this.buildSessionWithServices({
|
|
1820
|
+
sessionManager,
|
|
1821
|
+
model: init.model,
|
|
1822
|
+
systemPromptSuffix: init.systemPromptSuffix,
|
|
1823
|
+
});
|
|
1824
|
+
const session = built.session;
|
|
1825
|
+
// Prefer the SessionManager id (from the JSONL header). Some SDK
|
|
1826
|
+
// builds expose a different agent-session UUID on `session.sessionId`
|
|
1827
|
+
// even when the manager correctly reopened our operator-chat file —
|
|
1828
|
+
// map by the durable Console sessionId in that case.
|
|
1829
|
+
const reportedId = session.sessionId ?? managerSessionId;
|
|
1830
|
+
if (reportedId &&
|
|
1831
|
+
reportedId !== sessionId &&
|
|
1832
|
+
managerSessionId !== sessionId) {
|
|
1833
|
+
try {
|
|
1834
|
+
session.dispose();
|
|
1835
|
+
}
|
|
1836
|
+
catch {
|
|
1837
|
+
// ignore
|
|
1838
|
+
}
|
|
1839
|
+
return {
|
|
1840
|
+
ok: false,
|
|
1841
|
+
code: "REOPEN_FAILED",
|
|
1842
|
+
message: `reopened Pi sessionId mismatch: expected ${sessionId}, got ${reportedId}`,
|
|
1843
|
+
};
|
|
1844
|
+
}
|
|
1845
|
+
session.setActiveToolsByName(computeOperatorChatActiveToolNames(session));
|
|
1846
|
+
this.sessions.set(sessionId, session);
|
|
1847
|
+
this.sessionManagers.set(sessionId, sessionManager);
|
|
1848
|
+
this.modelRuntimes.set(sessionId, built.modelRuntime);
|
|
1849
|
+
this.sessionModels.set(sessionId, init.model);
|
|
1850
|
+
this.serviceScopes.set(sessionId, built.services);
|
|
1851
|
+
this.revisions.set(sessionId, 1);
|
|
1852
|
+
this.thinkingSelections.set(sessionId, "auto");
|
|
1853
|
+
this.sessionInit.set(sessionId, { status: "ready" });
|
|
1854
|
+
return {
|
|
1855
|
+
ok: true,
|
|
1856
|
+
handle: {
|
|
1857
|
+
sessionId,
|
|
1858
|
+
sessionFile: session.sessionFile ?? init.sessionFile,
|
|
1859
|
+
createdAt: new Date().toISOString(),
|
|
1860
|
+
model: init.model,
|
|
1861
|
+
activeTools: session.getActiveToolNames(),
|
|
1862
|
+
},
|
|
1863
|
+
};
|
|
1864
|
+
}
|
|
1865
|
+
catch (error) {
|
|
1866
|
+
return {
|
|
1867
|
+
ok: false,
|
|
1868
|
+
code: "REOPEN_FAILED",
|
|
1869
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1870
|
+
};
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
/** Build a handle for an already-active session (idempotent reopen path). */
|
|
1874
|
+
async snapshotActive(sessionId) {
|
|
1875
|
+
const session = this.sessions.get(sessionId);
|
|
1876
|
+
const stored = this.sessionModels.get(sessionId);
|
|
1877
|
+
const fromSession = session.model?.provider
|
|
1878
|
+
? {
|
|
1879
|
+
provider: session.model.provider,
|
|
1880
|
+
modelId: session.model.id ??
|
|
1881
|
+
session.model.modelId ??
|
|
1882
|
+
"",
|
|
1883
|
+
}
|
|
1884
|
+
: undefined;
|
|
1885
|
+
const model = stored ??
|
|
1886
|
+
(fromSession?.modelId ? fromSession : undefined);
|
|
1887
|
+
return {
|
|
1888
|
+
sessionId,
|
|
1889
|
+
sessionFile: session.sessionFile,
|
|
1890
|
+
createdAt: new Date().toISOString(),
|
|
1891
|
+
model,
|
|
1892
|
+
activeTools: session.getActiveToolNames(),
|
|
1893
|
+
};
|
|
1894
|
+
}
|
|
1895
|
+
/**
|
|
1896
|
+
* Shared services + pinned-tool materialization for reopen/fork paths.
|
|
1897
|
+
* Create uses startMaterialization instead so the durable shell can return
|
|
1898
|
+
* before AgentSession is ready. Tool definitions reuse runtime-level caches.
|
|
1899
|
+
*/
|
|
1900
|
+
async buildSessionWithServices(init) {
|
|
1901
|
+
const agentDir = await safeGetAgentDir(this.bindings);
|
|
1902
|
+
const appendSystemPrompt = [
|
|
1903
|
+
OPERATOR_CHAT_SYSTEM_PROMPT_BASE,
|
|
1904
|
+
...(init.systemPromptSuffix ? [init.systemPromptSuffix] : []),
|
|
1905
|
+
];
|
|
1906
|
+
await this.refreshSkillPrefsCache().catch(() => undefined);
|
|
1907
|
+
const { services } = await this.bindings.createServices({
|
|
1908
|
+
cwd: this.options.cwd,
|
|
1909
|
+
agentDir,
|
|
1910
|
+
resourceLoaderOptions: await this.buildResourceLoaderOptions({
|
|
1911
|
+
appendSystemPrompt,
|
|
1912
|
+
}),
|
|
1913
|
+
});
|
|
1914
|
+
const modelRuntime = services.modelRuntime;
|
|
1915
|
+
const resolvedModel = init.model
|
|
1916
|
+
? (this.bindings.resolveModel({
|
|
1917
|
+
modelRuntime,
|
|
1918
|
+
provider: init.model.provider,
|
|
1919
|
+
modelId: init.model.modelId,
|
|
1920
|
+
}) ?? undefined)
|
|
1921
|
+
: undefined;
|
|
1922
|
+
assertNoWriteToolInList(OPERATOR_CHAT_ALLOWED_TOOLS);
|
|
1923
|
+
const customTools = await this.loadCustomTools();
|
|
1924
|
+
const { session } = await this.bindings.createSessionFromServices({
|
|
1925
|
+
services,
|
|
1926
|
+
sessionManager: init.sessionManager,
|
|
1927
|
+
excludeTools: [...OPERATOR_CHAT_SESSION_EXCLUDE_TOOLS],
|
|
1928
|
+
...(customTools.length > 0 ? { customTools } : {}),
|
|
1929
|
+
...(resolvedModel ? { model: resolvedModel } : {}),
|
|
1930
|
+
});
|
|
1931
|
+
await bindSessionExtensions(session);
|
|
1932
|
+
pinOperatorChatTools(session);
|
|
1933
|
+
return {
|
|
1934
|
+
session,
|
|
1935
|
+
modelRuntime,
|
|
1936
|
+
services: {
|
|
1937
|
+
resourceLoader: services.resourceLoader,
|
|
1938
|
+
settingsManager: services.settingsManager,
|
|
1939
|
+
},
|
|
1940
|
+
};
|
|
1941
|
+
}
|
|
1942
|
+
/** Re-open a previously created session (cross-request). */
|
|
1943
|
+
open(sessionId) {
|
|
1944
|
+
return this.sessions.get(sessionId);
|
|
1945
|
+
}
|
|
1946
|
+
hasSession(sessionId) {
|
|
1947
|
+
return this.sessions.has(sessionId);
|
|
1948
|
+
}
|
|
1949
|
+
/**
|
|
1950
|
+
* Live runtime busy facts for /state reconcile. isPromptRunning is owned by
|
|
1951
|
+
* this Console admission fence; other flags mirror the Pi session when present.
|
|
1952
|
+
*/
|
|
1953
|
+
getSessionBusyFacts(sessionId) {
|
|
1954
|
+
const session = this.sessions.get(sessionId);
|
|
1955
|
+
const isPromptRunning = this.promptOwners.has(sessionId);
|
|
1956
|
+
return {
|
|
1957
|
+
isStreaming: session?.isStreaming === true || isPromptRunning,
|
|
1958
|
+
isPromptRunning,
|
|
1959
|
+
isCompacting: session?.isCompacting === true,
|
|
1960
|
+
isBashRunning: session?.isBashRunning === true ||
|
|
1961
|
+
session?.hasPendingBashMessages === true,
|
|
1962
|
+
};
|
|
1963
|
+
}
|
|
1964
|
+
/**
|
|
1965
|
+
* Send a prompt and stream events. Gate 3: each tool invocation is
|
|
1966
|
+
* re-authorized by authorizeOperatorChatTool before the dispatcher runs it
|
|
1967
|
+
* (the dispatcher is wired by the HTTP layer, see chat-session.ts).
|
|
1968
|
+
*
|
|
1969
|
+
* Prompt ownership is acquired synchronously before the first await so two
|
|
1970
|
+
* concurrent prompts on the same Session cannot both enter the SDK path.
|
|
1971
|
+
*/
|
|
1972
|
+
async prompt(sessionId, text, onEvent, options) {
|
|
1973
|
+
// Synchronous admission fence — before any await.
|
|
1974
|
+
if (this.promptOwners.has(sessionId)) {
|
|
1975
|
+
throw new Error("CHAT_TURN_ACTIVE");
|
|
1976
|
+
}
|
|
1977
|
+
this.promptOwners.add(sessionId);
|
|
1978
|
+
let unsub;
|
|
1979
|
+
let abortPromise;
|
|
1980
|
+
const onAbort = () => {
|
|
1981
|
+
// M1 T08 / AC-R3-003: Stop must abort only the current turn, NEVER
|
|
1982
|
+
// dispose the session. Await session.abort() so ownership settles
|
|
1983
|
+
// after the SDK reports idle. When abort is unavailable, fail closed
|
|
1984
|
+
// non-destructively — Session stays reusable; terminal closeout or
|
|
1985
|
+
// /state reconcile still converges ownership.
|
|
1986
|
+
const session = this.sessions.get(sessionId);
|
|
1987
|
+
if (!session)
|
|
1988
|
+
return;
|
|
1989
|
+
try {
|
|
1990
|
+
if (typeof session.abort === "function") {
|
|
1991
|
+
abortPromise = Promise.resolve(session.abort()).catch(() => undefined);
|
|
1992
|
+
}
|
|
1993
|
+
// else: do not call session.dispose() — Stop never destroys Session.
|
|
1994
|
+
}
|
|
1995
|
+
catch {
|
|
1996
|
+
// ignore
|
|
1997
|
+
}
|
|
1998
|
+
};
|
|
1999
|
+
try {
|
|
2000
|
+
// AC-FIX-002: if Stop already fired before readiness, never start model work.
|
|
2001
|
+
if (options?.signal?.aborted) {
|
|
2002
|
+
return;
|
|
2003
|
+
}
|
|
2004
|
+
// First message while creating waits on the same materialization promise.
|
|
2005
|
+
await this.ensureSessionReady(sessionId);
|
|
2006
|
+
// Abort may land during readiness (or between readiness and prompt).
|
|
2007
|
+
if (options?.signal?.aborted) {
|
|
2008
|
+
return;
|
|
2009
|
+
}
|
|
2010
|
+
const session = this.sessions.get(sessionId);
|
|
2011
|
+
if (!session) {
|
|
2012
|
+
throw new Error(`chat session not found: ${sessionId}`);
|
|
2013
|
+
}
|
|
2014
|
+
// Gate 2 (re-pin before each turn): interview turns pass through the
|
|
2015
|
+
// existing closed interview allow/deny registry. The next ordinary turn
|
|
2016
|
+
// explicitly restores the normal Operator Chat set (baseline + extension
|
|
2017
|
+
// tools, ADR 0012).
|
|
2018
|
+
const requestedTools = OPERATOR_CHAT_ACTIVE_TOOL_NAMES();
|
|
2019
|
+
const activeTools = options?.mode === "requirement-interview"
|
|
2020
|
+
? filterActiveInterviewTools(requestedTools).allowed
|
|
2021
|
+
: computeOperatorChatActiveToolNames(session);
|
|
2022
|
+
session.setActiveToolsByName(activeTools);
|
|
2023
|
+
// Final pre-prompt fence: abort after tool pin must still skip session.prompt.
|
|
2024
|
+
if (options?.signal?.aborted) {
|
|
2025
|
+
return;
|
|
2026
|
+
}
|
|
2027
|
+
const emitContextUsage = () => {
|
|
2028
|
+
try {
|
|
2029
|
+
const contextUsage = session.getSessionStats?.()?.contextUsage;
|
|
2030
|
+
if (!contextUsage)
|
|
2031
|
+
return;
|
|
2032
|
+
if (contextUsage.contextWindow <= 0 &&
|
|
2033
|
+
contextUsage.percent === null &&
|
|
2034
|
+
contextUsage.tokens === null) {
|
|
2035
|
+
return;
|
|
2036
|
+
}
|
|
2037
|
+
onEvent({
|
|
2038
|
+
type: "usage",
|
|
2039
|
+
sessionId,
|
|
2040
|
+
usage: {
|
|
2041
|
+
inputTokens: 0,
|
|
2042
|
+
outputTokens: 0,
|
|
2043
|
+
totalTokens: 0,
|
|
2044
|
+
contextUsage,
|
|
2045
|
+
},
|
|
2046
|
+
});
|
|
2047
|
+
}
|
|
2048
|
+
catch {
|
|
2049
|
+
// Stats getter is best-effort; never break the event stream.
|
|
2050
|
+
}
|
|
2051
|
+
};
|
|
2052
|
+
emitContextUsage();
|
|
2053
|
+
unsub = session.subscribe((event) => {
|
|
2054
|
+
const mapped = mapSdkEvent(sessionId, event);
|
|
2055
|
+
if (mapped)
|
|
2056
|
+
onEvent(mapped);
|
|
2057
|
+
// Pi puts per-assistant-message usage on `message.usage` ({ input, output,
|
|
2058
|
+
// cacheRead, cacheWrite, totalTokens, cost }). Emit on message_end so
|
|
2059
|
+
// multi-step tool turns accumulate like pi-web SessionInfoBar; turn_end
|
|
2060
|
+
// repeats the final message and must not double-count.
|
|
2061
|
+
if (event.type === "message_end") {
|
|
2062
|
+
const usage = extractUsageSample(event);
|
|
2063
|
+
if (usage)
|
|
2064
|
+
onEvent({ type: "usage", sessionId, usage });
|
|
2065
|
+
emitContextUsage();
|
|
2066
|
+
}
|
|
2067
|
+
// Keep the context fill bar live through the turn (pi-web SessionInfoBar).
|
|
2068
|
+
if (event.type === "agent_start" ||
|
|
2069
|
+
event.type === "agent_settled" ||
|
|
2070
|
+
event.type === "tool_execution_end") {
|
|
2071
|
+
emitContextUsage();
|
|
2072
|
+
}
|
|
2073
|
+
});
|
|
2074
|
+
options?.signal?.addEventListener("abort", onAbort);
|
|
2075
|
+
if (options?.signal?.aborted) {
|
|
2076
|
+
onAbort();
|
|
2077
|
+
if (abortPromise)
|
|
2078
|
+
await abortPromise.catch(() => undefined);
|
|
2079
|
+
return;
|
|
2080
|
+
}
|
|
2081
|
+
try {
|
|
2082
|
+
await session.prompt(text, options?.images?.length ? { images: options.images } : undefined);
|
|
2083
|
+
// Wait until the session reports idle / not streaming. The SDK prompt()
|
|
2084
|
+
// resolves when the agent turn completes, but post-turn continuation
|
|
2085
|
+
// (auto-compaction / follow-up) may still be in flight. We poll isIdle.
|
|
2086
|
+
// While polling we emit periodic heartbeat events so the SSE stream
|
|
2087
|
+
// keeps proxies/browsers from timing out (no other data is flowing).
|
|
2088
|
+
await waitForIdle(session, {
|
|
2089
|
+
signal: options?.signal,
|
|
2090
|
+
onHeartbeat: () => onEvent({ type: "heartbeat", sessionId }),
|
|
2091
|
+
});
|
|
2092
|
+
if (abortPromise)
|
|
2093
|
+
await abortPromise;
|
|
2094
|
+
// User abort is closed out as Turn state `aborted`; do not publish a
|
|
2095
|
+
// red ordinary error event for intentional Stop.
|
|
2096
|
+
if (!options?.signal?.aborted) {
|
|
2097
|
+
onEvent({ type: "agent_settled", sessionId });
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
catch (error) {
|
|
2101
|
+
if (abortPromise)
|
|
2102
|
+
await abortPromise.catch(() => undefined);
|
|
2103
|
+
if (options?.signal?.aborted) {
|
|
2104
|
+
// User abort: swallow ordinary error publication (AC-FIX-003).
|
|
2105
|
+
return;
|
|
2106
|
+
}
|
|
2107
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2108
|
+
onEvent({ type: "error", sessionId, message });
|
|
2109
|
+
throw error;
|
|
2110
|
+
}
|
|
2111
|
+
finally {
|
|
2112
|
+
options?.signal?.removeEventListener("abort", onAbort);
|
|
2113
|
+
unsub?.();
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2116
|
+
finally {
|
|
2117
|
+
this.promptOwners.delete(sessionId);
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
/** Generate exactly one title in an isolated in-memory, tool-less LOW session. */
|
|
2121
|
+
async generateTitle(sessionId, firstUserText) {
|
|
2122
|
+
if (this.titleInFlight.has(sessionId))
|
|
2123
|
+
return undefined;
|
|
2124
|
+
this.titleInFlight.add(sessionId);
|
|
2125
|
+
let session;
|
|
2126
|
+
let unsubscribe;
|
|
2127
|
+
let timeout;
|
|
2128
|
+
try {
|
|
2129
|
+
const { services } = await this.bindings.createServices({
|
|
2130
|
+
cwd: this.options.cwd,
|
|
2131
|
+
agentDir: await safeGetAgentDir(this.bindings),
|
|
2132
|
+
// The detached title worker has a closed title-only resource surface:
|
|
2133
|
+
// no project/user context, extensions, skills, prompt templates, or
|
|
2134
|
+
// themes. Its sole system prompt is deliberately minimal; never append
|
|
2135
|
+
// the operator-chat prompt or any loaded resource text.
|
|
2136
|
+
resourceLoaderOptions: {
|
|
2137
|
+
noContextFiles: true,
|
|
2138
|
+
noSkills: true,
|
|
2139
|
+
noExtensions: true,
|
|
2140
|
+
noPromptTemplates: true,
|
|
2141
|
+
noThemes: true,
|
|
2142
|
+
systemPrompt: "Generate one concise plain-text session title in the user's primary language. Return only the title.",
|
|
2143
|
+
},
|
|
2144
|
+
});
|
|
2145
|
+
const low = await resolveLowChatModel(this.options.cwd, this.bindings.listAvailableModels({ modelRuntime: services.modelRuntime }));
|
|
2146
|
+
if (!low)
|
|
2147
|
+
return undefined;
|
|
2148
|
+
const model = this.bindings.resolveModel({
|
|
2149
|
+
modelRuntime: services.modelRuntime,
|
|
2150
|
+
provider: low.provider,
|
|
2151
|
+
modelId: low.modelId,
|
|
2152
|
+
});
|
|
2153
|
+
if (!model)
|
|
2154
|
+
return undefined;
|
|
2155
|
+
if (!this.bindings.createInMemorySessionManager)
|
|
2156
|
+
return undefined;
|
|
2157
|
+
const sessionManager = await this.bindings.createInMemorySessionManager({
|
|
2158
|
+
cwd: this.options.cwd,
|
|
2159
|
+
});
|
|
2160
|
+
const created = await this.bindings.createSessionFromServices({
|
|
2161
|
+
services,
|
|
2162
|
+
sessionManager,
|
|
2163
|
+
noTools: "all",
|
|
2164
|
+
tools: [],
|
|
2165
|
+
customTools: [],
|
|
2166
|
+
model,
|
|
2167
|
+
});
|
|
2168
|
+
session = created.session;
|
|
2169
|
+
let output = "";
|
|
2170
|
+
unsubscribe = session.subscribe((event) => {
|
|
2171
|
+
if (event.type === "message_end")
|
|
2172
|
+
output = extractAssistantText(event.message);
|
|
2173
|
+
});
|
|
2174
|
+
const controller = new AbortController();
|
|
2175
|
+
timeout = setTimeout(() => controller.abort(), 10_000);
|
|
2176
|
+
await Promise.race([
|
|
2177
|
+
session.prompt(`User message:\n${firstUserText.slice(0, 1200)}`, { signal: controller.signal }),
|
|
2178
|
+
new Promise((_, reject) => controller.signal.addEventListener("abort", () => reject(new Error("TITLE_TIMEOUT")), { once: true })),
|
|
2179
|
+
]);
|
|
2180
|
+
return output;
|
|
2181
|
+
}
|
|
2182
|
+
catch {
|
|
2183
|
+
return undefined;
|
|
2184
|
+
}
|
|
2185
|
+
finally {
|
|
2186
|
+
if (timeout)
|
|
2187
|
+
clearTimeout(timeout);
|
|
2188
|
+
unsubscribe?.();
|
|
2189
|
+
try {
|
|
2190
|
+
await session?.abort?.();
|
|
2191
|
+
}
|
|
2192
|
+
catch { /* abort converges before disposal */ }
|
|
2193
|
+
try {
|
|
2194
|
+
session?.dispose();
|
|
2195
|
+
}
|
|
2196
|
+
catch { /* isolated title cleanup is best-effort */ }
|
|
2197
|
+
this.titleInFlight.delete(sessionId);
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
/** Pi session_info mirror after durable automatic-title persistence.
|
|
2201
|
+
* Callers own the detached catch boundary so synchronous throws and rejected
|
|
2202
|
+
* mirror Promises are both observed without affecting durable Chat state. */
|
|
2203
|
+
async setSessionName(sessionId, title) {
|
|
2204
|
+
await this.sessions.get(sessionId)?.setSessionName?.(title);
|
|
2205
|
+
}
|
|
2206
|
+
async compact(sessionId, customInstructions) {
|
|
2207
|
+
const session = this.sessions.get(sessionId);
|
|
2208
|
+
if (!session)
|
|
2209
|
+
throw new Error(`chat session not found: ${sessionId}`);
|
|
2210
|
+
if (session.isStreaming === true || session.isIdle === false)
|
|
2211
|
+
throw new Error("CHAT_TURN_ACTIVE");
|
|
2212
|
+
if (typeof session.compact !== "function")
|
|
2213
|
+
throw new Error("COMPACTION_UNSUPPORTED");
|
|
2214
|
+
const result = await session.compact(customInstructions);
|
|
2215
|
+
return projectCompactSnapshot(result ?? {});
|
|
2216
|
+
}
|
|
2217
|
+
dispose(sessionId) {
|
|
2218
|
+
// Mark disposed first so a late materialization never registers ready.
|
|
2219
|
+
this.disposedSessions.add(sessionId);
|
|
2220
|
+
this.sessionInit.delete(sessionId);
|
|
2221
|
+
const session = this.sessions.get(sessionId);
|
|
2222
|
+
if (session) {
|
|
2223
|
+
try {
|
|
2224
|
+
session.dispose();
|
|
2225
|
+
}
|
|
2226
|
+
catch {
|
|
2227
|
+
// ignore
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
this.sessions.delete(sessionId);
|
|
2231
|
+
this.sessionManagers.delete(sessionId);
|
|
2232
|
+
this.mainlineLeaves.delete(sessionId);
|
|
2233
|
+
this.modelRuntimes.delete(sessionId);
|
|
2234
|
+
this.sessionModels.delete(sessionId);
|
|
2235
|
+
this.serviceScopes.delete(sessionId);
|
|
2236
|
+
this.revisions.delete(sessionId);
|
|
2237
|
+
this.thinkingSelections.delete(sessionId);
|
|
2238
|
+
this.reloadInFlight.delete(sessionId);
|
|
2239
|
+
}
|
|
2240
|
+
disposeAll() {
|
|
2241
|
+
for (const id of [...this.sessions.keys()])
|
|
2242
|
+
this.dispose(id);
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
function mapAssistantMessageEvent(type, sessionId, message) {
|
|
2246
|
+
const content = extractAssistantContentBlocks(message);
|
|
2247
|
+
return {
|
|
2248
|
+
type,
|
|
2249
|
+
sessionId,
|
|
2250
|
+
text: extractAssistantText(message),
|
|
2251
|
+
...(content.length > 0 ? { content } : {}),
|
|
2252
|
+
};
|
|
2253
|
+
}
|
|
2254
|
+
function mapSdkEvent(sessionId, event) {
|
|
2255
|
+
switch (event.type) {
|
|
2256
|
+
case "agent_start":
|
|
2257
|
+
return { type: "agent_start", sessionId };
|
|
2258
|
+
case "agent_end":
|
|
2259
|
+
return {
|
|
2260
|
+
type: "agent_end",
|
|
2261
|
+
sessionId,
|
|
2262
|
+
willRetry: Boolean(event.willRetry),
|
|
2263
|
+
};
|
|
2264
|
+
case "message_update":
|
|
2265
|
+
if (!isAssistantSdkMessage(event.message))
|
|
2266
|
+
return undefined;
|
|
2267
|
+
return mapAssistantMessageEvent("message_update", sessionId, event.message);
|
|
2268
|
+
case "message_end":
|
|
2269
|
+
if (!isAssistantSdkMessage(event.message))
|
|
2270
|
+
return undefined;
|
|
2271
|
+
return mapAssistantMessageEvent("message_end", sessionId, event.message);
|
|
2272
|
+
case "turn_end":
|
|
2273
|
+
// Usage is emitted from message_end (see prompt subscribe). turn_end
|
|
2274
|
+
// carries the same final assistant message and would double-count.
|
|
2275
|
+
return undefined;
|
|
2276
|
+
case "tool_execution_start":
|
|
2277
|
+
return {
|
|
2278
|
+
type: "tool_call",
|
|
2279
|
+
sessionId,
|
|
2280
|
+
toolCallId: event.toolCallId,
|
|
2281
|
+
toolName: event.toolName,
|
|
2282
|
+
args: event.args,
|
|
2283
|
+
};
|
|
2284
|
+
case "tool_execution_end":
|
|
2285
|
+
return {
|
|
2286
|
+
type: "tool_result",
|
|
2287
|
+
sessionId,
|
|
2288
|
+
toolCallId: event.toolCallId,
|
|
2289
|
+
toolName: event.toolName,
|
|
2290
|
+
result: event.result,
|
|
2291
|
+
isError: event.isError,
|
|
2292
|
+
};
|
|
2293
|
+
case "agent_settled":
|
|
2294
|
+
return { type: "agent_settled", sessionId };
|
|
2295
|
+
default:
|
|
2296
|
+
return undefined;
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
async function waitForIdle(session, options) {
|
|
2300
|
+
// Default 45s — shorter than the previous 120s so a stuck SDK session does
|
|
2301
|
+
// not pin an HTTP connection for two full minutes. The SDK agent turn
|
|
2302
|
+
// normally settles in seconds; 45s covers auto-compaction/follow-up tails
|
|
2303
|
+
// while staying under typical proxy idle timeouts (60s).
|
|
2304
|
+
const timeoutMs = options?.timeoutMs ?? 45_000;
|
|
2305
|
+
const heartbeatIntervalMs = options?.heartbeatIntervalMs ?? 10_000;
|
|
2306
|
+
const start = Date.now();
|
|
2307
|
+
let lastHeartbeat = start;
|
|
2308
|
+
// eslint-disable-next-line no-constant-condition
|
|
2309
|
+
while (true) {
|
|
2310
|
+
if (options?.signal?.aborted)
|
|
2311
|
+
return;
|
|
2312
|
+
if (session.isIdle === true)
|
|
2313
|
+
return;
|
|
2314
|
+
if (session.isStreaming === false)
|
|
2315
|
+
return;
|
|
2316
|
+
const now = Date.now();
|
|
2317
|
+
if (now - start > timeoutMs)
|
|
2318
|
+
return;
|
|
2319
|
+
if (options?.onHeartbeat && now - lastHeartbeat >= heartbeatIntervalMs) {
|
|
2320
|
+
lastHeartbeat = now;
|
|
2321
|
+
try {
|
|
2322
|
+
options.onHeartbeat();
|
|
2323
|
+
}
|
|
2324
|
+
catch {
|
|
2325
|
+
// Heartbeat is best-effort; never let it break the wait loop.
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
async function safeGetAgentDir(bindings) {
|
|
2332
|
+
try {
|
|
2333
|
+
return await bindings.getAgentDir();
|
|
2334
|
+
}
|
|
2335
|
+
catch {
|
|
2336
|
+
// Fall back to a Console-app-data scoped agent dir so we never accidentally
|
|
2337
|
+
// touch the user's real ~/.pi/agent config if getAgentDir throws.
|
|
2338
|
+
return path.join(process.cwd(), ".harness", "console", "pi-agent-dir");
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
/**
|
|
2342
|
+
* Cached SDK module loader. The pi-coding-agent package is ESM-only, so we
|
|
2343
|
+
* must use dynamic import() (not require). Cached to avoid repeated imports.
|
|
2344
|
+
*/
|
|
2345
|
+
let cachedSdk;
|
|
2346
|
+
async function loadSdk() {
|
|
2347
|
+
if (!cachedSdk) {
|
|
2348
|
+
cachedSdk = import("@earendil-works/pi-coding-agent");
|
|
2349
|
+
}
|
|
2350
|
+
return cachedSdk;
|
|
2351
|
+
}
|
|
2352
|
+
/**
|
|
2353
|
+
* Default production bindings backed by the real Pi SDK (services mode).
|
|
2354
|
+
* Dynamically imported so the worker bundle does not hard-fail if the SDK is
|
|
2355
|
+
* absent at module load (discovered lazily by pi-readiness instead).
|
|
2356
|
+
*/
|
|
2357
|
+
export function createDefaultPiSdkBindings() {
|
|
2358
|
+
return {
|
|
2359
|
+
getAgentDir: async () => {
|
|
2360
|
+
const sdk = (await loadSdk());
|
|
2361
|
+
return sdk.getAgentDir();
|
|
2362
|
+
},
|
|
2363
|
+
createServices: async (opts) => {
|
|
2364
|
+
const sdk = (await loadSdk());
|
|
2365
|
+
// createAgentSessionServices returns the full AgentSessionServices object
|
|
2366
|
+
// directly (cwd/agentDir/modelRuntime/settingsManager/resourceLoader).
|
|
2367
|
+
// Wrap it so our type contract is satisfied.
|
|
2368
|
+
const result = await sdk.createAgentSessionServices({
|
|
2369
|
+
cwd: opts.cwd,
|
|
2370
|
+
agentDir: opts.agentDir,
|
|
2371
|
+
resourceLoaderOptions: opts.resourceLoaderOptions,
|
|
2372
|
+
});
|
|
2373
|
+
return { services: result };
|
|
2374
|
+
},
|
|
2375
|
+
createSessionFromServices: async (opts) => {
|
|
2376
|
+
const sdk = (await loadSdk());
|
|
2377
|
+
return sdk.createAgentSessionFromServices({
|
|
2378
|
+
services: opts.services,
|
|
2379
|
+
sessionManager: opts.sessionManager,
|
|
2380
|
+
...(opts.tools ? { tools: opts.tools } : {}),
|
|
2381
|
+
...(opts.noTools ? { noTools: opts.noTools } : {}),
|
|
2382
|
+
...(opts.excludeTools ? { excludeTools: opts.excludeTools } : {}),
|
|
2383
|
+
...(opts.customTools ? { customTools: opts.customTools } : {}),
|
|
2384
|
+
...(opts.model ? { model: opts.model } : {}),
|
|
2385
|
+
});
|
|
2386
|
+
},
|
|
2387
|
+
createSessionManager: async (opts) => {
|
|
2388
|
+
const sdk = (await loadSdk());
|
|
2389
|
+
return sdk.SessionManager.create(opts.cwd, opts.sessionDir, {
|
|
2390
|
+
id: opts.sessionId,
|
|
2391
|
+
});
|
|
2392
|
+
},
|
|
2393
|
+
createInMemorySessionManager: async (opts) => {
|
|
2394
|
+
const sdk = (await loadSdk());
|
|
2395
|
+
return sdk.SessionManager.inMemory(opts.cwd);
|
|
2396
|
+
},
|
|
2397
|
+
forkSessionManager: async (opts) => {
|
|
2398
|
+
const sdk = (await loadSdk());
|
|
2399
|
+
return sdk.SessionManager.forkFrom(opts.sourcePath, opts.targetCwd, opts.targetSessionDir, { id: opts.targetSessionId });
|
|
2400
|
+
},
|
|
2401
|
+
openSessionManager: async (opts) => {
|
|
2402
|
+
const sdk = (await loadSdk());
|
|
2403
|
+
try {
|
|
2404
|
+
// Pi SessionManager.open(missingPath) does NOT throw — it creates a
|
|
2405
|
+
// fresh in-memory session with a random UUID. That would reopen the
|
|
2406
|
+
// wrong conversation under the durable Console sessionId. Fail closed
|
|
2407
|
+
// before open when the persisted file is absent.
|
|
2408
|
+
await access(opts.sessionFile);
|
|
2409
|
+
return sdk.SessionManager.open(opts.sessionFile);
|
|
2410
|
+
}
|
|
2411
|
+
catch {
|
|
2412
|
+
// Missing/corrupt session file — caller fails closed rather than
|
|
2413
|
+
// silently creating a fresh session and losing prior context.
|
|
2414
|
+
return undefined;
|
|
2415
|
+
}
|
|
2416
|
+
},
|
|
2417
|
+
resolveModel: (opts) => {
|
|
2418
|
+
const rt = opts.modelRuntime;
|
|
2419
|
+
if (typeof rt?.getModel !== "function")
|
|
2420
|
+
return undefined;
|
|
2421
|
+
try {
|
|
2422
|
+
return rt.getModel(opts.provider, opts.modelId);
|
|
2423
|
+
}
|
|
2424
|
+
catch {
|
|
2425
|
+
return undefined;
|
|
2426
|
+
}
|
|
2427
|
+
},
|
|
2428
|
+
listAvailableModels: (opts) => {
|
|
2429
|
+
const rt = opts.modelRuntime;
|
|
2430
|
+
const available = rt?.snapshot?.available ?? [];
|
|
2431
|
+
const configured = rt?.snapshot?.configuredProviders;
|
|
2432
|
+
const isConfigured = (provider) => configured instanceof Set ? configured.has(provider) : false;
|
|
2433
|
+
return available.map((m) => ({
|
|
2434
|
+
id: m.id,
|
|
2435
|
+
name: m.name,
|
|
2436
|
+
provider: m.provider,
|
|
2437
|
+
hasCredentials: isConfigured(m.provider),
|
|
2438
|
+
}));
|
|
2439
|
+
},
|
|
2440
|
+
};
|
|
2441
|
+
}
|
|
2442
|
+
/**
|
|
2443
|
+
* Re-export the tool gate for the dispatcher layer so the per-tool-call gate
|
|
2444
|
+
* (Gate 3) lives in exactly one place.
|
|
2445
|
+
*/
|
|
2446
|
+
export { authorizeOperatorChatTool, assertNoWriteToolInList, OPERATOR_CHAT_ALLOWED_TOOLS, };
|