@tea-agent/loop-agent 0.43.0-next.1 → 0.43.0-next.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +129 -9
- package/dist/application/dag/run-dag.js +5 -0
- package/dist/application/evaluation/budget.js +19 -1
- package/dist/application/task-lifecycle/advance.js +17 -4
- package/dist/application/task-lifecycle/observe.js +43 -29
- package/dist/application/task-lifecycle/plan-transitions.js +5 -4
- package/dist/build-stamp.json +3 -3
- package/dist/executors/dag-pi-executor.js +1214 -584
- package/dist/executors/pi-executor.js +22 -1
- package/dist/executors/pi-sdk-executor.js +140 -39
- package/dist/executors/shell-executor.js +5 -2
- package/dist/shared/frontend-execution-policy.js +23 -0
- package/dist/task/config-types.js +4 -0
- package/dist/task/source-prepare/ledger-reconciliation.js +2 -2
- package/dist/task/source-prepare/ledger-review.js +6 -9
- package/dist/task/source-prepare/semantic-intake.js +16 -26
- package/dist/task/source-prepare/source-fidelity-pi.js +28 -9
- package/dist/task/source-references.js +48 -23
- package/dist/worker/console/chat/assistant-content.js +23 -2
- package/dist/worker/console/chat/browser-policy.js +143 -0
- package/dist/worker/console/chat/browser-routes.js +148 -0
- package/dist/worker/console/chat/explore-tools.js +13 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/ask-user-question.js +44 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/explore-tools.js +202 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/goal-tools.js +78 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/operator-tools.js +86 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/todo-write.js +38 -0
- package/dist/worker/console/chat/pi-runtime/inspection-types.js +28 -0
- package/dist/worker/console/chat/pi-runtime/inspection.js +439 -0
- package/dist/worker/console/chat/pi-runtime/sdk-bindings.js +153 -0
- package/dist/worker/console/chat/pi-runtime/sdk-session.js +5 -0
- package/dist/worker/console/chat/pi-runtime.js +213 -1181
- package/dist/worker/console/chat/routes.js +212 -71
- package/dist/worker/console/chat/scm-routes.js +217 -0
- package/dist/worker/console/chat/scm-service.js +283 -0
- package/dist/worker/console/chat/scm-tools.js +111 -0
- package/dist/worker/console/chat/sdd-data-alignment.js +65 -9
- package/dist/worker/console/chat/session-catalog.js +32 -0
- package/dist/worker/console/chat/session-mode-view.js +48 -0
- package/dist/worker/console/chat/session-mode.js +218 -0
- package/dist/worker/console/chat/session-store.js +22 -6
- package/dist/worker/console/chat/shortcuts.js +6 -0
- package/dist/worker/console/chat/terminal-routes.js +216 -0
- package/dist/worker/console/chat/terminal-sessions.js +348 -0
- package/dist/worker/console/chat/terminal-tools.js +211 -0
- package/dist/worker/console/chat/tool-preview.js +11 -0
- package/dist/worker/console/chat/tools.js +2 -0
- package/dist/worker/console/server.js +2 -28
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-B0Q0nClf.js → abnfDiagram-N423BO3Z-D_fNbWc_.js} +1 -1
- package/dist/worker/console/static/assets/{arc-DCPjC19G.js → arc-_HuqLW6m.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CP5n9jmG.js → architectureDiagram-T3A2C74G-Djy2qY1m.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-CmaWi_Wg.js → blockDiagram-VBNYF7ZC-bB-1TSyY.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-Ct5tcMfZ.js → c4Diagram-5PPSVZJV-C5Hmghrz.js} +1 -1
- package/dist/worker/console/static/assets/channel-C0A6UEIa.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-C0osm1Zf.js → chunk-2GRJ4B5K-Duyl39oy.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-CDWviED7.js → chunk-2Q5K7J3B-x6mdnSyf.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-mtSk1-j7.js → chunk-5RXB4S5H-WlBuFj7W.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-D5J9PHC7.js → chunk-5VM5RSS4-CTF429HX.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-Begg4WAa.js → chunk-6Q2QTUOP-Dwd6r-SF.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-sF1AEy7T.js → chunk-GF5L2VYU-C-Br82D1.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BqbsWXZb.js → chunk-JWPE2WC7-CuGp0DeE.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BKJrPcJ6.js → chunk-KBJHAD2P-CJhFX6Lh.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CcwNMRho.js → chunk-RYQCIY6F-DpGOa_jT.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-BEJi3iIs.js → chunk-XXDRQBXY-DXJaTynd.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DzguNppy.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DzguNppy.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-DRLgTvVu.js → cose-bilkent-JH36ORCC-CRBZWjE4.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BoAVG3cs.js → cynefin-VYW2F7L2-BdXcxtlb.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Bzol06hR.js → cynefinDiagram-MW4NZA55-07UKsOts.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-UmliJM77.js → dagre-VZM6K2ZE-D38p9__S.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-BWLcJkFo.js → diagram-7IWD3JNH-nSiBNNjH.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-CgApOm-O.js → diagram-B4RE2ZJO-BFOTot5D.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-Bh3RgTLs.js → diagram-LBJQPF4R-DypKaZ6k.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-Dsh-D5nE.js → diagram-Q27KOJAE-B6yoOQeW.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-Dkbbpcpb.js → diagram-UB23O5K3-BHYS6viX.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-3EBaA3t3.js → ebnfDiagram-BXEA7PRR-D2VD5Snj.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-Bq2Dc5ok.js → erDiagram-JOGREHBK-E98mWxN1.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-De7y55ha.js → flowDiagram-UKHOOZJN-CT1zxhqP.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-fLiDbQRh.js → ganttDiagram-PKOTCBZU-6GcngNcy.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Du617mp1.js → gitGraphDiagram-DS77QQ5N-DCL6NvKe.js} +1 -1
- package/dist/worker/console/static/assets/index-24jp3aBh.js +468 -0
- package/dist/worker/console/static/assets/index-CY6y6I8M.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-BwmlF-XP.js → infoDiagram-6WML65LV-C0627e-9.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-zsGtS59u.js → ishikawaDiagram-WSZJBQD7-C_u3P6Ik.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DTTPTe3f.js → journeyDiagram-NVQOT4AX-DPDPv9HL.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BkXg9aP-.js → kanban-definition-27J2QSJJ-D3OHdWiO.js} +1 -1
- package/dist/worker/console/static/assets/{linear-7U2ue5IE.js → linear-DI88zD7n.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-BUuGHmWO.js → mermaid.core-p2l8IrWY.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-7q6fX0Sv.js → mindmap-definition-FAOFIHXS-B1bAX-oS.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-CkQPJN55.js → pegDiagram-VL7TDLO6-DwOnWxdD.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-CCal_tfX.js → pieDiagram-7S7Q4E2Y-xRkTXnJC.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-B7bpxyAZ.js → quadrantDiagram-CIZ2JOQS-_9UC5Zio.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-CJmww7D-.js → railroadDiagram-AXF67PYL-CgEUirWJ.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-D6ldWJEv.js → requirementDiagram-LRYGKXZP-Czbpwiyz.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-VGh33I9e.js → sankeyDiagram-W5VNT64P-_j7_KiqY.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-CCkDrIjU.js → sequenceDiagram-SI44F4Z6-BNJa6Hyi.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-NC8f5Otb.js → sizeCapture-X5ZJPWSS-DedpKtOc.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-_92ezZdF.js → stateDiagram-OKZ733FA-BBvthr7c.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BABVQnfM.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-BdUCGtbP.js → swimlanes-SLNWSIFB-Ds7umgEM.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-D0qsLaxR.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CdD1H8ia.js → timeline-definition-Z64GVDOM-CKa-p1nF.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-Dkdk3oFo.js → vennDiagram-T6HMQDX7-LoUNR56o.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CKu2uPPY.js → wardleyDiagram-T6FBY63Y-Bqbl6ym7.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-ClqAnuG8.js → xychartDiagram-ELKLHX3M-B8dUg4lf.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/active-turn-tail.js +10 -0
- package/dist/worker/console/static-src/operator-chat/chat-link.js +94 -0
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +122 -10
- package/dist/worker/console/static-src/operator-chat/compaction-message.js +7 -0
- package/dist/worker/console/static-src/operator-chat/pending-user-message.js +27 -15
- package/dist/worker/console/static-src/operator-chat/process-label.js +41 -0
- package/dist/worker/console/static-src/operator-chat/turn-group-equality.js +13 -0
- package/dist/worker/console/static-src/operator-chat/turn-process-disclosure.js +10 -0
- package/dist/worker/console/static-src/operator-chat/use-searchable-hidden.js +51 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +44 -24
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +18 -2
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +41 -8
- package/dist/worker/console/static-src/operator-chat/useComposer.js +26 -11
- package/dist/worker/console/static-src/operator-chat/user-turn-anchor-equality.js +21 -0
- package/dist/worker/console/workspace-context.js +15 -1
- package/dist/worker/observe/node-transparency.js +81 -72
- package/dist/worker/observe/routes.js +20 -1
- package/dist/worker/observe/static/dag-inspector-humanize.js +3 -0
- package/dist/worker/observe/static/dom.js +20 -1
- package/dist/worker/observe/static/format-pool.d.ts +2 -0
- package/dist/worker/observe/static/format-pool.js +6 -0
- package/dist/worker/observe/static/format.js +7 -0
- package/dist/worker/observe/static/inspect-workspace.js +34 -7
- package/dist/worker/observe/static/inspector-submission.js +32 -0
- package/dist/worker/observe/static/kpi.js +1 -0
- package/dist/worker/observe/static/relations.js +2 -0
- package/dist/worker/observe/static/router.js +13 -0
- package/dist/worker/observe/static/run-processing.js +2 -0
- package/dist/worker/observe/static/shell-chrome.js +36 -3
- package/dist/worker/observe/static/state.js +35 -2
- package/dist/worker/observe/static/styles.css +263 -39
- package/dist/worker/observe/static/task-failure-labels.d.ts +4 -0
- package/dist/worker/observe/static/task-failure-labels.js +67 -0
- package/dist/worker/observe/static/task-history.js +12 -0
- package/dist/worker/observe/static/views/batch.js +6 -13
- package/dist/worker/observe/static/views/dag-graph.js +50 -3
- package/dist/worker/observe/static/views/dag-inspector.js +742 -268
- package/dist/worker/observe/static/views/dag-trajectory.js +3 -0
- package/dist/worker/observe/static/views/dag.d.ts +6 -0
- package/dist/worker/observe/static/views/dag.js +49 -11
- package/dist/worker/observe/static/views/dags.js +2 -0
- package/dist/worker/observe/static/views/dashboard.js +21 -12
- package/dist/worker/observe/static/views/failures.js +21 -11
- package/dist/worker/observe/static/views/feature.js +11 -29
- package/dist/worker/observe/static/views/pool.js +37 -28
- package/dist/worker/observe/static/views/run.js +48 -5
- package/dist/worker/observe/static/views/session-timeline.js +189 -240
- package/dist/worker/observe/static/views/task.js +81 -62
- package/dist/workflows/dag/budget-enforcement.js +53 -3
- package/dist/workflows/dag/frontend-capacity.js +9 -0
- package/dist/workflows/dag/frontend-design-policy.js +2 -1
- package/dist/workflows/dag/frontend-durable-tools.js +193 -0
- package/dist/workflows/dag/frontend-execution-groups.js +24 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +13 -0
- package/dist/workflows/dag/frontend-input-projection.js +76 -0
- package/dist/workflows/dag/frontend-plan-completeness.js +186 -0
- package/dist/workflows/dag/frontend-plan-recovery-policy.js +18 -0
- package/dist/workflows/dag/frontend-plan-render.js +10 -3
- package/dist/workflows/dag/frontend-recovery-controller.js +7 -7
- package/dist/workflows/dag/frontend-recovery-lineage.js +13 -0
- package/dist/workflows/dag/frontend-recovery-run.js +4 -0
- package/dist/workflows/dag/frontend-review-scopes.js +139 -0
- package/dist/workflows/dag/frontend-session-budget.js +249 -0
- package/dist/workflows/dag/frontend-shadow-dual-write.js +20 -2
- package/dist/workflows/dag/frontend-shape-facts.js +12 -2
- package/dist/workflows/dag/frontend-shape.js +30 -1
- package/dist/workflows/dag/frontend-test-execution-evidence.js +3 -2
- package/dist/workflows/dag/frontend-typed-event-store.js +11 -0
- package/dist/workflows/dag/init-hybrid.js +16 -10
- package/dist/workflows/dag/node-execution.js +38 -155
- package/dist/workflows/dag/prompt.js +4 -0
- package/dist/workflows/dag/rerun-plan.js +7 -1
- package/dist/workflows/dag/runner.js +26 -1
- package/dist/workflows/dag/types.js +6 -0
- package/docs/operations/README.md +1 -0
- package/docs/templates/frontend-design-contract.md +4 -4
- package/docs/templates/frontend-implementation-contract.schema.json +34 -2
- package/docs/templates/frontend-implementation-dag.json +5 -5
- package/package.json +6 -3
- package/skills/frontend-contract/SKILL.md +2 -1
- package/skills/frontend-contract/references/contract-protocol.md +19 -3
- package/skills/frontend-design-review/SKILL.md +12 -11
- package/skills/frontend-plan/SKILL.md +9 -2
- package/skills/frontend-plan/references/decision-contract.md +18 -5
- package/skills/frontend-review/SKILL.md +10 -11
- package/skills/frontend-scout/references/scout-evidence.md +4 -0
- package/dist/worker/console/static/assets/channel-DAS07MdS.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Cwk-5jiW.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Cwk-5jiW.js +0 -1
- package/dist/worker/console/static/assets/index-C0O48S_P.js +0 -449
- package/dist/worker/console/static/assets/index-CzKf4U8P.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DyJg5gzW.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-B1cw_0Uy.js +0 -8
|
@@ -26,25 +26,35 @@
|
|
|
26
26
|
* `createDefaultPiSdkBindings()`.
|
|
27
27
|
*/
|
|
28
28
|
import { createHash, randomBytes } from "node:crypto";
|
|
29
|
-
import {
|
|
29
|
+
import { stat } from "node:fs/promises";
|
|
30
30
|
import path from "node:path";
|
|
31
31
|
import { extractAssistantContentBlocks, extractAssistantText, isAssistantSdkMessage, } from "./assistant-content.js";
|
|
32
32
|
import { parseProviderError, } from "./provider-error.js";
|
|
33
33
|
import { projectCompactSnapshot, } from "./chat-event-store.js";
|
|
34
34
|
import { extractUsageSample } from "./usage.js";
|
|
35
|
-
import { OPERATOR_CHAT_ALLOWED_TOOLS,
|
|
35
|
+
import { OPERATOR_CHAT_ALLOWED_TOOLS, authorizeOperatorChatTool, assertNoWriteToolInList, } from "./tools.js";
|
|
36
|
+
import { authorizeChatSessionTool, DEFAULT_CONSOLE_CHAT_SESSION_MODE, chatActiveToolBaseline, composeChatSystemPrompt, composeChatSystemPromptParts, computeChatActiveToolNames, shouldInjectGoalContinuation, shouldLoadWorkflowBusinessTools, } from "./session-mode.js";
|
|
36
37
|
import { buildAgentCustomTool, ExploreOrchestrator, } from "./subagents/index.js";
|
|
37
38
|
import { createOperatorChatResourceLoader, } from "./resource-loader.js";
|
|
38
|
-
import { applySkillAutoInvocationPreferences,
|
|
39
|
-
import { buildModelCallableToolSchemas } from "./tool-adapter.js";
|
|
39
|
+
import { applySkillAutoInvocationPreferences, loadResourcePreferences, } from "./resource-preferences-store.js";
|
|
40
40
|
import { applyProcessCompactionOverride, createCompactionController, createContextPressureExtension, createSiftBridgeState, housekeepSiftStash, isConsoleSiftEnabled, parseCompactionPolicy, parseSiftBridgeConfig, projectPromptPressure, deriveEffectiveWindow, projectSafeTelemetry, emptyContextPressureCounters, } from "../../../shared/pi-context-pressure/index.js";
|
|
41
41
|
import { incrementCounter as incrementPressureCounter } from "../../../shared/pi-context-pressure/telemetry.js";
|
|
42
42
|
import { resolveDefaultChatModel, resolveLowChatModel, } from "./model-resolver.js";
|
|
43
43
|
import { filterActiveInterviewTools } from "../interview/tools.js";
|
|
44
|
-
import {
|
|
44
|
+
import { redactRuntimeText } from "./runtime-context.js";
|
|
45
45
|
import { buildTodoContinuationContext, } from "./todos.js";
|
|
46
|
+
import { buildPersistentTerminalTools } from "./terminal-tools.js";
|
|
47
|
+
import { buildScmTools } from "./scm-tools.js";
|
|
46
48
|
import { buildGoalContinuationContext, } from "./goals.js";
|
|
47
49
|
import { injectWizardLocalHeaders, } from "./sdd-data-alignment.js";
|
|
50
|
+
import { buildCustomOperatorTools } from "./pi-runtime/custom-tools/operator-tools.js";
|
|
51
|
+
import { buildAskUserQuestionTool } from "./pi-runtime/custom-tools/ask-user-question.js";
|
|
52
|
+
import { buildTodoWriteTool } from "./pi-runtime/custom-tools/todo-write.js";
|
|
53
|
+
import { buildGoalTools } from "./pi-runtime/custom-tools/goal-tools.js";
|
|
54
|
+
import { buildExploreCustomTools } from "./pi-runtime/custom-tools/explore-tools.js";
|
|
55
|
+
import { THINKING_LEVELS, RuntimeSnapshotUnavailableError, } from "./pi-runtime/inspection-types.js";
|
|
56
|
+
import { collectRuntimeInventories, collectSlashCommands, projectRuntimeSnapshot, } from "./pi-runtime/inspection.js";
|
|
57
|
+
import { createDefaultPiSdkBindings, safeGetAgentDir, } from "./pi-runtime/sdk-bindings.js";
|
|
48
58
|
/**
|
|
49
59
|
* Base system prompt fragment every General Operator Chat session receives.
|
|
50
60
|
*
|
|
@@ -75,15 +85,6 @@ export const OPERATOR_CHAT_SYSTEM_PROMPT_BASE = [
|
|
|
75
85
|
"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.",
|
|
76
86
|
"Narrate long-DAG progress briefly at start, meaningful phase changes/findings, verification failure/recovery, prolonged quiet and terminal state: say what finished and what is next. Never narrate unchanged operationWait polls or heartbeats, or claim beyond structured facts.",
|
|
77
87
|
].join("\n");
|
|
78
|
-
/** Compose the inspectable system prompt actually injected into Operator Chat. */
|
|
79
|
-
export function composeOperatorChatSystemPrompt(input) {
|
|
80
|
-
return [
|
|
81
|
-
OPERATOR_CHAT_SYSTEM_PROMPT_BASE,
|
|
82
|
-
...(input.systemPromptSuffix?.trim()
|
|
83
|
-
? [input.systemPromptSuffix.trim()]
|
|
84
|
-
: []),
|
|
85
|
-
].join("\n\n");
|
|
86
|
-
}
|
|
87
88
|
/**
|
|
88
89
|
* Built-in SDK tool names registered for every Chat session (ADR 0011).
|
|
89
90
|
* Full Pi builtins: read/write/edit/bash/grep/find/ls. safe-* custom tools
|
|
@@ -112,27 +113,8 @@ export const OPERATOR_CHAT_SAFE_EXPLORE_TOOL_IDS = Object.freeze([
|
|
|
112
113
|
"git-diff",
|
|
113
114
|
]);
|
|
114
115
|
export function OPERATOR_CHAT_ACTIVE_TOOL_NAMES() {
|
|
115
|
-
return
|
|
116
|
-
...OPERATOR_CHAT_ALLOWED_TOOLS,
|
|
117
|
-
...OPERATOR_CHAT_BUILTIN_EXPLORE_TOOLS,
|
|
118
|
-
...OPERATOR_CHAT_SAFE_EXPLORE_TOOL_IDS,
|
|
119
|
-
...OPERATOR_CHAT_BUILTIN_CUSTOM_TOOLS,
|
|
120
|
-
];
|
|
116
|
+
return chatActiveToolBaseline("workflow");
|
|
121
117
|
}
|
|
122
|
-
/**
|
|
123
|
-
* Non-Pi write / coding channels that must never be activated in Operator
|
|
124
|
-
* Chat even when a user/project extension registers them (ADR 0011 / 0012).
|
|
125
|
-
* Mirrors the NON_PI_WRITE_CHANNELS guard in tools.ts for the Gate 2 pin.
|
|
126
|
-
*/
|
|
127
|
-
const OPERATOR_CHAT_EXTENSION_DENIED_TOOL_IDS = new Set([
|
|
128
|
-
"apply_patch",
|
|
129
|
-
"apply-patch",
|
|
130
|
-
"full-tools",
|
|
131
|
-
"full_tools",
|
|
132
|
-
"coding-chat",
|
|
133
|
-
"coding_chat",
|
|
134
|
-
"shell",
|
|
135
|
-
].map((t) => t.toLowerCase()));
|
|
136
118
|
/**
|
|
137
119
|
* Gate 2 active-tool set for a live session (ADR 0012): the operator-chat
|
|
138
120
|
* baseline (operator actions + Pi builtins + safe explore) PLUS extension
|
|
@@ -143,24 +125,8 @@ const OPERATOR_CHAT_EXTENSION_DENIED_TOOL_IDS = new Set([
|
|
|
143
125
|
* coding-chat). Unknown/denied names are never activated; Gate 3 still
|
|
144
126
|
* re-authorizes every tool call per invocation.
|
|
145
127
|
*/
|
|
146
|
-
export function computeOperatorChatActiveToolNames(session) {
|
|
147
|
-
|
|
148
|
-
if (!session?.getAllTools)
|
|
149
|
-
return baseline;
|
|
150
|
-
const baselineLower = new Set(baseline.map((t) => t.toLowerCase()));
|
|
151
|
-
const extras = [];
|
|
152
|
-
for (const tool of session.getAllTools()) {
|
|
153
|
-
const name = tool?.name;
|
|
154
|
-
if (!name || typeof name !== "string")
|
|
155
|
-
continue;
|
|
156
|
-
const lower = name.toLowerCase();
|
|
157
|
-
if (baselineLower.has(lower))
|
|
158
|
-
continue;
|
|
159
|
-
if (OPERATOR_CHAT_EXTENSION_DENIED_TOOL_IDS.has(lower))
|
|
160
|
-
continue;
|
|
161
|
-
extras.push(name);
|
|
162
|
-
}
|
|
163
|
-
return extras.length > 0 ? [...baseline, ...extras] : baseline;
|
|
128
|
+
export function computeOperatorChatActiveToolNames(session, sessionMode = DEFAULT_CONSOLE_CHAT_SESSION_MODE) {
|
|
129
|
+
return computeChatActiveToolNames(session, sessionMode);
|
|
164
130
|
}
|
|
165
131
|
const OPERATOR_CHAT_SESSION_EXCLUDE_TOOLS = Object.freeze([
|
|
166
132
|
"apply_patch",
|
|
@@ -216,8 +182,8 @@ async function bindSessionExtensions(session) {
|
|
|
216
182
|
return;
|
|
217
183
|
await session.bindExtensions(createConsoleExtensionBindings());
|
|
218
184
|
}
|
|
219
|
-
function pinOperatorChatTools(session) {
|
|
220
|
-
session.setActiveToolsByName(
|
|
185
|
+
function pinOperatorChatTools(session, sessionMode = DEFAULT_CONSOLE_CHAT_SESSION_MODE) {
|
|
186
|
+
session.setActiveToolsByName(computeChatActiveToolNames(session, sessionMode));
|
|
221
187
|
}
|
|
222
188
|
/**
|
|
223
189
|
* Pi has no hook result that merely appends instructions to its built-in
|
|
@@ -319,592 +285,8 @@ export function createWizardLocalHeadersExtension(options) {
|
|
|
319
285
|
},
|
|
320
286
|
};
|
|
321
287
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
* Each tool's execute() routes into the operator action dispatcher (Gate 3).
|
|
325
|
-
* Requires the `typebox` + `defineTool` packages (both bundled with the SDK).
|
|
326
|
-
*/
|
|
327
|
-
async function buildCustomOperatorTools(options) {
|
|
328
|
-
// typebox is a transitive dep of @earendil-works/pi-coding-agent; Node's
|
|
329
|
-
// resolver hoists to the SDK's nested copy. defineTool is re-exported by SDK.
|
|
330
|
-
const [{ Type }, { defineTool }] = await Promise.all([
|
|
331
|
-
import("typebox"),
|
|
332
|
-
import("@earendil-works/pi-coding-agent"),
|
|
333
|
-
]);
|
|
334
|
-
const schemas = buildModelCallableToolSchemas();
|
|
335
|
-
const ctx = options.actionContext;
|
|
336
|
-
return schemas.map((schema) => {
|
|
337
|
-
// TypeBox schema is built dynamically; cast through unknown to the builder
|
|
338
|
-
// since Static<TParams> is structural and cannot be statically inferred here.
|
|
339
|
-
const parameters = Type.Object(schema.inputParams.reduce((acc, param) => {
|
|
340
|
-
// Carry the registry's param description into the tool schema so
|
|
341
|
-
// the model sees parameter semantics, not just names/types.
|
|
342
|
-
const desc = param.description?.trim()
|
|
343
|
-
? { description: param.description }
|
|
344
|
-
: {};
|
|
345
|
-
let t;
|
|
346
|
-
switch (param.type) {
|
|
347
|
-
case "string":
|
|
348
|
-
t = Type.String(desc);
|
|
349
|
-
break;
|
|
350
|
-
case "boolean":
|
|
351
|
-
t = Type.Boolean(desc);
|
|
352
|
-
break;
|
|
353
|
-
case "number":
|
|
354
|
-
t = Type.Number(desc);
|
|
355
|
-
break;
|
|
356
|
-
case "array":
|
|
357
|
-
t = Type.Array(Type.String({}), Object.keys(desc).length ? desc : {});
|
|
358
|
-
break;
|
|
359
|
-
default:
|
|
360
|
-
t = Type.Object({}, { additionalProperties: true });
|
|
361
|
-
}
|
|
362
|
-
acc[param.name] = param.required ? t : Type.Optional(t);
|
|
363
|
-
return acc;
|
|
364
|
-
}, {}), { additionalProperties: false });
|
|
365
|
-
return defineTool({
|
|
366
|
-
name: schema.toolId,
|
|
367
|
-
label: schema.toolId,
|
|
368
|
-
description: `${schema.description} (kind=${schema.kind}; cli: ${schema.cli})`,
|
|
369
|
-
promptSnippet: schema.description,
|
|
370
|
-
parameters: parameters,
|
|
371
|
-
async execute(toolCallId, params) {
|
|
372
|
-
const decision = authorizeOperatorChatTool(schema.toolId);
|
|
373
|
-
if (!decision.ok) {
|
|
374
|
-
return {
|
|
375
|
-
content: [
|
|
376
|
-
{
|
|
377
|
-
type: "text",
|
|
378
|
-
text: JSON.stringify({
|
|
379
|
-
error: decision.message,
|
|
380
|
-
code: decision.code,
|
|
381
|
-
}),
|
|
382
|
-
},
|
|
383
|
-
],
|
|
384
|
-
details: { error: decision.message, code: decision.code },
|
|
385
|
-
};
|
|
386
|
-
}
|
|
387
|
-
// Defer require to avoid circular import at module load.
|
|
388
|
-
const { dispatchChatToolCall } = await import("./session-store.js");
|
|
389
|
-
const result = await dispatchChatToolCall(ctx, decision.toolId, params, toolCallId);
|
|
390
|
-
const payload = result.ok
|
|
391
|
-
? result.result
|
|
392
|
-
: { error: result.message, code: result.errorCode };
|
|
393
|
-
return {
|
|
394
|
-
content: [{ type: "text", text: JSON.stringify(payload) }],
|
|
395
|
-
details: payload,
|
|
396
|
-
};
|
|
397
|
-
},
|
|
398
|
-
});
|
|
399
|
-
});
|
|
400
|
-
}
|
|
401
|
-
const ASK_USER_QUESTION_DESCRIPTION = "Ask the user concise questions when a confirmation, choice, or missing detail is required before proceeding. " +
|
|
402
|
-
"The tool pauses until the user answers. Use it instead of writing questions as ordinary assistant text. " +
|
|
403
|
-
"Send one or more questions with stable ids; recommended options should be first and end with (Recommended).";
|
|
404
|
-
async function buildAskUserQuestionTool(options) {
|
|
405
|
-
const [{ Type }, { defineTool }] = await Promise.all([
|
|
406
|
-
import("typebox"),
|
|
407
|
-
import("@earendil-works/pi-coding-agent"),
|
|
408
|
-
]);
|
|
409
|
-
return defineTool({
|
|
410
|
-
name: "ask_user_question",
|
|
411
|
-
label: "Ask user",
|
|
412
|
-
description: ASK_USER_QUESTION_DESCRIPTION,
|
|
413
|
-
promptSnippet: ASK_USER_QUESTION_DESCRIPTION,
|
|
414
|
-
parameters: Type.Object({
|
|
415
|
-
questions: Type.Array(Type.Object({
|
|
416
|
-
id: Type.String({ description: "Stable question id echoed in the answer." }),
|
|
417
|
-
question: Type.String({ description: "Specific user-facing question." }),
|
|
418
|
-
header: Type.Optional(Type.String({ description: "Optional short heading." })),
|
|
419
|
-
detail: Type.Optional(Type.String({ description: "Optional Markdown context shown below the question." })),
|
|
420
|
-
options: Type.Optional(Type.Array(Type.Object({
|
|
421
|
-
label: Type.String({ description: "Short user-facing option label." }),
|
|
422
|
-
description: Type.Optional(Type.String({ description: "One sentence explaining impact or tradeoff." })),
|
|
423
|
-
}, { additionalProperties: false }))),
|
|
424
|
-
multi_select: Type.Optional(Type.Boolean({ description: "Allow more than one option." })),
|
|
425
|
-
}, { additionalProperties: false }), { minItems: 1, maxItems: 20 }),
|
|
426
|
-
}, { additionalProperties: false }),
|
|
427
|
-
async execute(toolCallId, params, signal) {
|
|
428
|
-
const answer = await options.registry.ask({
|
|
429
|
-
sessionId: options.sessionId,
|
|
430
|
-
toolCallId,
|
|
431
|
-
questions: params.questions,
|
|
432
|
-
signal,
|
|
433
|
-
});
|
|
434
|
-
return {
|
|
435
|
-
content: [{ type: "text", text: JSON.stringify(answer) }],
|
|
436
|
-
details: answer,
|
|
437
|
-
};
|
|
438
|
-
},
|
|
439
|
-
});
|
|
440
|
-
}
|
|
441
|
-
const TODO_WRITE_DESCRIPTION = "Record and update a structured task list for the current work. Send the ENTIRE list every call — it REPLACES the previous list (no partial updates, no per-item edits). Before starting multi-step work, add one todo per concrete step. Keep at least one todo `in_progress` while work remains. Mark a todo `completed` immediately when it is done (do not batch completions) and resend the full list as progress or DAG state advances. Skip the list for simple Q&A, read-only lookups, and single-step tasks. Use it for multi-step implementation and whenever you prepare, run, or supervise a DAG — call `todo_write` before entering the DAG tool chain. Phrase DAG todos as user-facing phases users can understand, not copies of internal DAG nodes. Statuses: `pending` / `in_progress` / `completed`.";
|
|
442
|
-
async function buildTodoWriteTool(options) {
|
|
443
|
-
const [{ Type }, { defineTool }] = await Promise.all([
|
|
444
|
-
import("typebox"),
|
|
445
|
-
import("@earendil-works/pi-coding-agent"),
|
|
446
|
-
]);
|
|
447
|
-
return defineTool({
|
|
448
|
-
name: "todo_write",
|
|
449
|
-
label: "Update todo list",
|
|
450
|
-
description: TODO_WRITE_DESCRIPTION,
|
|
451
|
-
promptSnippet: TODO_WRITE_DESCRIPTION,
|
|
452
|
-
parameters: Type.Object({
|
|
453
|
-
todos: Type.Array(Type.Object({
|
|
454
|
-
content: Type.String({ description: "Short imperative task description." }),
|
|
455
|
-
status: Type.Union([
|
|
456
|
-
Type.Literal("pending"),
|
|
457
|
-
Type.Literal("in_progress"),
|
|
458
|
-
Type.Literal("completed"),
|
|
459
|
-
]),
|
|
460
|
-
}, { additionalProperties: false })),
|
|
461
|
-
}, { additionalProperties: false }),
|
|
462
|
-
async execute(toolCallId, params) {
|
|
463
|
-
const result = options.registry.write(options.sessionId, toolCallId, params.todos);
|
|
464
|
-
return {
|
|
465
|
-
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
466
|
-
details: result,
|
|
467
|
-
};
|
|
468
|
-
},
|
|
469
|
-
});
|
|
470
|
-
}
|
|
471
|
-
const GET_GOAL_DESCRIPTION = "Read the current session's durable cross-turn Goal, including revision, status, round limit, and whether this Console process is authorized to continue it.";
|
|
472
|
-
const CREATE_GOAL_DESCRIPTION = "Create a durable cross-turn Goal only when the user explicitly asks to start or create a Goal. Never silently upgrade an ordinary request. A session may have only one unfinished Goal.";
|
|
473
|
-
const UPDATE_GOAL_DESCRIPTION = "Finish or block the current durable Goal using its exact goalId and revision. Set complete only when the objective is genuinely achieved and no required work remains. Set blocked only when safe progress cannot continue; include the blocker.";
|
|
474
|
-
async function buildGoalTools(options) {
|
|
475
|
-
const [{ Type }, { defineTool }] = await Promise.all([
|
|
476
|
-
import("typebox"),
|
|
477
|
-
import("@earendil-works/pi-coding-agent"),
|
|
478
|
-
]);
|
|
479
|
-
const textResult = (value) => ({
|
|
480
|
-
content: [{ type: "text", text: JSON.stringify(value) }],
|
|
481
|
-
details: value,
|
|
482
|
-
});
|
|
483
|
-
return [
|
|
484
|
-
defineTool({
|
|
485
|
-
name: "get_goal",
|
|
486
|
-
label: "Get goal",
|
|
487
|
-
description: GET_GOAL_DESCRIPTION,
|
|
488
|
-
promptSnippet: GET_GOAL_DESCRIPTION,
|
|
489
|
-
parameters: Type.Object({}, { additionalProperties: false }),
|
|
490
|
-
async execute() {
|
|
491
|
-
return textResult({ goal: options.registry.get(options.sessionId) });
|
|
492
|
-
},
|
|
493
|
-
}),
|
|
494
|
-
defineTool({
|
|
495
|
-
name: "create_goal",
|
|
496
|
-
label: "Create goal",
|
|
497
|
-
description: CREATE_GOAL_DESCRIPTION,
|
|
498
|
-
promptSnippet: CREATE_GOAL_DESCRIPTION,
|
|
499
|
-
parameters: Type.Object({
|
|
500
|
-
objective: Type.String({
|
|
501
|
-
description: "Concrete final objective and completion standard explicitly requested by the user.",
|
|
502
|
-
}),
|
|
503
|
-
}, { additionalProperties: false }),
|
|
504
|
-
async execute(_toolCallId, params) {
|
|
505
|
-
if (!options.registry.consumeModelCreateGrant(options.sessionId)) {
|
|
506
|
-
return textResult({
|
|
507
|
-
ok: false,
|
|
508
|
-
code: "GOAL_EXPLICIT_REQUEST_REQUIRED",
|
|
509
|
-
message: "create_goal requires an explicit user request to create or start a Goal",
|
|
510
|
-
});
|
|
511
|
-
}
|
|
512
|
-
return textResult(options.registry.create({
|
|
513
|
-
sessionId: options.sessionId,
|
|
514
|
-
objective: params.objective,
|
|
515
|
-
}));
|
|
516
|
-
},
|
|
517
|
-
}),
|
|
518
|
-
defineTool({
|
|
519
|
-
name: "update_goal",
|
|
520
|
-
label: "Update goal",
|
|
521
|
-
description: UPDATE_GOAL_DESCRIPTION,
|
|
522
|
-
promptSnippet: UPDATE_GOAL_DESCRIPTION,
|
|
523
|
-
parameters: Type.Object({
|
|
524
|
-
goalId: Type.String(),
|
|
525
|
-
revision: Type.Number({ minimum: 1 }),
|
|
526
|
-
status: Type.Union([
|
|
527
|
-
Type.Literal("complete"),
|
|
528
|
-
Type.Literal("blocked"),
|
|
529
|
-
]),
|
|
530
|
-
blocker: Type.Optional(Type.String()),
|
|
531
|
-
}, { additionalProperties: false }),
|
|
532
|
-
async execute(_toolCallId, params) {
|
|
533
|
-
return textResult(options.registry.update({
|
|
534
|
-
sessionId: options.sessionId,
|
|
535
|
-
goalId: params.goalId,
|
|
536
|
-
expectedRevision: params.revision,
|
|
537
|
-
phase: params.status,
|
|
538
|
-
blocker: params.blocker,
|
|
539
|
-
arm: false,
|
|
540
|
-
}));
|
|
541
|
-
},
|
|
542
|
-
}),
|
|
543
|
-
];
|
|
544
|
-
}
|
|
545
|
-
/**
|
|
546
|
-
* Build the safe explore custom tools (roadmap M0-A / G16): safe-read,
|
|
547
|
-
* safe-grep, gitStatus, gitDiff. These replace the bare SDK bash/read/grep so
|
|
548
|
-
* the model can probe the repo WITHOUT write escapes (echo redirect, tee,
|
|
549
|
-
* sed -i, node -e) and WITHOUT reading sensitive files (env files, key/pem,
|
|
550
|
-
* auth.json, sessions dir) — and every returned byte is secret-scrubbed
|
|
551
|
-
* before reaching the model context.
|
|
552
|
-
*
|
|
553
|
-
* gitStatus/gitDiff use a FIXED argv (typed, no shell) so they cannot be
|
|
554
|
-
* turned into an arbitrary-shell vector.
|
|
555
|
-
*/
|
|
556
|
-
async function buildExploreCustomTools(options) {
|
|
557
|
-
const [{ Type }, { defineTool }] = await Promise.all([
|
|
558
|
-
import("typebox"),
|
|
559
|
-
import("@earendil-works/pi-coding-agent"),
|
|
560
|
-
]);
|
|
561
|
-
const { execFileSync } = await import("node:child_process");
|
|
562
|
-
const repoRoot = options.repoRoot;
|
|
563
|
-
const { safeReadFile, safeGrep, scrubSecrets } = await import("./explore-tools.js");
|
|
564
|
-
function safeGit(args) {
|
|
565
|
-
try {
|
|
566
|
-
const stdout = execFileSync("git", args, {
|
|
567
|
-
cwd: repoRoot,
|
|
568
|
-
encoding: "utf8",
|
|
569
|
-
// Fixed argv, no shell, bounded output.
|
|
570
|
-
maxBuffer: 512 * 1024,
|
|
571
|
-
timeout: 10_000,
|
|
572
|
-
windowsHide: true,
|
|
573
|
-
});
|
|
574
|
-
return { ok: true, stdout };
|
|
575
|
-
}
|
|
576
|
-
catch (e) {
|
|
577
|
-
return {
|
|
578
|
-
ok: false,
|
|
579
|
-
code: "GIT_FAILED",
|
|
580
|
-
message: e instanceof Error ? e.message : String(e),
|
|
581
|
-
};
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
const safeReadTool = defineTool({
|
|
585
|
-
name: "safe-read",
|
|
586
|
-
label: "safe-read",
|
|
587
|
-
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.",
|
|
588
|
-
promptSnippet: "safe-read: read a repo file (safe; secrets scrubbed)",
|
|
589
|
-
parameters: Type.Object({
|
|
590
|
-
path: Type.String({ description: "repo-relative file path" }),
|
|
591
|
-
}, { additionalProperties: false }),
|
|
592
|
-
async execute(_toolCallId, params) {
|
|
593
|
-
const raw = String(params.path ?? "");
|
|
594
|
-
try {
|
|
595
|
-
const result = await safeReadFile(repoRoot, raw);
|
|
596
|
-
return {
|
|
597
|
-
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
598
|
-
details: result,
|
|
599
|
-
};
|
|
600
|
-
}
|
|
601
|
-
catch (e) {
|
|
602
|
-
const err = e;
|
|
603
|
-
return {
|
|
604
|
-
content: [
|
|
605
|
-
{
|
|
606
|
-
type: "text",
|
|
607
|
-
text: JSON.stringify({
|
|
608
|
-
error: err.message ?? String(e),
|
|
609
|
-
code: err.code ?? "READ_FAILED",
|
|
610
|
-
}),
|
|
611
|
-
},
|
|
612
|
-
],
|
|
613
|
-
details: {
|
|
614
|
-
error: err.message ?? String(e),
|
|
615
|
-
code: err.code ?? "READ_FAILED",
|
|
616
|
-
},
|
|
617
|
-
};
|
|
618
|
-
}
|
|
619
|
-
},
|
|
620
|
-
});
|
|
621
|
-
const safeGrepTool = defineTool({
|
|
622
|
-
name: "safe-grep",
|
|
623
|
-
label: "safe-grep",
|
|
624
|
-
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.",
|
|
625
|
-
promptSnippet: "safe-grep: search repo files (safe; secrets scrubbed)",
|
|
626
|
-
parameters: Type.Object({
|
|
627
|
-
pattern: Type.String({ description: "regex source (no shell)" }),
|
|
628
|
-
caseInsensitive: Type.Optional(Type.Boolean()),
|
|
629
|
-
glob: Type.Optional(Type.String({ description: "optional glob filter e.g. *.ts" })),
|
|
630
|
-
}, { additionalProperties: false }),
|
|
631
|
-
async execute(_toolCallId, params) {
|
|
632
|
-
try {
|
|
633
|
-
const result = await safeGrep(repoRoot, String(params.pattern ?? ""), {
|
|
634
|
-
caseInsensitive: Boolean(params.caseInsensitive),
|
|
635
|
-
glob: params.glob ? String(params.glob) : undefined,
|
|
636
|
-
});
|
|
637
|
-
return {
|
|
638
|
-
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
639
|
-
details: result,
|
|
640
|
-
};
|
|
641
|
-
}
|
|
642
|
-
catch (e) {
|
|
643
|
-
const err = e;
|
|
644
|
-
return {
|
|
645
|
-
content: [
|
|
646
|
-
{
|
|
647
|
-
type: "text",
|
|
648
|
-
text: JSON.stringify({
|
|
649
|
-
error: err.message ?? String(e),
|
|
650
|
-
code: err.code ?? "GREP_FAILED",
|
|
651
|
-
}),
|
|
652
|
-
},
|
|
653
|
-
],
|
|
654
|
-
details: {
|
|
655
|
-
error: err.message ?? String(e),
|
|
656
|
-
code: err.code ?? "GREP_FAILED",
|
|
657
|
-
},
|
|
658
|
-
};
|
|
659
|
-
}
|
|
660
|
-
},
|
|
661
|
-
});
|
|
662
|
-
const gitStatusTool = defineTool({
|
|
663
|
-
name: "git-status",
|
|
664
|
-
label: "git-status",
|
|
665
|
-
description: "Typed `git status --porcelain` (fixed argv, no shell). Returns scrubbed working-tree status.",
|
|
666
|
-
promptSnippet: "git-status: working tree status (read-only)",
|
|
667
|
-
parameters: Type.Object({}, { additionalProperties: false }),
|
|
668
|
-
async execute() {
|
|
669
|
-
const r = safeGit(["status", "--porcelain"]);
|
|
670
|
-
if (!r.ok) {
|
|
671
|
-
return {
|
|
672
|
-
content: [
|
|
673
|
-
{
|
|
674
|
-
type: "text",
|
|
675
|
-
text: JSON.stringify({ error: r.message, code: r.code }),
|
|
676
|
-
},
|
|
677
|
-
],
|
|
678
|
-
details: { error: r.message, code: r.code },
|
|
679
|
-
};
|
|
680
|
-
}
|
|
681
|
-
const { scrubbed, redactions } = scrubSecrets(r.stdout);
|
|
682
|
-
const payload = { status: scrubbed, redactions };
|
|
683
|
-
return {
|
|
684
|
-
content: [{ type: "text", text: JSON.stringify(payload) }],
|
|
685
|
-
details: payload,
|
|
686
|
-
};
|
|
687
|
-
},
|
|
688
|
-
});
|
|
689
|
-
const gitDiffTool = defineTool({
|
|
690
|
-
name: "git-diff",
|
|
691
|
-
label: "git-diff",
|
|
692
|
-
description: "Typed `git diff` (fixed argv: --stat or a single -- path; no shell). Returns scrubbed diff. path must be repo-relative and not sensitive.",
|
|
693
|
-
promptSnippet: "git-diff: diff (read-only, scrubbed)",
|
|
694
|
-
parameters: Type.Object({
|
|
695
|
-
path: Type.Optional(Type.String({
|
|
696
|
-
description: "optional repo-relative path to diff (default: whole tree --stat)",
|
|
697
|
-
})),
|
|
698
|
-
}, { additionalProperties: false }),
|
|
699
|
-
async execute(_toolCallId, params) {
|
|
700
|
-
let rawPath = params.path ? String(params.path) : undefined;
|
|
701
|
-
// If a path is supplied, validate it via the read-path guard so git diff
|
|
702
|
-
// cannot be aimed at sensitive files (.env* / auth.json / sessions).
|
|
703
|
-
if (rawPath) {
|
|
704
|
-
const { guardReadPath } = await import("./explore-tools.js");
|
|
705
|
-
const g = await guardReadPath(repoRoot, rawPath);
|
|
706
|
-
if (!g.ok) {
|
|
707
|
-
return {
|
|
708
|
-
content: [
|
|
709
|
-
{
|
|
710
|
-
type: "text",
|
|
711
|
-
text: JSON.stringify({ error: g.message, code: g.code }),
|
|
712
|
-
},
|
|
713
|
-
],
|
|
714
|
-
details: { error: g.message, code: g.code },
|
|
715
|
-
};
|
|
716
|
-
}
|
|
717
|
-
rawPath = g.repoRelative;
|
|
718
|
-
}
|
|
719
|
-
const args = rawPath ? ["diff", "--", rawPath] : ["diff", "--stat"];
|
|
720
|
-
const r = safeGit(args);
|
|
721
|
-
if (!r.ok) {
|
|
722
|
-
return {
|
|
723
|
-
content: [
|
|
724
|
-
{
|
|
725
|
-
type: "text",
|
|
726
|
-
text: JSON.stringify({ error: r.message, code: r.code }),
|
|
727
|
-
},
|
|
728
|
-
],
|
|
729
|
-
details: { error: r.message, code: r.code },
|
|
730
|
-
};
|
|
731
|
-
}
|
|
732
|
-
const { scrubbed, redactions } = scrubSecrets(r.stdout);
|
|
733
|
-
const payload = { diff: scrubbed, redactions };
|
|
734
|
-
return {
|
|
735
|
-
content: [{ type: "text", text: JSON.stringify(payload) }],
|
|
736
|
-
details: payload,
|
|
737
|
-
};
|
|
738
|
-
},
|
|
739
|
-
});
|
|
740
|
-
return [safeReadTool, safeGrepTool, gitStatusTool, gitDiffTool];
|
|
741
|
-
}
|
|
742
|
-
export const THINKING_LEVELS = [
|
|
743
|
-
"off",
|
|
744
|
-
"minimal",
|
|
745
|
-
"low",
|
|
746
|
-
"medium",
|
|
747
|
-
"high",
|
|
748
|
-
"xhigh",
|
|
749
|
-
"max",
|
|
750
|
-
];
|
|
751
|
-
/**
|
|
752
|
-
* Structured critical-failure error for snapshot projection (RF-02). Model
|
|
753
|
-
* enumeration or system prompt projection failures throw this so consuming
|
|
754
|
-
* routes can return 503 PI_RUNTIME_UNAVAILABLE instead of a fake-empty 200.
|
|
755
|
-
* Partial ResourceLoader / package-inventory failures are NOT critical — they
|
|
756
|
-
* keep the 200 snapshot and surface as structured diagnostics instead.
|
|
757
|
-
*/
|
|
758
|
-
export class RuntimeSnapshotUnavailableError extends Error {
|
|
759
|
-
code = "PI_RUNTIME_UNAVAILABLE";
|
|
760
|
-
constructor(message) {
|
|
761
|
-
super(message);
|
|
762
|
-
this.name = "RuntimeSnapshotUnavailableError";
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
/**
|
|
766
|
-
* Browser-safe projection of a live runtime snapshot (design §2.2 / RF-03).
|
|
767
|
-
*
|
|
768
|
-
* Every browser-visible string runs the shared secret-scrub chain
|
|
769
|
-
* (scrubSecrets + ghp_/Bearer redaction via redactRuntimeText) and a fixed
|
|
770
|
-
* per-field length limit: 200 (names/spec/source/origin), 400 (paths/errors/
|
|
771
|
-
* diagnostic messages), 600 (skill descriptions), 80 (package versions);
|
|
772
|
-
* truncation uses the fixed `…[truncated N chars …]` marker. The server-only
|
|
773
|
-
* `raw` prompt and the RAW-based `sha256`/`characters` pass through untouched,
|
|
774
|
-
* and real empty lists stay empty.
|
|
775
|
-
*
|
|
776
|
-
* Pure + deterministic and exported so the route harness reuses the exact
|
|
777
|
-
* production implementation (same code path as getRuntimeSnapshot).
|
|
778
|
-
*/
|
|
779
|
-
export function projectRuntimeSnapshot(input) {
|
|
780
|
-
const field = (value, max) => value === undefined ? undefined : redactRuntimeText(value, max);
|
|
781
|
-
const projectSkill = (skill) => ({
|
|
782
|
-
name: field(skill.name, 200) ?? "",
|
|
783
|
-
description: field(skill.description, 600) ?? "",
|
|
784
|
-
path: field(skill.path, 400) ?? "",
|
|
785
|
-
...(skill.baseDir ? { baseDir: field(skill.baseDir, 400) } : {}),
|
|
786
|
-
scope: skill.scope,
|
|
787
|
-
source: field(skill.source, 200) ?? "",
|
|
788
|
-
resourceId: field(skill.resourceId, 200) ?? "",
|
|
789
|
-
autoInvocationEnabled: skill.autoInvocationEnabled,
|
|
790
|
-
diagnostics: skill.diagnostics.map((diag) => field(diag, 400) ?? ""),
|
|
791
|
-
});
|
|
792
|
-
const projectExtension = (ext) => ({
|
|
793
|
-
name: field(ext.name, 200) ?? "",
|
|
794
|
-
path: field(ext.path, 400) ?? "",
|
|
795
|
-
...(ext.resolvedPath
|
|
796
|
-
? { resolvedPath: field(ext.resolvedPath, 400) }
|
|
797
|
-
: {}),
|
|
798
|
-
source: field(ext.source, 200) ?? "",
|
|
799
|
-
scope: ext.scope,
|
|
800
|
-
...(ext.origin ? { origin: field(ext.origin, 200) } : {}),
|
|
801
|
-
enabled: ext.enabled,
|
|
802
|
-
...(ext.error ? { error: field(ext.error, 400) } : {}),
|
|
803
|
-
});
|
|
804
|
-
const projectPackage = (pkg) => ({
|
|
805
|
-
spec: field(pkg.spec, 200) ?? "",
|
|
806
|
-
scope: pkg.scope,
|
|
807
|
-
enabled: pkg.enabled,
|
|
808
|
-
...(pkg.version ? { version: field(pkg.version, 80) } : {}),
|
|
809
|
-
...(pkg.configuredVersion
|
|
810
|
-
? { configuredVersion: field(pkg.configuredVersion, 80) }
|
|
811
|
-
: {}),
|
|
812
|
-
...(pkg.packageName ? { packageName: field(pkg.packageName, 200) } : {}),
|
|
813
|
-
...(pkg.path ? { path: field(pkg.path, 400) } : {}),
|
|
814
|
-
...(pkg.installedPath ? { installedPath: field(pkg.installedPath, 400) } : {}),
|
|
815
|
-
...(pkg.cwd ? { cwd: field(pkg.cwd, 400) } : {}),
|
|
816
|
-
resolved: pkg.resolved.map((resource) => ({
|
|
817
|
-
type: resource.type,
|
|
818
|
-
name: field(resource.name, 200) ?? "",
|
|
819
|
-
path: field(resource.path, 400) ?? "",
|
|
820
|
-
source: field(resource.source, 200) ?? "",
|
|
821
|
-
scope: resource.scope,
|
|
822
|
-
})),
|
|
823
|
-
diagnostics: pkg.diagnostics.map((diag) => field(diag, 400) ?? ""),
|
|
824
|
-
});
|
|
825
|
-
const projectMcpTool = (tool) => ({
|
|
826
|
-
name: field(tool.name, 200) ?? "",
|
|
827
|
-
description: field(tool.description, 600) ?? "",
|
|
828
|
-
...(tool.server ? { server: field(tool.server, 200) } : {}),
|
|
829
|
-
source: field(tool.source, 200) ?? "",
|
|
830
|
-
scope: tool.scope,
|
|
831
|
-
...(tool.path ? { path: field(tool.path, 400) } : {}),
|
|
832
|
-
active: tool.active,
|
|
833
|
-
});
|
|
834
|
-
const projectMcp = (mcp) => ({
|
|
835
|
-
servers: mcp.servers.map((server) => ({
|
|
836
|
-
name: field(server.name, 200) ?? "",
|
|
837
|
-
scope: server.scope,
|
|
838
|
-
status: server.status,
|
|
839
|
-
enabled: server.enabled,
|
|
840
|
-
...(server.transport ? { transport: field(server.transport, 80) } : {}),
|
|
841
|
-
...(server.command ? { command: field(server.command, 400) } : {}),
|
|
842
|
-
...(server.url ? { url: field(server.url, 400) } : {}),
|
|
843
|
-
...(server.configPath ? { configPath: field(server.configPath, 400) } : {}),
|
|
844
|
-
tools: server.tools.map(projectMcpTool),
|
|
845
|
-
})),
|
|
846
|
-
unassignedTools: mcp.unassignedTools.map(projectMcpTool),
|
|
847
|
-
});
|
|
848
|
-
const projectDiagnostic = (diag) => ({
|
|
849
|
-
type: diag.type,
|
|
850
|
-
...(diag.code ? { code: diag.code } : {}),
|
|
851
|
-
message: field(diag.message, 400) ?? "",
|
|
852
|
-
...(diag.path ? { path: field(diag.path, 400) } : {}),
|
|
853
|
-
...(diag.retryable !== undefined
|
|
854
|
-
? { retryable: diag.retryable }
|
|
855
|
-
: {}),
|
|
856
|
-
});
|
|
857
|
-
return {
|
|
858
|
-
sessionId: input.sessionId,
|
|
859
|
-
revision: input.revision,
|
|
860
|
-
loadedAt: input.loadedAt,
|
|
861
|
-
selection: {
|
|
862
|
-
...(input.selection.model
|
|
863
|
-
? {
|
|
864
|
-
model: {
|
|
865
|
-
provider: input.selection.model.provider,
|
|
866
|
-
id: input.selection.model.id,
|
|
867
|
-
...(input.selection.model.name
|
|
868
|
-
? { name: field(input.selection.model.name, 200) }
|
|
869
|
-
: {}),
|
|
870
|
-
},
|
|
871
|
-
}
|
|
872
|
-
: {}),
|
|
873
|
-
thinkingSelection: input.selection.thinkingSelection,
|
|
874
|
-
...(input.selection.effectiveThinkingLevel
|
|
875
|
-
? { effectiveThinkingLevel: input.selection.effectiveThinkingLevel }
|
|
876
|
-
: {}),
|
|
877
|
-
},
|
|
878
|
-
models: input.models.map((model) => ({
|
|
879
|
-
provider: model.provider,
|
|
880
|
-
id: model.id,
|
|
881
|
-
...(model.name ? { name: field(model.name, 200) } : {}),
|
|
882
|
-
...(model.hasCredentials !== undefined
|
|
883
|
-
? { hasCredentials: model.hasCredentials }
|
|
884
|
-
: {}),
|
|
885
|
-
})),
|
|
886
|
-
thinkingLevels: input.thinkingLevels.map((option) => ({ ...option })),
|
|
887
|
-
systemPrompt: {
|
|
888
|
-
...(input.systemPrompt.raw !== undefined
|
|
889
|
-
? { raw: input.systemPrompt.raw }
|
|
890
|
-
: {}),
|
|
891
|
-
text: redactRuntimeText(input.systemPrompt.text, RUNTIME_CONTEXT_TEXT_MAX),
|
|
892
|
-
sha256: input.systemPrompt.sha256,
|
|
893
|
-
characters: input.systemPrompt.characters,
|
|
894
|
-
},
|
|
895
|
-
activeTools: [...input.activeTools],
|
|
896
|
-
contextFiles: input.contextFiles.map((file) => ({
|
|
897
|
-
path: field(file.path, 400) ?? "",
|
|
898
|
-
characters: file.characters,
|
|
899
|
-
})),
|
|
900
|
-
skills: input.skills.map(projectSkill),
|
|
901
|
-
extensions: input.extensions.map(projectExtension),
|
|
902
|
-
packages: input.packages.map(projectPackage),
|
|
903
|
-
mcp: projectMcp(input.mcp ?? { servers: [], unassignedTools: [] }),
|
|
904
|
-
diagnostics: input.diagnostics.map(projectDiagnostic),
|
|
905
|
-
hasBash: input.hasBash,
|
|
906
|
-
};
|
|
907
|
-
}
|
|
288
|
+
export { THINKING_LEVELS, RuntimeSnapshotUnavailableError, projectRuntimeSnapshot, };
|
|
289
|
+
export { createDefaultPiSdkBindings } from "./pi-runtime/sdk-bindings.js";
|
|
908
290
|
export { extractAssistantContentBlocks, extractAssistantText, isAssistantSdkMessage };
|
|
909
291
|
function readSessionManagerId(manager) {
|
|
910
292
|
if (!manager || typeof manager !== "object")
|
|
@@ -1021,12 +403,14 @@ export class ConsolePiRuntime {
|
|
|
1021
403
|
sessionInit = new Map();
|
|
1022
404
|
/** Resolved model binding after materialization (create may return before this is known). */
|
|
1023
405
|
sessionModels = new Map();
|
|
406
|
+
/** Durable session mode; record is the source of truth, this is the live handle cache. */
|
|
407
|
+
sessionModes = new Map();
|
|
1024
408
|
/** SessionIds disposed while materialization was still in flight. */
|
|
1025
409
|
disposedSessions = new Set();
|
|
1026
410
|
compactionControllers = new Map();
|
|
1027
411
|
siftStates = new Map();
|
|
1028
412
|
/** Session-less tool definition caches (no AgentSession state). */
|
|
1029
|
-
|
|
413
|
+
operatorCustomToolsByMode = new Map();
|
|
1030
414
|
exploreCustomToolsPromise;
|
|
1031
415
|
exploreOrchestrator;
|
|
1032
416
|
bindings;
|
|
@@ -1054,6 +438,9 @@ export class ConsolePiRuntime {
|
|
|
1054
438
|
},
|
|
1055
439
|
});
|
|
1056
440
|
}
|
|
441
|
+
async syncAimaxAuthToken() {
|
|
442
|
+
await this.options.sddDataAlignment?.auth?.getToken();
|
|
443
|
+
}
|
|
1057
444
|
/** Absolute path for repo-scoped resource preferences (may be undefined in unit tests). */
|
|
1058
445
|
get resourcePreferencesPath() {
|
|
1059
446
|
return this.options.resourcePreferencesPath;
|
|
@@ -1082,10 +469,20 @@ export class ConsolePiRuntime {
|
|
|
1082
469
|
* after a PATCH sees the latest resource-preferences.json (not create-time
|
|
1083
470
|
* closure state).
|
|
1084
471
|
*/
|
|
472
|
+
sessionModeOf(sessionId) {
|
|
473
|
+
if (!sessionId)
|
|
474
|
+
return DEFAULT_CONSOLE_CHAT_SESSION_MODE;
|
|
475
|
+
return this.sessionModes.get(sessionId) ?? DEFAULT_CONSOLE_CHAT_SESSION_MODE;
|
|
476
|
+
}
|
|
477
|
+
rememberSessionMode(sessionId, sessionMode = DEFAULT_CONSOLE_CHAT_SESSION_MODE) {
|
|
478
|
+
this.sessionModes.set(sessionId, sessionMode);
|
|
479
|
+
return sessionMode;
|
|
480
|
+
}
|
|
1085
481
|
async buildResourceLoaderOptions(base = {}, sessionId) {
|
|
1086
482
|
const inheritedFactories = Array.isArray(base.extensionFactories)
|
|
1087
483
|
? base.extensionFactories
|
|
1088
484
|
: [];
|
|
485
|
+
const sessionMode = this.sessionModeOf(sessionId);
|
|
1089
486
|
const todoContextExtension = sessionId && this.options.todoRegistry
|
|
1090
487
|
? createTodoCompactionContextExtension({
|
|
1091
488
|
sessionId,
|
|
@@ -1095,7 +492,9 @@ export class ConsolePiRuntime {
|
|
|
1095
492
|
const pressureExtension = sessionId
|
|
1096
493
|
? this.createContextPressureInlineExtension(sessionId)
|
|
1097
494
|
: undefined;
|
|
1098
|
-
const goalContextExtension = sessionId &&
|
|
495
|
+
const goalContextExtension = sessionId &&
|
|
496
|
+
this.options.goalRegistry &&
|
|
497
|
+
shouldInjectGoalContinuation(sessionMode)
|
|
1099
498
|
? createGoalCompactionContextExtension({
|
|
1100
499
|
sessionId,
|
|
1101
500
|
registry: this.options.goalRegistry,
|
|
@@ -1341,8 +740,10 @@ export class ConsolePiRuntime {
|
|
|
1341
740
|
* `initializing: true`; dependent routes wait on the same promise.
|
|
1342
741
|
*/
|
|
1343
742
|
async createSession(init) {
|
|
743
|
+
await this.syncAimaxAuthToken();
|
|
1344
744
|
const sessionId = `operator-chat-${randomBytes(12).toString("hex")}`;
|
|
1345
745
|
this.disposedSessions.delete(sessionId);
|
|
746
|
+
const sessionMode = this.rememberSessionMode(sessionId, init?.sessionMode ?? DEFAULT_CONSOLE_CHAT_SESSION_MODE);
|
|
1346
747
|
const sessionManager = await this.bindings.createSessionManager({
|
|
1347
748
|
cwd: this.options.cwd,
|
|
1348
749
|
sessionDir: this.options.sessionDir,
|
|
@@ -1368,6 +769,7 @@ export class ConsolePiRuntime {
|
|
|
1368
769
|
sessionManager,
|
|
1369
770
|
model: init?.model,
|
|
1370
771
|
systemPromptSuffix: init?.systemPromptSuffix,
|
|
772
|
+
sessionMode,
|
|
1371
773
|
// Create path must resolve default model from the SAME services object
|
|
1372
774
|
// used for formal materialization (no throwaway createServices).
|
|
1373
775
|
resolveDefaultModel: !init?.model,
|
|
@@ -1377,7 +779,8 @@ export class ConsolePiRuntime {
|
|
|
1377
779
|
sessionFile,
|
|
1378
780
|
createdAt: new Date().toISOString(),
|
|
1379
781
|
model: init?.model,
|
|
1380
|
-
activeTools:
|
|
782
|
+
activeTools: chatActiveToolBaseline(sessionMode),
|
|
783
|
+
sessionMode,
|
|
1381
784
|
initializing: true,
|
|
1382
785
|
};
|
|
1383
786
|
}
|
|
@@ -1435,28 +838,33 @@ export class ConsolePiRuntime {
|
|
|
1435
838
|
this.sessionInit.set(sessionId, { status: "initializing", promise });
|
|
1436
839
|
return promise;
|
|
1437
840
|
}
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
841
|
+
/**
|
|
842
|
+
* Shared session preparation for create/reopen/fork: config capture, prompt
|
|
843
|
+
* composition, resource loading, model resolution, tool assembly, SDK session
|
|
844
|
+
* create, extension bind, tool pin, compaction override. Lifecycle maps stay
|
|
845
|
+
* owned by the caller.
|
|
846
|
+
*/
|
|
847
|
+
async prepareLiveSession(init) {
|
|
848
|
+
const aborted = () => Boolean(init.abortIfDisposed && this.disposedSessions.has(init.sessionId));
|
|
849
|
+
if (aborted())
|
|
850
|
+
return undefined;
|
|
1441
851
|
const agentDir = await safeGetAgentDir(this.bindings);
|
|
1442
852
|
// Capture before services parse models.json. A concurrent edit can therefore
|
|
1443
853
|
// cause at most one harmless extra refresh, never a missed next-turn refresh.
|
|
1444
854
|
const modelConfigFile = await captureModelConfigFile(agentDir);
|
|
1445
|
-
const appendSystemPrompt =
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
// default-model resolve from the same modelRuntime). Repo-scoped skill
|
|
1451
|
-
// auto-invocation prefs inject skillsOverride without touching SKILL.md.
|
|
855
|
+
const appendSystemPrompt = composeChatSystemPromptParts({
|
|
856
|
+
sessionMode: init.sessionMode,
|
|
857
|
+
workflowPrompt: OPERATOR_CHAT_SYSTEM_PROMPT_BASE,
|
|
858
|
+
systemPromptSuffix: init.systemPromptSuffix,
|
|
859
|
+
});
|
|
1452
860
|
await this.refreshSkillPrefsCache().catch(() => undefined);
|
|
1453
861
|
const { services } = await this.bindings.createServices({
|
|
1454
862
|
cwd: this.options.cwd,
|
|
1455
863
|
agentDir,
|
|
1456
|
-
resourceLoaderOptions: await this.buildResourceLoaderOptions({ appendSystemPrompt }, sessionId),
|
|
864
|
+
resourceLoaderOptions: await this.buildResourceLoaderOptions({ appendSystemPrompt }, init.sessionId),
|
|
1457
865
|
});
|
|
1458
|
-
if (
|
|
1459
|
-
return;
|
|
866
|
+
if (aborted())
|
|
867
|
+
return undefined;
|
|
1460
868
|
const modelRuntime = services.modelRuntime;
|
|
1461
869
|
let model = init.model;
|
|
1462
870
|
if (!model && init.resolveDefaultModel) {
|
|
@@ -1470,9 +878,9 @@ export class ConsolePiRuntime {
|
|
|
1470
878
|
}) ?? undefined)
|
|
1471
879
|
: undefined;
|
|
1472
880
|
assertNoWriteToolInList(OPERATOR_CHAT_ALLOWED_TOOLS);
|
|
1473
|
-
const customTools = await this.loadCustomTools(sessionId);
|
|
1474
|
-
if (
|
|
1475
|
-
return;
|
|
881
|
+
const customTools = await this.loadCustomTools(init.sessionId);
|
|
882
|
+
if (aborted())
|
|
883
|
+
return undefined;
|
|
1476
884
|
// pi-web parity: do NOT pass `tools`. A closed allowlist becomes
|
|
1477
885
|
// SDK allowedToolNames and filters extension MCP tools out of the
|
|
1478
886
|
// registry before Gate 2 can re-add them. Operator/safe customTools
|
|
@@ -1486,35 +894,59 @@ export class ConsolePiRuntime {
|
|
|
1486
894
|
...(customTools.length > 0 ? { customTools } : {}),
|
|
1487
895
|
...(resolvedModel ? { model: resolvedModel } : {}),
|
|
1488
896
|
});
|
|
1489
|
-
if (
|
|
897
|
+
if (aborted()) {
|
|
1490
898
|
try {
|
|
1491
899
|
session.dispose();
|
|
1492
900
|
}
|
|
1493
901
|
catch {
|
|
1494
902
|
// ignore late dispose cleanup
|
|
1495
903
|
}
|
|
1496
|
-
return;
|
|
904
|
+
return undefined;
|
|
1497
905
|
}
|
|
1498
906
|
await bindSessionExtensions(session);
|
|
1499
|
-
pinOperatorChatTools(session);
|
|
907
|
+
pinOperatorChatTools(session, init.sessionMode);
|
|
908
|
+
this.applySessionCompactionOverride(init.sessionId, services, session);
|
|
909
|
+
return {
|
|
910
|
+
session,
|
|
911
|
+
modelRuntime,
|
|
912
|
+
modelConfigFile,
|
|
913
|
+
model,
|
|
914
|
+
services: {
|
|
915
|
+
resourceLoader: services.resourceLoader,
|
|
916
|
+
settingsManager: services.settingsManager,
|
|
917
|
+
},
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
async materializeSession(sessionId, init) {
|
|
921
|
+
if (this.disposedSessions.has(sessionId))
|
|
922
|
+
return;
|
|
923
|
+
await this.syncAimaxAuthToken();
|
|
924
|
+
const sessionMode = this.rememberSessionMode(sessionId, init.sessionMode ?? this.sessionModeOf(sessionId));
|
|
925
|
+
const prepared = await this.prepareLiveSession({
|
|
926
|
+
sessionId,
|
|
927
|
+
sessionManager: init.sessionManager,
|
|
928
|
+
model: init.model,
|
|
929
|
+
systemPromptSuffix: init.systemPromptSuffix,
|
|
930
|
+
sessionMode,
|
|
931
|
+
resolveDefaultModel: init.resolveDefaultModel,
|
|
932
|
+
abortIfDisposed: true,
|
|
933
|
+
});
|
|
934
|
+
if (!prepared)
|
|
935
|
+
return;
|
|
1500
936
|
// Register then re-check dispose race: a concurrent dispose must win.
|
|
1501
|
-
this.sessions.set(sessionId, session);
|
|
937
|
+
this.sessions.set(sessionId, prepared.session);
|
|
1502
938
|
this.sessionManagers.set(sessionId, init.sessionManager);
|
|
1503
|
-
this.modelRuntimes.set(sessionId, modelRuntime);
|
|
1504
|
-
this.modelConfigFiles.set(sessionId, modelConfigFile);
|
|
1505
|
-
this.sessionModels.set(sessionId, model);
|
|
1506
|
-
this.serviceScopes.set(sessionId,
|
|
1507
|
-
resourceLoader: services.resourceLoader,
|
|
1508
|
-
settingsManager: services.settingsManager,
|
|
1509
|
-
});
|
|
1510
|
-
this.applySessionCompactionOverride(sessionId, services, session);
|
|
939
|
+
this.modelRuntimes.set(sessionId, prepared.modelRuntime);
|
|
940
|
+
this.modelConfigFiles.set(sessionId, prepared.modelConfigFile);
|
|
941
|
+
this.sessionModels.set(sessionId, prepared.model);
|
|
942
|
+
this.serviceScopes.set(sessionId, prepared.services);
|
|
1511
943
|
this.revisions.set(sessionId, 1);
|
|
1512
944
|
if (!this.thinkingSelections.has(sessionId)) {
|
|
1513
945
|
this.thinkingSelections.set(sessionId, "auto");
|
|
1514
946
|
}
|
|
1515
947
|
if (this.disposedSessions.has(sessionId)) {
|
|
1516
948
|
try {
|
|
1517
|
-
session.dispose();
|
|
949
|
+
prepared.session.dispose();
|
|
1518
950
|
}
|
|
1519
951
|
catch {
|
|
1520
952
|
// ignore
|
|
@@ -1524,6 +956,7 @@ export class ConsolePiRuntime {
|
|
|
1524
956
|
this.modelRuntimes.delete(sessionId);
|
|
1525
957
|
this.modelConfigFiles.delete(sessionId);
|
|
1526
958
|
this.sessionModels.delete(sessionId);
|
|
959
|
+
this.sessionModes.delete(sessionId);
|
|
1527
960
|
this.serviceScopes.delete(sessionId);
|
|
1528
961
|
this.revisions.delete(sessionId);
|
|
1529
962
|
this.thinkingSelections.delete(sessionId);
|
|
@@ -1531,8 +964,12 @@ export class ConsolePiRuntime {
|
|
|
1531
964
|
}
|
|
1532
965
|
/** Cached session-less custom tool definitions (operator + explore). */
|
|
1533
966
|
async loadCustomTools(sessionId) {
|
|
967
|
+
const sessionMode = this.sessionModeOf(sessionId);
|
|
968
|
+
const loadWorkflowTools = shouldLoadWorkflowBusinessTools(sessionMode);
|
|
1534
969
|
const [operatorCustomTools, exploreCustomTools] = await Promise.all([
|
|
1535
|
-
|
|
970
|
+
loadWorkflowTools
|
|
971
|
+
? this.loadOperatorCustomTools(sessionMode)
|
|
972
|
+
: Promise.resolve([]),
|
|
1536
973
|
this.loadExploreCustomTools(),
|
|
1537
974
|
]);
|
|
1538
975
|
const questionTool = this.options.questionRegistry
|
|
@@ -1542,11 +979,31 @@ export class ConsolePiRuntime {
|
|
|
1542
979
|
})
|
|
1543
980
|
: undefined;
|
|
1544
981
|
const todoTool = this.options.todoRegistry
|
|
1545
|
-
? await buildTodoWriteTool({
|
|
982
|
+
? await buildTodoWriteTool({
|
|
983
|
+
sessionId,
|
|
984
|
+
registry: this.options.todoRegistry,
|
|
985
|
+
sessionMode,
|
|
986
|
+
})
|
|
1546
987
|
: undefined;
|
|
1547
|
-
const goalTools = this.options.goalRegistry
|
|
988
|
+
const goalTools = loadWorkflowTools && this.options.goalRegistry
|
|
1548
989
|
? await buildGoalTools({ sessionId, registry: this.options.goalRegistry })
|
|
1549
990
|
: [];
|
|
991
|
+
const terminalTools = this.options.terminalRegistry
|
|
992
|
+
? await buildPersistentTerminalTools({
|
|
993
|
+
ownerSessionId: sessionId,
|
|
994
|
+
registry: this.options.terminalRegistry,
|
|
995
|
+
defaultCwd: this.options.cwd,
|
|
996
|
+
}).catch((error) => {
|
|
997
|
+
process.stderr.write(`[console] chat terminal-tools build failed: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
998
|
+
return [];
|
|
999
|
+
})
|
|
1000
|
+
: [];
|
|
1001
|
+
const scmTools = this.options.scmService
|
|
1002
|
+
? await buildScmTools({ scm: this.options.scmService }).catch((error) => {
|
|
1003
|
+
process.stderr.write(`[console] chat scm-tools build failed: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
1004
|
+
return [];
|
|
1005
|
+
})
|
|
1006
|
+
: [];
|
|
1550
1007
|
const agentTool = await buildAgentCustomTool({
|
|
1551
1008
|
parentSessionId: sessionId,
|
|
1552
1009
|
orchestrator: this.exploreOrchestrator,
|
|
@@ -1571,23 +1028,28 @@ export class ConsolePiRuntime {
|
|
|
1571
1028
|
...(questionTool ? [questionTool] : []),
|
|
1572
1029
|
...(todoTool ? [todoTool] : []),
|
|
1573
1030
|
...goalTools,
|
|
1031
|
+
...terminalTools,
|
|
1032
|
+
...scmTools,
|
|
1574
1033
|
...(agentTool ? [agentTool] : []),
|
|
1575
1034
|
];
|
|
1576
1035
|
}
|
|
1577
|
-
loadOperatorCustomTools() {
|
|
1036
|
+
loadOperatorCustomTools(sessionMode) {
|
|
1578
1037
|
if (!this.options.actionContext)
|
|
1579
1038
|
return Promise.resolve([]);
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1039
|
+
const cached = this.operatorCustomToolsByMode.get(sessionMode);
|
|
1040
|
+
if (cached)
|
|
1041
|
+
return cached;
|
|
1042
|
+
const promise = buildCustomOperatorTools({
|
|
1043
|
+
actionContext: this.options.actionContext,
|
|
1044
|
+
sessionMode,
|
|
1045
|
+
}).catch((error) => {
|
|
1046
|
+
process.stderr.write(`[console] chat custom-tools build failed: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
1047
|
+
// Allow a later create/reopen to retry after a transient failure.
|
|
1048
|
+
this.operatorCustomToolsByMode.delete(sessionMode);
|
|
1049
|
+
return [];
|
|
1050
|
+
});
|
|
1051
|
+
this.operatorCustomToolsByMode.set(sessionMode, promise);
|
|
1052
|
+
return promise;
|
|
1591
1053
|
}
|
|
1592
1054
|
loadExploreCustomTools() {
|
|
1593
1055
|
if (!this.exploreCustomToolsPromise) {
|
|
@@ -1605,6 +1067,7 @@ export class ConsolePiRuntime {
|
|
|
1605
1067
|
if (!this.bindings.forkSessionManager)
|
|
1606
1068
|
throw new Error("SESSION_FORK_UNSUPPORTED");
|
|
1607
1069
|
const sessionId = `operator-chat-${randomBytes(12).toString("hex")}`;
|
|
1070
|
+
const sessionMode = this.rememberSessionMode(sessionId, init.sessionMode ?? DEFAULT_CONSOLE_CHAT_SESSION_MODE);
|
|
1608
1071
|
const sessionManager = await this.bindings.forkSessionManager({
|
|
1609
1072
|
sourcePath: init.sourceSessionFile,
|
|
1610
1073
|
targetCwd: this.options.cwd,
|
|
@@ -1615,8 +1078,8 @@ export class ConsolePiRuntime {
|
|
|
1615
1078
|
sessionId,
|
|
1616
1079
|
sessionManager,
|
|
1617
1080
|
model: init.model,
|
|
1081
|
+
sessionMode,
|
|
1618
1082
|
});
|
|
1619
|
-
built.session.setActiveToolsByName(computeOperatorChatActiveToolNames(built.session));
|
|
1620
1083
|
this.sessions.set(sessionId, built.session);
|
|
1621
1084
|
this.sessionManagers.set(sessionId, sessionManager);
|
|
1622
1085
|
this.modelRuntimes.set(sessionId, built.modelRuntime);
|
|
@@ -1632,6 +1095,7 @@ export class ConsolePiRuntime {
|
|
|
1632
1095
|
createdAt: new Date().toISOString(),
|
|
1633
1096
|
model: init.model,
|
|
1634
1097
|
activeTools: built.session.getActiveToolNames(),
|
|
1098
|
+
sessionMode,
|
|
1635
1099
|
};
|
|
1636
1100
|
}
|
|
1637
1101
|
async branchSession(input) {
|
|
@@ -1810,15 +1274,22 @@ export class ConsolePiRuntime {
|
|
|
1810
1274
|
async getRuntimeSystemPrompt(sessionId) {
|
|
1811
1275
|
await this.ensureSessionReady(sessionId).catch(() => undefined);
|
|
1812
1276
|
const session = this.sessions.get(sessionId);
|
|
1813
|
-
if (!session)
|
|
1814
|
-
return
|
|
1277
|
+
if (!session) {
|
|
1278
|
+
return composeChatSystemPrompt({
|
|
1279
|
+
sessionMode: this.sessionModeOf(sessionId),
|
|
1280
|
+
workflowPrompt: OPERATOR_CHAT_SYSTEM_PROMPT_BASE,
|
|
1281
|
+
});
|
|
1282
|
+
}
|
|
1815
1283
|
const fromState = session.state?.systemPrompt;
|
|
1816
1284
|
const fromGetter = session.systemPrompt;
|
|
1817
1285
|
const loader = this.serviceScopes.get(sessionId)?.resourceLoader;
|
|
1818
1286
|
return (fromState ||
|
|
1819
1287
|
fromGetter ||
|
|
1820
1288
|
loader?.getSystemPrompt?.() ||
|
|
1821
|
-
|
|
1289
|
+
composeChatSystemPrompt({
|
|
1290
|
+
sessionMode: this.sessionModeOf(sessionId),
|
|
1291
|
+
workflowPrompt: OPERATOR_CHAT_SYSTEM_PROMPT_BASE,
|
|
1292
|
+
}));
|
|
1822
1293
|
}
|
|
1823
1294
|
/**
|
|
1824
1295
|
* Read the RAW system prompt for the snapshot, converting any projection
|
|
@@ -1911,6 +1382,7 @@ export class ConsolePiRuntime {
|
|
|
1911
1382
|
if (services)
|
|
1912
1383
|
this.applySessionCompactionOverride(sessionId, services, session);
|
|
1913
1384
|
await this.refreshModelConfiguration(sessionId, "force");
|
|
1385
|
+
pinOperatorChatTools(session, this.sessionModeOf(sessionId));
|
|
1914
1386
|
}
|
|
1915
1387
|
catch (error) {
|
|
1916
1388
|
return {
|
|
@@ -1941,91 +1413,14 @@ export class ConsolePiRuntime {
|
|
|
1941
1413
|
* Never returns prompt/skill file bodies.
|
|
1942
1414
|
*/
|
|
1943
1415
|
async listSlashCommands(sessionId) {
|
|
1944
|
-
const scrub = (value) => {
|
|
1945
|
-
const raw = String(value ?? "");
|
|
1946
|
-
return raw
|
|
1947
|
-
.replace(/\/Users\/[^\s]+/gi, "~")
|
|
1948
|
-
.replace(/[A-Za-z]:\\Users\\[^\s]+/gi, "~")
|
|
1949
|
-
.slice(0, 240);
|
|
1950
|
-
};
|
|
1951
1416
|
// Fail soft at the HTTP layer: bubble readiness errors so routes can attach
|
|
1952
1417
|
// a compact warning while the UI keeps local Operator/Pi Web commands.
|
|
1953
1418
|
await this.ensureSessionReady(sessionId);
|
|
1954
1419
|
const session = this.sessions.get(sessionId);
|
|
1955
1420
|
if (!session)
|
|
1956
1421
|
return { commands: [] };
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
const SOURCE_RANK = {
|
|
1960
|
-
extension: 0,
|
|
1961
|
-
skill: 1,
|
|
1962
|
-
prompt: 2,
|
|
1963
|
-
};
|
|
1964
|
-
const byKey = new Map();
|
|
1965
|
-
const sourceWarnings = [];
|
|
1966
|
-
const push = (command, label, description, source) => {
|
|
1967
|
-
const name = command.startsWith("/") ? command : `/${command}`;
|
|
1968
|
-
const key = name.toLowerCase();
|
|
1969
|
-
if (!key || key === "/")
|
|
1970
|
-
return;
|
|
1971
|
-
const next = {
|
|
1972
|
-
command: name,
|
|
1973
|
-
label: (label || name).slice(0, 80),
|
|
1974
|
-
description: scrub(description),
|
|
1975
|
-
source,
|
|
1976
|
-
};
|
|
1977
|
-
const existing = byKey.get(key);
|
|
1978
|
-
if (!existing || SOURCE_RANK[source] < SOURCE_RANK[existing.source]) {
|
|
1979
|
-
byKey.set(key, next);
|
|
1980
|
-
}
|
|
1981
|
-
};
|
|
1982
|
-
try {
|
|
1983
|
-
const cmds = session.extensionRunner?.getRegisteredCommands?.() ?? [];
|
|
1984
|
-
for (const cmd of cmds) {
|
|
1985
|
-
const name = cmd.invocationName || cmd.name;
|
|
1986
|
-
if (!name)
|
|
1987
|
-
continue;
|
|
1988
|
-
push(String(name), String(cmd.name || name), String(cmd.description || ""), "extension");
|
|
1989
|
-
}
|
|
1990
|
-
}
|
|
1991
|
-
catch (error) {
|
|
1992
|
-
// Source isolation: keep other sources; surface compact warning (AC-3).
|
|
1993
|
-
sourceWarnings.push(`extension: ${scrub(error instanceof Error ? error.message : String(error))}`);
|
|
1994
|
-
}
|
|
1995
|
-
try {
|
|
1996
|
-
// Enumerate prompts before skills on purpose: priority must still let skill win.
|
|
1997
|
-
for (const tpl of session.promptTemplates ?? []) {
|
|
1998
|
-
if (!tpl?.name)
|
|
1999
|
-
continue;
|
|
2000
|
-
push(String(tpl.name), String(tpl.name), String(tpl.description || ""), "prompt");
|
|
2001
|
-
}
|
|
2002
|
-
}
|
|
2003
|
-
catch (error) {
|
|
2004
|
-
sourceWarnings.push(`prompt: ${scrub(error instanceof Error ? error.message : String(error))}`);
|
|
2005
|
-
}
|
|
2006
|
-
try {
|
|
2007
|
-
const services = this.serviceScopes.get(sessionId);
|
|
2008
|
-
const resourceLoader = services?.resourceLoader;
|
|
2009
|
-
const skills = resourceLoader?.getSkills?.()?.skills ?? [];
|
|
2010
|
-
for (const skill of skills) {
|
|
2011
|
-
if (!skill?.name)
|
|
2012
|
-
continue;
|
|
2013
|
-
const command = skill.name.startsWith("skill:")
|
|
2014
|
-
? `/${skill.name}`
|
|
2015
|
-
: `/skill:${skill.name}`;
|
|
2016
|
-
push(command, skill.name, String(skill.description || ""), "skill");
|
|
2017
|
-
}
|
|
2018
|
-
}
|
|
2019
|
-
catch (error) {
|
|
2020
|
-
sourceWarnings.push(`skill: ${scrub(error instanceof Error ? error.message : String(error))}`);
|
|
2021
|
-
}
|
|
2022
|
-
const warning = sourceWarnings.length > 0
|
|
2023
|
-
? sourceWarnings.join("; ").slice(0, 160)
|
|
2024
|
-
: undefined;
|
|
2025
|
-
return {
|
|
2026
|
-
commands: [...byKey.values()],
|
|
2027
|
-
...(warning ? { warning } : {}),
|
|
2028
|
-
};
|
|
1422
|
+
const resourceLoader = this.serviceScopes.get(sessionId)?.resourceLoader;
|
|
1423
|
+
return collectSlashCommands({ session, resourceLoader });
|
|
2029
1424
|
}
|
|
2030
1425
|
async getRuntimeSnapshot(sessionId, options) {
|
|
2031
1426
|
try {
|
|
@@ -2069,9 +1464,6 @@ export class ConsolePiRuntime {
|
|
|
2069
1464
|
throw new RuntimeSnapshotUnavailableError(`model enumeration failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
2070
1465
|
}
|
|
2071
1466
|
}
|
|
2072
|
-
// Thinking options: auto is always offered; explicit levels only when the
|
|
2073
|
-
// runtime session supports setThinkingLevel (xhigh/max ship with the SDK
|
|
2074
|
-
// and therefore surface only when this runtime provides them).
|
|
2075
1467
|
const supportedLevels = typeof session.setThinkingLevel === "function"
|
|
2076
1468
|
? [...THINKING_LEVELS]
|
|
2077
1469
|
: [];
|
|
@@ -2079,220 +1471,25 @@ export class ConsolePiRuntime {
|
|
|
2079
1471
|
{ value: "auto" },
|
|
2080
1472
|
...supportedLevels.map((value) => ({ value })),
|
|
2081
1473
|
];
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
// Each subface is isolated (RF-02): a throwing subface keeps the other
|
|
2085
|
-
// inventories AND surfaces a structured retryable diagnostic instead of
|
|
2086
|
-
// silently dropping the whole resource projection.
|
|
2087
|
-
const contextFiles = [];
|
|
2088
|
-
const skills = [];
|
|
2089
|
-
const extensions = [];
|
|
2090
|
-
const diagnostics = [];
|
|
2091
|
-
const resolvedResources = [];
|
|
2092
|
-
const resourceFailure = (subface, error) => {
|
|
2093
|
-
diagnostics.push({
|
|
2094
|
-
type: "error",
|
|
2095
|
-
code: "RESOURCE_LOAD_FAILED",
|
|
2096
|
-
message: `${subface} inventory failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
2097
|
-
retryable: true,
|
|
2098
|
-
});
|
|
2099
|
-
};
|
|
1474
|
+
let skillPrefs = { schemaVersion: 1, skills: {} };
|
|
1475
|
+
let skillPrefsError;
|
|
2100
1476
|
if (resourceLoader) {
|
|
2101
|
-
try {
|
|
2102
|
-
const agentsFiles = resourceLoader.getAgentsFiles?.();
|
|
2103
|
-
for (const file of agentsFiles?.agentsFiles ?? []) {
|
|
2104
|
-
if (!file.path)
|
|
2105
|
-
continue;
|
|
2106
|
-
contextFiles.push({
|
|
2107
|
-
path: file.path,
|
|
2108
|
-
characters: file.content?.length ?? 0,
|
|
2109
|
-
});
|
|
2110
|
-
}
|
|
2111
|
-
}
|
|
2112
|
-
catch (error) {
|
|
2113
|
-
resourceFailure("context files", error);
|
|
2114
|
-
}
|
|
2115
|
-
// Preferences are the source of truth for UI auto-invocation state;
|
|
2116
|
-
// loader disableModelInvocation may also reflect frontmatter. Prefer
|
|
2117
|
-
// explicit preference (default on) when projecting autoInvocationEnabled.
|
|
2118
|
-
let skillPrefs = { schemaVersion: 1, skills: {} };
|
|
2119
1477
|
try {
|
|
2120
1478
|
skillPrefs = await this.loadSkillAutoInvocationPreferences();
|
|
2121
1479
|
}
|
|
2122
1480
|
catch (error) {
|
|
2123
|
-
|
|
2124
|
-
type: "error",
|
|
2125
|
-
code: "PI_RESOURCE_PREFERENCES_MALFORMED",
|
|
2126
|
-
message: error instanceof Error ? error.message : String(error),
|
|
2127
|
-
retryable: true,
|
|
2128
|
-
});
|
|
2129
|
-
}
|
|
2130
|
-
try {
|
|
2131
|
-
const skillsResult = resourceLoader.getSkills?.();
|
|
2132
|
-
for (const skill of skillsResult?.skills ?? []) {
|
|
2133
|
-
const resourceId = skillResourceId({
|
|
2134
|
-
name: skill.name,
|
|
2135
|
-
filePath: skill.filePath,
|
|
2136
|
-
});
|
|
2137
|
-
// Preference off always means autoInvocationEnabled=false even if
|
|
2138
|
-
// loader already applied disableModelInvocation. Frontmatter-only
|
|
2139
|
-
// disable (no preference) also surfaces as autoInvocationEnabled=false.
|
|
2140
|
-
const prefEnabled = isAutoInvocationEnabled(skillPrefs, resourceId);
|
|
2141
|
-
const autoInvocationEnabled = prefEnabled && skill.disableModelInvocation !== true;
|
|
2142
|
-
skills.push({
|
|
2143
|
-
name: skill.name,
|
|
2144
|
-
description: skill.description ?? "",
|
|
2145
|
-
path: skill.filePath,
|
|
2146
|
-
...(skill.baseDir ? { baseDir: skill.baseDir } : {}),
|
|
2147
|
-
scope: skill.sourceInfo?.scope ?? "unknown",
|
|
2148
|
-
source: skill.sourceInfo?.source ?? "",
|
|
2149
|
-
resourceId,
|
|
2150
|
-
autoInvocationEnabled,
|
|
2151
|
-
diagnostics: [],
|
|
2152
|
-
});
|
|
2153
|
-
resolvedResources.push({
|
|
2154
|
-
type: "skill",
|
|
2155
|
-
name: skill.name,
|
|
2156
|
-
path: skill.filePath,
|
|
2157
|
-
source: skill.sourceInfo?.source ?? "",
|
|
2158
|
-
scope: skill.sourceInfo?.scope ?? "unknown",
|
|
2159
|
-
});
|
|
2160
|
-
}
|
|
2161
|
-
for (const diag of skillsResult?.diagnostics ?? []) {
|
|
2162
|
-
if (!diag?.message)
|
|
2163
|
-
continue;
|
|
2164
|
-
diagnostics.push({
|
|
2165
|
-
type: diag.type ?? "warning",
|
|
2166
|
-
message: diag.message,
|
|
2167
|
-
...(diag.path ? { path: diag.path } : {}),
|
|
2168
|
-
});
|
|
2169
|
-
}
|
|
2170
|
-
}
|
|
2171
|
-
catch (error) {
|
|
2172
|
-
resourceFailure("skills", error);
|
|
2173
|
-
}
|
|
2174
|
-
try {
|
|
2175
|
-
const extResult = resourceLoader.getExtensions?.();
|
|
2176
|
-
for (const ext of extResult?.extensions ?? []) {
|
|
2177
|
-
const path = ext.resolvedPath ?? ext.path ?? "";
|
|
2178
|
-
extensions.push({
|
|
2179
|
-
name: ext.sourceInfo?.source ?? ext.path ?? "extension",
|
|
2180
|
-
path,
|
|
2181
|
-
...(ext.resolvedPath ? { resolvedPath: ext.resolvedPath } : {}),
|
|
2182
|
-
source: ext.sourceInfo?.source ?? "",
|
|
2183
|
-
scope: ext.sourceInfo?.scope ?? "unknown",
|
|
2184
|
-
...(ext.sourceInfo?.origin ? { origin: ext.sourceInfo.origin } : {}),
|
|
2185
|
-
enabled: true,
|
|
2186
|
-
});
|
|
2187
|
-
resolvedResources.push({
|
|
2188
|
-
type: "extension",
|
|
2189
|
-
name: ext.sourceInfo?.source ?? ext.path ?? "extension",
|
|
2190
|
-
path,
|
|
2191
|
-
source: ext.sourceInfo?.source ?? "",
|
|
2192
|
-
scope: ext.sourceInfo?.scope ?? "unknown",
|
|
2193
|
-
});
|
|
2194
|
-
}
|
|
2195
|
-
for (const err of extResult?.errors ?? []) {
|
|
2196
|
-
if (!err?.error)
|
|
2197
|
-
continue;
|
|
2198
|
-
diagnostics.push({
|
|
2199
|
-
type: "error",
|
|
2200
|
-
message: err.error,
|
|
2201
|
-
...(err.path ? { path: err.path } : {}),
|
|
2202
|
-
});
|
|
2203
|
-
}
|
|
2204
|
-
}
|
|
2205
|
-
catch (error) {
|
|
2206
|
-
resourceFailure("extensions", error);
|
|
1481
|
+
skillPrefsError = error;
|
|
2207
1482
|
}
|
|
2208
|
-
try {
|
|
2209
|
-
const promptsResult = resourceLoader.getPrompts?.();
|
|
2210
|
-
for (const prompt of promptsResult?.prompts ?? []) {
|
|
2211
|
-
resolvedResources.push({
|
|
2212
|
-
type: "prompt",
|
|
2213
|
-
name: prompt.name,
|
|
2214
|
-
path: prompt.filePath,
|
|
2215
|
-
source: prompt.sourceInfo?.source ?? "",
|
|
2216
|
-
scope: prompt.sourceInfo?.scope ?? "unknown",
|
|
2217
|
-
});
|
|
2218
|
-
}
|
|
2219
|
-
for (const diag of promptsResult?.diagnostics ?? []) {
|
|
2220
|
-
if (!diag?.message)
|
|
2221
|
-
continue;
|
|
2222
|
-
diagnostics.push({
|
|
2223
|
-
type: diag.type ?? "warning",
|
|
2224
|
-
message: diag.message,
|
|
2225
|
-
...(diag.path ? { path: diag.path } : {}),
|
|
2226
|
-
});
|
|
2227
|
-
}
|
|
2228
|
-
}
|
|
2229
|
-
catch (error) {
|
|
2230
|
-
resourceFailure("prompts", error);
|
|
2231
|
-
}
|
|
2232
|
-
try {
|
|
2233
|
-
const themesResult = resourceLoader.getThemes?.();
|
|
2234
|
-
for (const theme of themesResult?.themes ?? []) {
|
|
2235
|
-
if (!theme.path)
|
|
2236
|
-
continue;
|
|
2237
|
-
resolvedResources.push({
|
|
2238
|
-
type: "theme",
|
|
2239
|
-
name: theme.name ?? theme.path,
|
|
2240
|
-
path: theme.path,
|
|
2241
|
-
source: "",
|
|
2242
|
-
scope: "unknown",
|
|
2243
|
-
});
|
|
2244
|
-
}
|
|
2245
|
-
for (const diag of themesResult?.diagnostics ?? []) {
|
|
2246
|
-
if (!diag?.message)
|
|
2247
|
-
continue;
|
|
2248
|
-
diagnostics.push({
|
|
2249
|
-
type: diag.type ?? "warning",
|
|
2250
|
-
message: diag.message,
|
|
2251
|
-
...(diag.path ? { path: diag.path } : {}),
|
|
2252
|
-
});
|
|
2253
|
-
}
|
|
2254
|
-
}
|
|
2255
|
-
catch (error) {
|
|
2256
|
-
resourceFailure("themes", error);
|
|
2257
|
-
}
|
|
2258
|
-
}
|
|
2259
|
-
// Packages: Pi settings specs + resolved resource metadata. Fields the SDK
|
|
2260
|
-
// does not expose stay absent (real missing, never fabricated). A
|
|
2261
|
-
// readPackageInventory failure is a PARTIAL failure (RF-02): the snapshot
|
|
2262
|
-
// stays 200 with a structured retryable diagnostic — never a silent
|
|
2263
|
-
// fake-empty package list.
|
|
2264
|
-
const { readPackageInventory } = await import("./pi-console-config.js");
|
|
2265
|
-
let packages = [];
|
|
2266
|
-
try {
|
|
2267
|
-
packages = (await readPackageInventory(await safeGetAgentDir(this.bindings), this.options.cwd, resolvedResources));
|
|
2268
|
-
}
|
|
2269
|
-
catch (error) {
|
|
2270
|
-
packages = [];
|
|
2271
|
-
diagnostics.push({
|
|
2272
|
-
type: "error",
|
|
2273
|
-
code: "PACKAGE_INVENTORY_FAILED",
|
|
2274
|
-
message: error instanceof Error ? error.message : String(error),
|
|
2275
|
-
retryable: true,
|
|
2276
|
-
});
|
|
2277
|
-
}
|
|
2278
|
-
const { readMcpInventory } = await import("./mcp-inventory.js");
|
|
2279
|
-
let mcp = { servers: [], unassignedTools: [] };
|
|
2280
|
-
try {
|
|
2281
|
-
mcp = await readMcpInventory({
|
|
2282
|
-
agentDir: await safeGetAgentDir(this.bindings),
|
|
2283
|
-
cwd: this.options.cwd,
|
|
2284
|
-
tools: session.getAllTools?.() ?? [],
|
|
2285
|
-
activeToolNames: session.getActiveToolNames(),
|
|
2286
|
-
});
|
|
2287
|
-
}
|
|
2288
|
-
catch (error) {
|
|
2289
|
-
diagnostics.push({
|
|
2290
|
-
type: "error",
|
|
2291
|
-
code: "MCP_INVENTORY_FAILED",
|
|
2292
|
-
message: error instanceof Error ? error.message : String(error),
|
|
2293
|
-
retryable: true,
|
|
2294
|
-
});
|
|
2295
1483
|
}
|
|
1484
|
+
const inventories = await collectRuntimeInventories({
|
|
1485
|
+
resourceLoader,
|
|
1486
|
+
skillPrefs,
|
|
1487
|
+
skillPrefsError,
|
|
1488
|
+
agentDir: await safeGetAgentDir(this.bindings),
|
|
1489
|
+
cwd: this.options.cwd,
|
|
1490
|
+
tools: session.getAllTools?.() ?? [],
|
|
1491
|
+
activeToolNames: session.getActiveToolNames(),
|
|
1492
|
+
});
|
|
2296
1493
|
return projectRuntimeSnapshot({
|
|
2297
1494
|
sessionId,
|
|
2298
1495
|
revision: this.revisions.get(sessionId) ?? 0,
|
|
@@ -2317,19 +1514,17 @@ export class ConsolePiRuntime {
|
|
|
2317
1514
|
thinkingLevels,
|
|
2318
1515
|
systemPrompt: {
|
|
2319
1516
|
...(options?.includeRaw ? { raw: rawSystemPrompt } : {}),
|
|
2320
|
-
// The RAW prompt flows into the projection: text is scrubbed +
|
|
2321
|
-
// length-limited while sha256/characters stay RAW-based (design §2.2).
|
|
2322
1517
|
text: rawSystemPrompt,
|
|
2323
1518
|
sha256: createHash("sha256").update(rawSystemPrompt).digest("hex"),
|
|
2324
1519
|
characters: rawSystemPrompt.length,
|
|
2325
1520
|
},
|
|
2326
1521
|
activeTools: session.getActiveToolNames(),
|
|
2327
|
-
contextFiles,
|
|
2328
|
-
skills,
|
|
2329
|
-
extensions,
|
|
2330
|
-
packages,
|
|
2331
|
-
mcp,
|
|
2332
|
-
diagnostics,
|
|
1522
|
+
contextFiles: inventories.contextFiles,
|
|
1523
|
+
skills: inventories.skills,
|
|
1524
|
+
extensions: inventories.extensions,
|
|
1525
|
+
packages: inventories.packages,
|
|
1526
|
+
mcp: inventories.mcp,
|
|
1527
|
+
diagnostics: inventories.diagnostics,
|
|
2333
1528
|
hasBash: true,
|
|
2334
1529
|
});
|
|
2335
1530
|
}
|
|
@@ -2347,7 +1542,9 @@ export class ConsolePiRuntime {
|
|
|
2347
1542
|
* than silently creating a fresh empty session and losing prior context.
|
|
2348
1543
|
*/
|
|
2349
1544
|
async reopenSession(init) {
|
|
1545
|
+
await this.syncAimaxAuthToken();
|
|
2350
1546
|
const sessionId = init.sessionId;
|
|
1547
|
+
const sessionMode = this.rememberSessionMode(sessionId, init.sessionMode ?? this.sessionModeOf(sessionId));
|
|
2351
1548
|
// Wait for in-flight create materialization rather than racing reopen.
|
|
2352
1549
|
const initState = this.sessionInit.get(sessionId);
|
|
2353
1550
|
if (initState?.status === "initializing") {
|
|
@@ -2424,6 +1621,7 @@ export class ConsolePiRuntime {
|
|
|
2424
1621
|
sessionManager,
|
|
2425
1622
|
model: init.model,
|
|
2426
1623
|
systemPromptSuffix: init.systemPromptSuffix,
|
|
1624
|
+
sessionMode,
|
|
2427
1625
|
});
|
|
2428
1626
|
const session = built.session;
|
|
2429
1627
|
// Prefer the SessionManager id (from the JSONL header). Some SDK
|
|
@@ -2446,7 +1644,6 @@ export class ConsolePiRuntime {
|
|
|
2446
1644
|
message: `reopened Pi sessionId mismatch: expected ${sessionId}, got ${reportedId}`,
|
|
2447
1645
|
};
|
|
2448
1646
|
}
|
|
2449
|
-
session.setActiveToolsByName(computeOperatorChatActiveToolNames(session));
|
|
2450
1647
|
this.sessions.set(sessionId, session);
|
|
2451
1648
|
this.sessionManagers.set(sessionId, sessionManager);
|
|
2452
1649
|
this.modelRuntimes.set(sessionId, built.modelRuntime);
|
|
@@ -2464,6 +1661,7 @@ export class ConsolePiRuntime {
|
|
|
2464
1661
|
createdAt: new Date().toISOString(),
|
|
2465
1662
|
model: init.model,
|
|
2466
1663
|
activeTools: session.getActiveToolNames(),
|
|
1664
|
+
sessionMode,
|
|
2467
1665
|
},
|
|
2468
1666
|
};
|
|
2469
1667
|
}
|
|
@@ -2495,6 +1693,7 @@ export class ConsolePiRuntime {
|
|
|
2495
1693
|
createdAt: new Date().toISOString(),
|
|
2496
1694
|
model,
|
|
2497
1695
|
activeTools: session.getActiveToolNames(),
|
|
1696
|
+
sessionMode: this.sessionModeOf(sessionId),
|
|
2498
1697
|
};
|
|
2499
1698
|
}
|
|
2500
1699
|
/**
|
|
@@ -2503,46 +1702,22 @@ export class ConsolePiRuntime {
|
|
|
2503
1702
|
* before AgentSession is ready. Tool definitions reuse runtime-level caches.
|
|
2504
1703
|
*/
|
|
2505
1704
|
async buildSessionWithServices(init) {
|
|
2506
|
-
const
|
|
2507
|
-
const
|
|
2508
|
-
|
|
2509
|
-
OPERATOR_CHAT_SYSTEM_PROMPT_BASE,
|
|
2510
|
-
...(init.systemPromptSuffix ? [init.systemPromptSuffix] : []),
|
|
2511
|
-
];
|
|
2512
|
-
await this.refreshSkillPrefsCache().catch(() => undefined);
|
|
2513
|
-
const { services } = await this.bindings.createServices({
|
|
2514
|
-
cwd: this.options.cwd,
|
|
2515
|
-
agentDir,
|
|
2516
|
-
resourceLoaderOptions: await this.buildResourceLoaderOptions({ appendSystemPrompt }, init.sessionId),
|
|
2517
|
-
});
|
|
2518
|
-
const modelRuntime = services.modelRuntime;
|
|
2519
|
-
const resolvedModel = init.model
|
|
2520
|
-
? (this.bindings.resolveModel({
|
|
2521
|
-
modelRuntime,
|
|
2522
|
-
provider: init.model.provider,
|
|
2523
|
-
modelId: init.model.modelId,
|
|
2524
|
-
}) ?? undefined)
|
|
2525
|
-
: undefined;
|
|
2526
|
-
assertNoWriteToolInList(OPERATOR_CHAT_ALLOWED_TOOLS);
|
|
2527
|
-
const customTools = await this.loadCustomTools(init.sessionId);
|
|
2528
|
-
const { session } = await this.bindings.createSessionFromServices({
|
|
2529
|
-
services,
|
|
1705
|
+
const sessionMode = this.rememberSessionMode(init.sessionId, init.sessionMode ?? this.sessionModeOf(init.sessionId));
|
|
1706
|
+
const prepared = await this.prepareLiveSession({
|
|
1707
|
+
sessionId: init.sessionId,
|
|
2530
1708
|
sessionManager: init.sessionManager,
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
1709
|
+
model: init.model,
|
|
1710
|
+
systemPromptSuffix: init.systemPromptSuffix,
|
|
1711
|
+
sessionMode,
|
|
2534
1712
|
});
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
1713
|
+
if (!prepared) {
|
|
1714
|
+
throw new Error(`chat session preparation aborted: ${init.sessionId}`);
|
|
1715
|
+
}
|
|
2538
1716
|
return {
|
|
2539
|
-
session,
|
|
2540
|
-
modelRuntime,
|
|
2541
|
-
modelConfigFile,
|
|
2542
|
-
services:
|
|
2543
|
-
resourceLoader: services.resourceLoader,
|
|
2544
|
-
settingsManager: services.settingsManager,
|
|
2545
|
-
},
|
|
1717
|
+
session: prepared.session,
|
|
1718
|
+
modelRuntime: prepared.modelRuntime,
|
|
1719
|
+
modelConfigFile: prepared.modelConfigFile,
|
|
1720
|
+
services: prepared.services,
|
|
2546
1721
|
};
|
|
2547
1722
|
}
|
|
2548
1723
|
/** Re-open a previously created session (cross-request). */
|
|
@@ -2602,7 +1777,7 @@ export class ConsolePiRuntime {
|
|
|
2602
1777
|
}
|
|
2603
1778
|
/**
|
|
2604
1779
|
* Send a prompt and stream events. Gate 3: each tool invocation is
|
|
2605
|
-
* re-authorized by
|
|
1780
|
+
* re-authorized by authorizeChatSessionTool(mode) before the dispatcher runs it
|
|
2606
1781
|
* (the dispatcher is wired by the HTTP layer, see chat-session.ts).
|
|
2607
1782
|
*
|
|
2608
1783
|
* Prompt ownership is acquired synchronously before the first await so two
|
|
@@ -2644,7 +1819,8 @@ export class ConsolePiRuntime {
|
|
|
2644
1819
|
if (options?.turnId && this.options.todoRegistry) {
|
|
2645
1820
|
this.options.todoRegistry.startTurn(sessionId, options.turnId);
|
|
2646
1821
|
}
|
|
2647
|
-
if (this.options.goalRegistry
|
|
1822
|
+
if (this.options.goalRegistry &&
|
|
1823
|
+
shouldInjectGoalContinuation(this.sessionModeOf(sessionId))) {
|
|
2648
1824
|
this.options.goalRegistry.startTurn(sessionId, text);
|
|
2649
1825
|
}
|
|
2650
1826
|
// AC-FIX-002: if Stop already fired before readiness, never start model work.
|
|
@@ -2665,10 +1841,11 @@ export class ConsolePiRuntime {
|
|
|
2665
1841
|
// existing closed interview allow/deny registry. The next ordinary turn
|
|
2666
1842
|
// explicitly restores the normal Operator Chat set (baseline + extension
|
|
2667
1843
|
// tools, ADR 0012).
|
|
2668
|
-
const
|
|
2669
|
-
const
|
|
1844
|
+
const sessionMode = this.sessionModeOf(sessionId);
|
|
1845
|
+
const requestedTools = chatActiveToolBaseline(sessionMode);
|
|
1846
|
+
const activeTools = options?.mode === "requirement-interview" && sessionMode === "workflow"
|
|
2670
1847
|
? filterActiveInterviewTools(requestedTools).allowed
|
|
2671
|
-
:
|
|
1848
|
+
: computeChatActiveToolNames(session, sessionMode);
|
|
2672
1849
|
session.setActiveToolsByName(activeTools);
|
|
2673
1850
|
// Final pre-prompt fence: abort after tool pin must still skip session.prompt.
|
|
2674
1851
|
if (options?.signal?.aborted) {
|
|
@@ -3006,6 +2183,7 @@ export class ConsolePiRuntime {
|
|
|
3006
2183
|
this.modelRuntimes.delete(sessionId);
|
|
3007
2184
|
this.modelConfigFiles.delete(sessionId);
|
|
3008
2185
|
this.sessionModels.delete(sessionId);
|
|
2186
|
+
this.sessionModes.delete(sessionId);
|
|
3009
2187
|
this.serviceScopes.delete(sessionId);
|
|
3010
2188
|
this.revisions.delete(sessionId);
|
|
3011
2189
|
this.thinkingSelections.delete(sessionId);
|
|
@@ -3173,155 +2351,9 @@ async function waitForIdle(session, options) {
|
|
|
3173
2351
|
await new Promise((r) => setTimeout(r, 50));
|
|
3174
2352
|
}
|
|
3175
2353
|
}
|
|
3176
|
-
async function safeGetAgentDir(bindings) {
|
|
3177
|
-
try {
|
|
3178
|
-
return await bindings.getAgentDir();
|
|
3179
|
-
}
|
|
3180
|
-
catch {
|
|
3181
|
-
// Fall back to a Console-app-data scoped agent dir so we never accidentally
|
|
3182
|
-
// touch the user's real ~/.pi/agent config if getAgentDir throws.
|
|
3183
|
-
return path.join(process.cwd(), ".harness", "console", "pi-agent-dir");
|
|
3184
|
-
}
|
|
3185
|
-
}
|
|
3186
|
-
/**
|
|
3187
|
-
* Cached SDK module loader. The pi-coding-agent package is ESM-only, so we
|
|
3188
|
-
* must use dynamic import() (not require). Cached to avoid repeated imports.
|
|
3189
|
-
*/
|
|
3190
|
-
let cachedSdk;
|
|
3191
|
-
async function loadSdk() {
|
|
3192
|
-
if (!cachedSdk) {
|
|
3193
|
-
cachedSdk = import("@earendil-works/pi-coding-agent");
|
|
3194
|
-
}
|
|
3195
|
-
return cachedSdk;
|
|
3196
|
-
}
|
|
3197
|
-
/**
|
|
3198
|
-
* Default production bindings backed by the real Pi SDK (services mode).
|
|
3199
|
-
* Dynamically imported so the worker bundle does not hard-fail if the SDK is
|
|
3200
|
-
* absent at module load (discovered lazily by pi-readiness instead).
|
|
3201
|
-
*/
|
|
3202
|
-
export function createDefaultPiSdkBindings() {
|
|
3203
|
-
return {
|
|
3204
|
-
getAgentDir: async () => {
|
|
3205
|
-
const sdk = (await loadSdk());
|
|
3206
|
-
return sdk.getAgentDir();
|
|
3207
|
-
},
|
|
3208
|
-
createServices: async (opts) => {
|
|
3209
|
-
const sdk = (await loadSdk());
|
|
3210
|
-
// createAgentSessionServices returns the full AgentSessionServices object
|
|
3211
|
-
// directly (cwd/agentDir/modelRuntime/settingsManager/resourceLoader).
|
|
3212
|
-
// Wrap it so our type contract is satisfied.
|
|
3213
|
-
const result = await sdk.createAgentSessionServices({
|
|
3214
|
-
cwd: opts.cwd,
|
|
3215
|
-
agentDir: opts.agentDir,
|
|
3216
|
-
resourceLoaderOptions: opts.resourceLoaderOptions,
|
|
3217
|
-
});
|
|
3218
|
-
return { services: result };
|
|
3219
|
-
},
|
|
3220
|
-
createSessionFromServices: async (opts) => {
|
|
3221
|
-
const sdk = (await loadSdk());
|
|
3222
|
-
return sdk.createAgentSessionFromServices({
|
|
3223
|
-
services: opts.services,
|
|
3224
|
-
sessionManager: opts.sessionManager,
|
|
3225
|
-
...(opts.tools ? { tools: opts.tools } : {}),
|
|
3226
|
-
...(opts.noTools ? { noTools: opts.noTools } : {}),
|
|
3227
|
-
...(opts.excludeTools ? { excludeTools: opts.excludeTools } : {}),
|
|
3228
|
-
...(opts.customTools ? { customTools: opts.customTools } : {}),
|
|
3229
|
-
...(opts.model ? { model: opts.model } : {}),
|
|
3230
|
-
});
|
|
3231
|
-
},
|
|
3232
|
-
createSessionManager: async (opts) => {
|
|
3233
|
-
const sdk = (await loadSdk());
|
|
3234
|
-
return sdk.SessionManager.create(opts.cwd, opts.sessionDir, {
|
|
3235
|
-
id: opts.sessionId,
|
|
3236
|
-
});
|
|
3237
|
-
},
|
|
3238
|
-
materializeSessionShell: async (opts) => {
|
|
3239
|
-
const sdk = (await loadSdk());
|
|
3240
|
-
const manager = opts.sessionManager;
|
|
3241
|
-
if (typeof manager.setSessionFile !== "function") {
|
|
3242
|
-
throw new Error("Pi SessionManager cannot materialize a session file");
|
|
3243
|
-
}
|
|
3244
|
-
let created = false;
|
|
3245
|
-
try {
|
|
3246
|
-
await mkdir(path.dirname(opts.sessionFile), { recursive: true });
|
|
3247
|
-
// Match Pi's documented session header. Rebinding through the public
|
|
3248
|
-
// SessionManager API marks it flushed, so Pi appends the first user
|
|
3249
|
-
// entry instead of trying to create the already-present file later.
|
|
3250
|
-
await writeFile(opts.sessionFile, `${JSON.stringify({
|
|
3251
|
-
type: "session",
|
|
3252
|
-
version: typeof sdk.CURRENT_SESSION_VERSION === "number"
|
|
3253
|
-
? sdk.CURRENT_SESSION_VERSION
|
|
3254
|
-
: 3,
|
|
3255
|
-
id: opts.sessionId,
|
|
3256
|
-
timestamp: new Date().toISOString(),
|
|
3257
|
-
cwd: opts.cwd,
|
|
3258
|
-
})}\n`, { encoding: "utf8", flag: "wx", mode: 0o600 });
|
|
3259
|
-
created = true;
|
|
3260
|
-
manager.setSessionFile(opts.sessionFile);
|
|
3261
|
-
}
|
|
3262
|
-
catch (error) {
|
|
3263
|
-
if (created)
|
|
3264
|
-
await rm(opts.sessionFile, { force: true }).catch(() => undefined);
|
|
3265
|
-
throw error;
|
|
3266
|
-
}
|
|
3267
|
-
},
|
|
3268
|
-
createInMemorySessionManager: async (opts) => {
|
|
3269
|
-
const sdk = (await loadSdk());
|
|
3270
|
-
return sdk.SessionManager.inMemory(opts.cwd);
|
|
3271
|
-
},
|
|
3272
|
-
forkSessionManager: async (opts) => {
|
|
3273
|
-
const sdk = (await loadSdk());
|
|
3274
|
-
return sdk.SessionManager.forkFrom(opts.sourcePath, opts.targetCwd, opts.targetSessionDir, { id: opts.targetSessionId });
|
|
3275
|
-
},
|
|
3276
|
-
openSessionManager: async (opts) => {
|
|
3277
|
-
const sdk = (await loadSdk());
|
|
3278
|
-
try {
|
|
3279
|
-
// Pi SessionManager.open(missingPath) does NOT throw — it creates a
|
|
3280
|
-
// fresh in-memory session with a random UUID. That would reopen the
|
|
3281
|
-
// wrong conversation under the durable Console sessionId. Fail closed
|
|
3282
|
-
// before open when the persisted file is absent.
|
|
3283
|
-
await access(opts.sessionFile);
|
|
3284
|
-
}
|
|
3285
|
-
catch (error) {
|
|
3286
|
-
const code = error && typeof error === "object" && "code" in error
|
|
3287
|
-
? String(error.code ?? "")
|
|
3288
|
-
: "";
|
|
3289
|
-
if (code === "ENOENT") {
|
|
3290
|
-
throw Object.assign(new Error(`SESSION_FILE_MISSING: ${opts.sessionFile}`), { code: "SESSION_FILE_MISSING", cause: error });
|
|
3291
|
-
}
|
|
3292
|
-
// File exists but is unreadable (permissions / I/O) — caller maps
|
|
3293
|
-
// undefined to 503 rather than silently creating a fresh session.
|
|
3294
|
-
return undefined;
|
|
3295
|
-
}
|
|
3296
|
-
return sdk.SessionManager.open(opts.sessionFile);
|
|
3297
|
-
},
|
|
3298
|
-
resolveModel: (opts) => {
|
|
3299
|
-
const rt = opts.modelRuntime;
|
|
3300
|
-
if (typeof rt?.getModel !== "function")
|
|
3301
|
-
return undefined;
|
|
3302
|
-
try {
|
|
3303
|
-
return rt.getModel(opts.provider, opts.modelId);
|
|
3304
|
-
}
|
|
3305
|
-
catch {
|
|
3306
|
-
return undefined;
|
|
3307
|
-
}
|
|
3308
|
-
},
|
|
3309
|
-
listAvailableModels: (opts) => {
|
|
3310
|
-
const rt = opts.modelRuntime;
|
|
3311
|
-
const available = rt?.snapshot?.available ?? [];
|
|
3312
|
-
const configured = rt?.snapshot?.configuredProviders;
|
|
3313
|
-
const isConfigured = (provider) => configured instanceof Set ? configured.has(provider) : false;
|
|
3314
|
-
return available.map((m) => ({
|
|
3315
|
-
id: m.id,
|
|
3316
|
-
name: m.name,
|
|
3317
|
-
provider: m.provider,
|
|
3318
|
-
hasCredentials: isConfigured(m.provider),
|
|
3319
|
-
}));
|
|
3320
|
-
},
|
|
3321
|
-
};
|
|
3322
|
-
}
|
|
3323
2354
|
/**
|
|
3324
|
-
* Re-export the tool
|
|
3325
|
-
* (Gate 3) lives in exactly one place.
|
|
2355
|
+
* Re-export the tool gates for the dispatcher layer so the per-tool-call gate
|
|
2356
|
+
* (Gate 3) lives in exactly one place. authorizeChatSessionTool is the
|
|
2357
|
+
* mode-aware gate; the legacy name stays for workflow-only callers.
|
|
3326
2358
|
*/
|
|
3327
|
-
export { authorizeOperatorChatTool, assertNoWriteToolInList, OPERATOR_CHAT_ALLOWED_TOOLS, };
|
|
2359
|
+
export { authorizeChatSessionTool, authorizeOperatorChatTool, assertNoWriteToolInList, OPERATOR_CHAT_ALLOWED_TOOLS, };
|