@xopcai/xopc 0.0.95 → 0.0.97
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/dist/browser-ext/manifest.json +1 -1
- package/dist/extensions/feishu/src/outbound/media-load.js +1 -1
- package/dist/extensions/feishu/src/workflow-progress.js +1 -1
- package/dist/extensions/telegram/src/plugin.js +1 -1
- package/dist/extensions/telegram/src/routing-integration.js +2 -2
- package/dist/extensions/telegram/src/workflow-progress.js +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/extensions/weixin/src/api/api.js +2 -2
- package/dist/extensions/weixin/src/auth/accounts.js +1 -1
- package/dist/extensions/weixin/src/cdn/upload.js +1 -1
- package/dist/extensions/weixin/src/media/data-url.js +1 -1
- package/dist/extensions/weixin/src/messaging/debug-mode.js +1 -1
- package/dist/extensions/weixin/src/messaging/inbound.js +1 -1
- package/dist/extensions/weixin/src/messaging/process-message.js +1 -1
- package/dist/extensions/weixin/src/plugin.js +1 -1
- package/dist/extensions/weixin/src/storage/sync-buf.js +1 -1
- package/dist/extensions/weixin/src/workflow-progress.js +1 -1
- package/dist/gateway/static/root/assets/{agents-CKe2LMnz.js → agents-B_YUvNi6.js} +2 -2
- package/dist/gateway/static/root/assets/{apps-page-Mi9mMIZ1.js → apps-page-BmwG5aur.js} +1 -1
- package/dist/gateway/static/root/assets/{channels-settings-BrdyC101.js → channels-settings-BiwkeKPb.js} +1 -1
- package/dist/gateway/static/root/assets/{channels-status-swr-D55Bu0nn.js → channels-status-swr-ChyN473C.js} +1 -1
- package/dist/gateway/static/root/assets/{cron-api-CPpx2l-E.js → cron-api-CvSifIfJ.js} +1 -1
- package/dist/gateway/static/root/assets/{cron-page-Bx2jB0YN.js → cron-page-BDqTDFy6.js} +1 -1
- package/dist/gateway/static/root/assets/{dist-D_AiG_Kg.js → dist-DxsUrjpy.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-debug-page-6ieHsxRE.js → extension-debug-page-DV_Av5Jq.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-B8nywHRO.js → extension-page-CwZwRhWw.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-DrskdEIV.js → extension-settings-page-Bb7TR1Se.js} +1 -1
- package/dist/gateway/static/root/assets/{fetch-B0aeeY0q.js → fetch-BLLOP2CM.js} +1 -1
- package/dist/gateway/static/root/assets/{field-primitives--9ooY8Xl.js → field-primitives-CyqVu1QR.js} +1 -1
- package/dist/gateway/static/root/assets/{heartbeat-config-api-DUZ_W1w-.js → heartbeat-config-api-Cd4M1eHt.js} +1 -1
- package/dist/gateway/static/root/assets/{index-Dj9FuxCm.js → index-0tS9lV85.js} +74 -74
- package/dist/gateway/static/root/assets/index-BJDmBCSl.css +1 -0
- package/dist/gateway/static/root/assets/{logs-page-CaXqhpKf.js → logs-page-BsAOSowN.js} +1 -1
- package/dist/gateway/static/root/assets/{note-detail-page-B91pLkEI.css → note-detail-page-D4ZIVQbk.css} +1 -1
- package/dist/gateway/static/root/assets/{note-detail-page-DYzym2B0.js → note-detail-page-Dlxoy6Ap.js} +54 -53
- package/dist/gateway/static/root/assets/{note-time-B-vSi2dR.js → note-time-B-r8yTpQ.js} +1 -1
- package/dist/gateway/static/root/assets/{notes-page-BkhWdGiT.js → notes-page-CHFcyqYW.js} +1 -1
- package/dist/gateway/static/root/assets/{sessions-page-53YFokoe.js → sessions-page-Ctu0kgt7.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-advanced-gate-BaZmaklx.js → settings-advanced-gate-Dh0TyOOg.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-form-section-DIJPKpTR.js → settings-form-section-DXMCEW1d.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-Dvb230FF.js → settings-page-CIkZ7233.js} +1 -1
- package/dist/gateway/static/root/assets/{share-preview-page-CRyjTAG6.js → share-preview-page-7RV65xhJ.js} +1 -1
- package/dist/gateway/static/root/assets/{skills-page-C5ZJbfAe.js → skills-page-D_Az1SlU.js} +1 -1
- package/dist/gateway/static/root/assets/{theme-store-Cg_SuBw0.js → theme-store-e2q2yjs4.js} +1 -1
- package/dist/gateway/static/root/assets/url-DpFBIyN9.js +3 -0
- package/dist/gateway/static/root/assets/{utils-lMYoWhqo.js → utils-OA_b1q0Q.js} +1 -1
- package/dist/gateway/static/root/assets/{voice-api-key-field-Dda2pcUU.js → voice-api-key-field-SJml1hAt.js} +1 -1
- package/dist/gateway/static/root/assets/{workflow-page.utils-KIladUrU.js → workflow-page.utils-D90VVCzC.js} +1 -1
- package/dist/gateway/static/root/assets/{workflows-page-BTis4Z7Y.js → workflows-page-y7Btji0J.js} +1 -1
- package/dist/gateway/static/root/index.html +5 -5
- package/dist/package.js +1 -1
- package/dist/src/agent/agent-manager.js +12 -8
- package/dist/src/agent/agent-manager.js.map +1 -1
- package/dist/src/agent/agent-scope.d.ts +0 -1
- package/dist/src/agent/agent-scope.js +2 -5
- package/dist/src/agent/agent-scope.js.map +1 -1
- package/dist/src/agent/bootstrap/bootstrap-cache.d.ts +2 -0
- package/dist/src/agent/bootstrap/bootstrap-cache.js +10 -1
- package/dist/src/agent/bootstrap/bootstrap-cache.js.map +1 -1
- package/dist/src/agent/bootstrap/bootstrap-files.d.ts +2 -1
- package/dist/src/agent/bootstrap/bootstrap-files.js +34 -12
- package/dist/src/agent/bootstrap/bootstrap-files.js.map +1 -1
- package/dist/src/agent/bootstrap/load-bootstrap-files.d.ts +1 -2
- package/dist/src/agent/bootstrap/load-bootstrap-files.js +6 -12
- package/dist/src/agent/bootstrap/load-bootstrap-files.js.map +1 -1
- package/dist/src/agent/bootstrap/types.d.ts +5 -5
- package/dist/src/agent/context/workspace-seed.js +6 -6
- package/dist/src/agent/context/workspace-seed.js.map +1 -1
- package/dist/src/agent/context/workspace-state.d.ts +20 -0
- package/dist/src/agent/context/workspace-state.js +57 -0
- package/dist/src/agent/context/workspace-state.js.map +1 -0
- package/dist/src/agent/context/workspace-templates/AGENTS.md +0 -4
- package/dist/src/agent/embedded/index.d.ts +2 -2
- package/dist/src/agent/embedded/index.js +3 -3
- package/dist/src/agent/embedded/run-turn.js +0 -3
- package/dist/src/agent/embedded/run-turn.js.map +1 -1
- package/dist/src/agent/embedded/session-manager-init.d.ts +0 -17
- package/dist/src/agent/embedded/session-manager-init.js +1 -36
- package/dist/src/agent/embedded/session-manager-init.js.map +1 -1
- package/dist/src/agent/embedded/session-runner.d.ts +3 -12
- package/dist/src/agent/embedded/session-runner.js +12 -26
- package/dist/src/agent/embedded/session-runner.js.map +1 -1
- package/dist/src/agent/embedded/session-tool-result-guard.js +2 -4
- package/dist/src/agent/embedded/session-tool-result-guard.js.map +1 -1
- package/dist/src/agent/embedded/sqlite-hydrating-session-manager.d.ts +10 -0
- package/dist/src/agent/embedded/sqlite-hydrating-session-manager.js +34 -0
- package/dist/src/agent/embedded/sqlite-hydrating-session-manager.js.map +1 -0
- package/dist/src/agent/goals/goal-run-store.js +4 -4
- package/dist/src/agent/goals/persistent-goal-service.js +8 -15
- package/dist/src/agent/goals/persistent-goal-service.js.map +1 -1
- package/dist/src/agent/goals/post-turn.js +2 -2
- package/dist/src/agent/image/load-image-media.js +2 -2
- package/dist/src/agent/ipc/bus.js +1 -1
- package/dist/src/agent/ipc/inbox.js +2 -2
- package/dist/src/agent/ipc/socket.js +1 -1
- package/dist/src/agent/mcp/bundle-mcp-materialize.js +1 -1
- package/dist/src/agent/mcp/bundle-mcp-runtime.js +2 -2
- package/dist/src/agent/mcp/mcp-transport-config.js +1 -1
- package/dist/src/agent/mcp/mcp-transport.js +1 -1
- package/dist/src/agent/memory/builtin-memory-store.js +1 -1
- package/dist/src/agent/memory/dreaming/deep-promotion.js +1 -1
- package/dist/src/agent/memory/dreaming/events.js +1 -1
- package/dist/src/agent/memory/dreaming/last-run.js +1 -1
- package/dist/src/agent/memory/dreaming/light-sweep.js +1 -1
- package/dist/src/agent/memory/dreaming/preview.js +1 -1
- package/dist/src/agent/memory/dreaming/rem-patterns.js +1 -1
- package/dist/src/agent/memory/dreaming/short-term-store.js +1 -1
- package/dist/src/agent/memory/dreaming/utils.js +1 -1
- package/dist/src/agent/memory/plugin-discovery.js +1 -1
- package/dist/src/agent/models/manager.js +1 -1
- package/dist/src/agent/prompt/memory/index.d.ts +1 -0
- package/dist/src/agent/prompt/memory/index.js +34 -80
- package/dist/src/agent/prompt/memory/index.js.map +1 -1
- package/dist/src/agent/prompt/service-prompt-builder.js +2 -2
- package/dist/src/agent/prompt/system-prompt.js +0 -1
- package/dist/src/agent/prompt/system-prompt.js.map +1 -1
- package/dist/src/agent/reply/post-compaction-context.js +1 -1
- package/dist/src/agent/reply/workspace-boundary-read.js +1 -1
- package/dist/src/agent/sandbox/path-policy.js +2 -2
- package/dist/src/agent/service/build-direct-message-content.js +1 -1
- package/dist/src/agent/service/process-direct-one-shot.js +8 -17
- package/dist/src/agent/service/process-direct-one-shot.js.map +1 -1
- package/dist/src/agent/service/process-direct-streaming.js +14 -23
- package/dist/src/agent/service/process-direct-streaming.js.map +1 -1
- package/dist/src/agent/service.js +7 -11
- package/dist/src/agent/service.js.map +1 -1
- package/dist/src/agent/session/session-inspector.js +1 -1
- package/dist/src/agent/skills/config.js +1 -1
- package/dist/src/agent/skills/hub-hash.js +2 -2
- package/dist/src/agent/skills/hub-lock.js +1 -1
- package/dist/src/agent/skills/hub-pull.js +3 -3
- package/dist/src/agent/skills/index.js +1 -1
- package/dist/src/agent/skills/managed-store.js +1 -1
- package/dist/src/agent/skills/scanner.js +1 -1
- package/dist/src/agent/skills/skill-manage-ops.js +1 -1
- package/dist/src/agent/skills/skill-manager.js +1 -1
- package/dist/src/agent/tools/dreaming-tool.js +1 -1
- package/dist/src/agent/tools/factory.js +1 -1
- package/dist/src/agent/tools/image-generate-tool.js +1 -1
- package/dist/src/agent/tools/index.d.ts +0 -1
- package/dist/src/agent/tools/index.js +1 -2
- package/dist/src/agent/tools/send-media.js +1 -1
- package/dist/src/agent/tools/session-search-tool.d.ts +0 -1
- package/dist/src/agent/tools/session-search-tool.js +11 -6
- package/dist/src/agent/tools/session-search-tool.js.map +1 -1
- package/dist/src/agent/tools/skill-manage-tool.js +1 -1
- package/dist/src/agent/tools/tool-paths.js +1 -3
- package/dist/src/agent/tools/tool-paths.js.map +1 -1
- package/dist/src/agent/tools/workflow-tool.js +1 -1
- package/dist/src/agent/tools/write.js +1 -1
- package/dist/src/agent/workflow/catalog.js +1 -1
- package/dist/src/auth/credentials.js +3 -3
- package/dist/src/auth/profiles/store.js +1 -1
- package/dist/src/auth/sync-provider-auth.js +1 -1
- package/dist/src/browser/cache-dir-policy.js +1 -1
- package/dist/src/browser/cdp-local-launcher.js +2 -2
- package/dist/src/browser/providers/browser-ext-install.js +4 -4
- package/dist/src/browser/providers/cloakbrowser.js +4 -4
- package/dist/src/browser/providers/playwright-doctor.js +1 -1
- package/dist/src/browser/stealth.js +1 -1
- package/dist/src/channels/attachments/inbound-persist.js +1 -1
- package/dist/src/channels/attachments/outbound-tts-persist.js +1 -1
- package/dist/src/channels/outbound/persist-store.js +1 -1
- package/dist/src/channels/pairing/allow-from-file.js +1 -1
- package/dist/src/channels/pairing/pairing-store.js +2 -2
- package/dist/src/chat-commands/agent-edit.js +3 -4
- package/dist/src/chat-commands/agent-edit.js.map +1 -1
- package/dist/src/chat-commands/builtins/config.js +2 -2
- package/dist/src/chat-commands/context.js +1 -1
- package/dist/src/cli/commands/config.js +1 -1
- package/dist/src/cli/commands/doctor/checks/config-health.js +1 -1
- package/dist/src/cli/commands/doctor/checks/provider-auth.js +1 -1
- package/dist/src/cli/commands/doctor/checks/session-integrity.js +32 -95
- package/dist/src/cli/commands/doctor/checks/session-integrity.js.map +1 -1
- package/dist/src/cli/commands/doctor/checks/state-integrity.js +1 -1
- package/dist/src/cli/commands/doctor/checks/workspace-status.js +1 -1
- package/dist/src/cli/commands/extension-dev.js +1 -1
- package/dist/src/cli/commands/extension-marketplace.js +1 -1
- package/dist/src/cli/commands/extension-pack.js +1 -1
- package/dist/src/cli/commands/gateway/logs.js +1 -1
- package/dist/src/cli/commands/image.js +1 -1
- package/dist/src/cli/commands/init.js +5 -7
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/commands/onboard.js +0 -8
- package/dist/src/cli/commands/onboard.js.map +1 -1
- package/dist/src/cli/templates.d.ts +3 -10
- package/dist/src/cli/templates.js +4 -32
- package/dist/src/cli/templates.js.map +1 -1
- package/dist/src/cli/utils/init-workspace-core.js +2 -2
- package/dist/src/commands/agents.config.js +1 -1
- package/dist/src/config/agent-profile.js +1 -1
- package/dist/src/config/gateway-bind.js +1 -1
- package/dist/src/config/index.js +7 -8
- package/dist/src/config/index.js.map +1 -1
- package/dist/src/config/loader.js +2 -2
- package/dist/src/config/models-json.js +2 -2
- package/dist/src/config/paths-state.d.ts +3 -0
- package/dist/src/config/paths-state.js +7 -3
- package/dist/src/config/paths-state.js.map +1 -1
- package/dist/src/config/paths.d.ts +5 -36
- package/dist/src/config/paths.js +7 -52
- package/dist/src/config/paths.js.map +1 -1
- package/dist/src/config/profile.js +2 -2
- package/dist/src/config/schema.d.ts +15 -0
- package/dist/src/config/schema.js +11 -0
- package/dist/src/config/schema.js.map +1 -1
- package/dist/src/config/workspace-path.js +1 -1
- package/dist/src/cron/execution-types.d.ts +42 -0
- package/dist/src/cron/executor.js +2 -2
- package/dist/src/cron/persistence.js +1 -1
- package/dist/src/cron/run-log-store.d.ts +4 -8
- package/dist/src/cron/run-log-store.js +26 -78
- package/dist/src/cron/run-log-store.js.map +1 -1
- package/dist/src/cron/service.d.ts +3 -3
- package/dist/src/cron/service.js +2 -2
- package/dist/src/cron/service.js.map +1 -1
- package/dist/src/cron/types.d.ts +1 -42
- package/dist/src/daemon/constants.js +1 -1
- package/dist/src/daemon/install-plan.js +2 -2
- package/dist/src/daemon/launchd.js +2 -2
- package/dist/src/daemon/schtasks.js +2 -2
- package/dist/src/daemon/systemd.js +2 -2
- package/dist/src/extensions/bundle-mcp.js +1 -1
- package/dist/src/extensions/discover-extensions.js +1 -1
- package/dist/src/extensions/health.js +1 -1
- package/dist/src/extensions/loader.js +1 -1
- package/dist/src/extensions/lockfile.js +2 -2
- package/dist/src/extensions/update.js +1 -1
- package/dist/src/gateway/agents-admin.js +4 -4
- package/dist/src/gateway/agents-admin.js.map +1 -1
- package/dist/src/gateway/file-path-classifier.d.ts +0 -1
- package/dist/src/gateway/file-path-classifier.js +2 -8
- package/dist/src/gateway/file-path-classifier.js.map +1 -1
- package/dist/src/gateway/heartbeat/service.js +1 -1
- package/dist/src/gateway/hono/lib/config-payload.js +1 -1
- package/dist/src/gateway/hono/lib/extension-store.js +2 -2
- package/dist/src/gateway/hono/lib/static-ui.js +2 -2
- package/dist/src/gateway/hono/oauth.js +1 -1
- package/dist/src/gateway/hono/routes/agents.js +1 -1
- package/dist/src/gateway/hono/routes/auth-registry-extensions.js +1 -1
- package/dist/src/gateway/hono/routes/config-patch/misc.js +1 -1
- package/dist/src/gateway/hono/routes/dreaming.js +1 -1
- package/dist/src/gateway/hono/routes/host-fs.js +2 -2
- package/dist/src/gateway/hono/routes/models.js +1 -1
- package/dist/src/gateway/hono/routes/shares.js +1 -1
- package/dist/src/gateway/hono/routes/workspace.js +2 -2
- package/dist/src/gateway/lock.js +3 -3
- package/dist/src/gateway/ports.js +1 -1
- package/dist/src/gateway/service/agent-runner.js +2 -2
- package/dist/src/gateway/service/marketplace-service.js +2 -2
- package/dist/src/gateway/service.js +5 -1
- package/dist/src/gateway/service.js.map +1 -1
- package/dist/src/gateway/session-reset-service.d.ts +1 -1
- package/dist/src/gateway/session-reset-service.js +1 -1
- package/dist/src/gateway/session-reset-service.js.map +1 -1
- package/dist/src/gateway/workspace-fs-file-list.js +1 -1
- package/dist/src/heartbeat/index.js +1 -1
- package/dist/src/infra/brew.js +1 -1
- package/dist/src/infra/node-sqlite.d.ts +1 -0
- package/dist/src/infra/node-sqlite.js +17 -0
- package/dist/src/infra/node-sqlite.js.map +1 -0
- package/dist/src/infra/package-json.js +1 -1
- package/dist/src/infra/package-update-steps.js +1 -1
- package/dist/src/infra/path-env.js +2 -2
- package/dist/src/infra/restart.js +2 -2
- package/dist/src/infra/sqlite-errors.d.ts +1 -0
- package/dist/src/infra/sqlite-errors.js +77 -0
- package/dist/src/infra/sqlite-errors.js.map +1 -0
- package/dist/src/infra/stable-node-path.js +1 -1
- package/dist/src/infra/unhandled-rejections.d.ts +1 -0
- package/dist/src/infra/unhandled-rejections.js +25 -0
- package/dist/src/infra/unhandled-rejections.js.map +1 -0
- package/dist/src/infra/update-check.js +1 -1
- package/dist/src/infra/update-global.js +1 -1
- package/dist/src/infra/update-lock.js +3 -3
- package/dist/src/infra/update-runner.js +1 -1
- package/dist/src/infra/update-startup.js +2 -2
- package/dist/src/infra/warning-filter.d.ts +7 -0
- package/dist/src/infra/warning-filter.js +59 -0
- package/dist/src/infra/warning-filter.js.map +1 -0
- package/dist/src/infra/write-file-atomic.js +2 -2
- package/dist/src/notes/store.d.ts +3 -9
- package/dist/src/notes/store.js +22 -196
- package/dist/src/notes/store.js.map +1 -1
- package/dist/src/providers/auth-runtime/auth-profile-store.js +1 -1
- package/dist/src/providers/index.js +2 -2
- package/dist/src/providers/model-registry.js +1 -1
- package/dist/src/session/config-store.d.ts +6 -75
- package/dist/src/session/config-store.js +38 -144
- package/dist/src/session/config-store.js.map +1 -1
- package/dist/src/session/config-types.d.ts +15 -0
- package/dist/src/session/config-types.js +1 -0
- package/dist/src/session/index.d.ts +1 -3
- package/dist/src/session/index.js +3 -5
- package/dist/src/session/init-session-turn.d.ts +0 -6
- package/dist/src/session/init-session-turn.js +18 -18
- package/dist/src/session/init-session-turn.js.map +1 -1
- package/dist/src/session/lifecycle-timestamps.d.ts +5 -2
- package/dist/src/session/lifecycle-timestamps.js.map +1 -1
- package/dist/src/session/{parity/load-jsonl-entries.js → load-jsonl-entries.js} +1 -1
- package/dist/src/session/load-jsonl-entries.js.map +1 -0
- package/dist/src/session/manager.d.ts +5 -3
- package/dist/src/session/manager.js +1 -5
- package/dist/src/session/manager.js.map +1 -1
- package/dist/src/session/resolve-session.d.ts +3 -6
- package/dist/src/session/resolve-session.js +26 -31
- package/dist/src/session/resolve-session.js.map +1 -1
- package/dist/src/session/session-context-for-llm.js +5 -1
- package/dist/src/session/session-context-for-llm.js.map +1 -1
- package/dist/src/session/session-id.js +12 -0
- package/dist/src/session/session-id.js.map +1 -0
- package/dist/src/session/session-title.js +2 -2
- package/dist/src/session/session-workspace.d.ts +1 -1
- package/dist/src/session/session-workspace.js.map +1 -1
- package/dist/src/session/store.d.ts +14 -63
- package/dist/src/session/store.js +172 -847
- package/dist/src/session/store.js.map +1 -1
- package/dist/src/session/stored-rows-to-file-entries.d.ts +11 -0
- package/dist/src/session/stored-rows-to-file-entries.js +95 -0
- package/dist/src/session/stored-rows-to-file-entries.js.map +1 -0
- package/dist/src/session/transcript-events.d.ts +1 -2
- package/dist/src/session/transcript-events.js +5 -12
- package/dist/src/session/transcript-events.js.map +1 -1
- package/dist/src/session/transcript-format.d.ts +1 -1
- package/dist/src/session/transcript-format.js.map +1 -1
- package/dist/src/session/transcript-stats.d.ts +1 -0
- package/dist/src/session/transcript-stats.js +10 -0
- package/dist/src/session/transcript-stats.js.map +1 -0
- package/dist/src/share/share-auto.js +2 -2
- package/dist/src/share/share-store.js +3 -3
- package/dist/src/share/share-thumbnail.js +2 -2
- package/dist/src/share/share-zip.js +1 -1
- package/dist/src/share/site-share-store.js +3 -3
- package/dist/src/share/site-static-serve.js +1 -1
- package/dist/src/storage/sqlite/config-repository.d.ts +6 -0
- package/dist/src/storage/sqlite/config-repository.js +56 -0
- package/dist/src/storage/sqlite/config-repository.js.map +1 -0
- package/dist/src/storage/sqlite/connection.d.ts +38 -0
- package/dist/src/storage/sqlite/connection.js +258 -0
- package/dist/src/storage/sqlite/connection.js.map +1 -0
- package/dist/src/storage/sqlite/cron-run-repository.d.ts +5 -0
- package/dist/src/storage/sqlite/cron-run-repository.js +97 -0
- package/dist/src/storage/sqlite/cron-run-repository.js.map +1 -0
- package/dist/src/storage/sqlite/fts.d.ts +2 -0
- package/dist/src/storage/sqlite/fts.js +11 -0
- package/dist/src/storage/sqlite/fts.js.map +1 -0
- package/dist/src/storage/sqlite/index.d.ts +12 -0
- package/dist/src/storage/sqlite/index.js +13 -0
- package/dist/src/storage/sqlite/memory-index-repository.d.ts +18 -0
- package/dist/src/storage/sqlite/memory-index-repository.js +132 -0
- package/dist/src/storage/sqlite/memory-index-repository.js.map +1 -0
- package/dist/src/storage/sqlite/notes-repository.d.ts +11 -0
- package/dist/src/storage/sqlite/notes-repository.js +191 -0
- package/dist/src/storage/sqlite/notes-repository.js.map +1 -0
- package/dist/src/storage/sqlite/paths.d.ts +1 -0
- package/dist/src/storage/sqlite/paths.js +7 -0
- package/dist/src/storage/sqlite/paths.js.map +1 -0
- package/dist/src/storage/sqlite/row-mappers.d.ts +82 -0
- package/dist/src/storage/sqlite/row-mappers.js +164 -0
- package/dist/src/storage/sqlite/row-mappers.js.map +1 -0
- package/dist/src/storage/sqlite/schema.d.ts +5 -0
- package/dist/src/storage/sqlite/schema.js +43 -0
- package/dist/src/storage/sqlite/schema.js.map +1 -0
- package/dist/src/storage/sqlite/schema.sql +195 -0
- package/dist/src/storage/sqlite/session-metadata.d.ts +8 -0
- package/dist/src/storage/sqlite/session-metadata.js +83 -0
- package/dist/src/storage/sqlite/session-metadata.js.map +1 -0
- package/dist/src/storage/sqlite/session-repository.d.ts +29 -0
- package/dist/src/storage/sqlite/session-repository.js +268 -0
- package/dist/src/storage/sqlite/session-repository.js.map +1 -0
- package/dist/src/storage/sqlite/transaction.d.ts +11 -0
- package/dist/src/storage/sqlite/transaction.js +115 -0
- package/dist/src/storage/sqlite/transaction.js.map +1 -0
- package/dist/src/storage/sqlite/transcript-repository.d.ts +34 -0
- package/dist/src/storage/sqlite/transcript-repository.js +241 -0
- package/dist/src/storage/sqlite/transcript-repository.js.map +1 -0
- package/dist/src/tui/clipboard-image.js +3 -3
- package/dist/src/tui/theme-manager.js +1 -1
- package/dist/src/tui/tui-keybindings-file.js +1 -1
- package/dist/src/tui/tui-scoped-models.js +2 -2
- package/dist/src/tui/tui-settings.js +1 -1
- package/dist/src/tui/tui.js +3 -3
- package/dist/src/tunnel/frpc-binary.js +3 -3
- package/dist/src/tunnel/frpc-config.js +1 -1
- package/dist/src/tunnel/frpc-extract.js +1 -1
- package/dist/src/tunnel/tunnel-state.js +1 -1
- package/dist/src/utils/logger/audit.js +1 -1
- package/dist/src/utils/logger/log-store.js +1 -1
- package/dist/src/utils/logger/rotation.js +1 -1
- package/dist/src/voice/tts/audio.js +1 -1
- package/dist/src/voice/tts/providers/edge-speech.js +2 -2
- package/dist/src/workflows/service/workflow-session-bridge.js +41 -64
- package/dist/src/workflows/service/workflow-session-bridge.js.map +1 -1
- package/dist/src/workflows/store/event-store.js +1 -1
- package/dist/src/workflows/store/run-store.js +1 -1
- package/package.json +2 -2
- package/dist/gateway/static/root/assets/index-Bj_l8QDp.css +0 -1
- package/dist/gateway/static/root/assets/url-BHHmdJYc.js +0 -3
- package/dist/src/agent/context/workspace-templates/BOOTSTRAP.md +0 -61
- package/dist/src/agent/embedded/session-manager-cache.d.ts +0 -19
- package/dist/src/agent/embedded/session-manager-cache.js +0 -48
- package/dist/src/agent/embedded/session-manager-cache.js.map +0 -1
- package/dist/src/session/parity/artifacts.d.ts +0 -16
- package/dist/src/session/parity/artifacts.js +0 -80
- package/dist/src/session/parity/artifacts.js.map +0 -1
- package/dist/src/session/parity/jsonl-transcript-io.d.ts +0 -54
- package/dist/src/session/parity/jsonl-transcript-io.js +0 -236
- package/dist/src/session/parity/jsonl-transcript-io.js.map +0 -1
- package/dist/src/session/parity/load-jsonl-entries.js.map +0 -1
- package/dist/src/session/parity/session-id.js +0 -18
- package/dist/src/session/parity/session-id.js.map +0 -1
- package/dist/src/session/parity/sessions-json-cache.d.ts +0 -14
- package/dist/src/session/parity/sessions-json-cache.js +0 -98
- package/dist/src/session/parity/sessions-json-cache.js.map +0 -1
- package/dist/src/session/parity/sessions-json-file-read.d.ts +0 -6
- package/dist/src/session/parity/sessions-json-file-read.js +0 -19
- package/dist/src/session/parity/sessions-json-file-read.js.map +0 -1
- package/dist/src/session/parity/sessions-json-file.d.ts +0 -11
- package/dist/src/session/parity/sessions-json-file.js +0 -52
- package/dist/src/session/parity/sessions-json-file.js.map +0 -1
- package/dist/src/session/parity/sessions-json-patch.d.ts +0 -14
- package/dist/src/session/parity/sessions-json-patch.js +0 -40
- package/dist/src/session/parity/sessions-json-patch.js.map +0 -1
- package/dist/src/session/parity/transcript-file-lock.d.ts +0 -22
- package/dist/src/session/parity/transcript-file-lock.js +0 -142
- package/dist/src/session/parity/transcript-file-lock.js.map +0 -1
- package/dist/src/session/parity/transcript-pagination.d.ts +0 -29
- package/dist/src/session/parity/transcript-pagination.js +0 -132
- package/dist/src/session/parity/transcript-pagination.js.map +0 -1
- package/dist/src/session/parity/transcript-paths.d.ts +0 -13
- package/dist/src/session/parity/transcript-paths.js +0 -64
- package/dist/src/session/parity/transcript-paths.js.map +0 -1
- package/dist/src/session/parity/xopc-session-disk-entry.d.ts +0 -22
- package/dist/src/session/search-index-cache.d.ts +0 -6
- package/dist/src/session/search-index-cache.js +0 -44
- package/dist/src/session/search-index-cache.js.map +0 -1
- package/dist/src/session/search-index.d.ts +0 -20
- package/dist/src/session/search-index.js +0 -124
- package/dist/src/session/search-index.js.map +0 -1
- /package/dist/src/{session/parity/xopc-session-disk-entry.js → cron/execution-types.js} +0 -0
- /package/dist/src/session/{parity/load-jsonl-entries.d.ts → load-jsonl-entries.d.ts} +0 -0
- /package/dist/src/session/{parity/session-id.d.ts → session-id.d.ts} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-turn.js","names":[],"sources":["../../../../src/agent/embedded/run-turn.ts"],"sourcesContent":["import type { AgentMessage } from '@earendil-works/pi-agent-core';\nimport type { ImageContent, Model, Api } from '@earendil-works/pi-ai';\n\nimport { createLogger } from '../../utils/logger.js';\nimport { registerEmbeddedRun, unregisterEmbeddedRun } from './runs.js';\nimport { subscribeEmbeddedSessionEvents, lastAssistantPlainText } from './subscribe-session.js';\nimport type { RunXopcEmbeddedTurnParams, RunXopcEmbeddedTurnResult } from './types.js';\nimport {\n getAssistantTurnErrorMessage,\n isAssistantTurnAborted,\n isAssistantTurnFailed,\n maybeRetryTurnAfterTransientLlmFailure,\n} from '../orchestration/llm-turn-retry.js';\nimport { runAgentTurnWithTimeout, resolveAgentTurnTimeoutMs } from '../orchestration/run-agent-turn-with-timeout.js';\nimport { detectToolLoops, type RecentToolCall } from '../orchestration/loop-guard.js';\nimport {\n acquireEmbeddedSessionRunner,\n resolveEmbeddedTranscriptInputs,\n} from './session-runner.js';\nimport { wrapStreamFnForXopcExtensions } from './xopc-stream-bridge.js';\n\nconst log = createLogger('EmbeddedRun');\nconst LOG_PREVIEW_MAX_CHARS = 300;\n\nfunction truncateForLog(value: string, maxChars = LOG_PREVIEW_MAX_CHARS): string {\n return value.length > maxChars ? `${value.slice(0, maxChars)}…` : value;\n}\n\nfunction extractTextFromContent(content: unknown): string {\n if (typeof content === 'string') {\n return content;\n }\n if (!Array.isArray(content)) {\n return '';\n }\n return content\n .filter((block): block is { type: string; text: string } => {\n return !!block && typeof block === 'object' && (block as { type?: string }).type === 'text';\n })\n .map((block) => block.text)\n .join('');\n}\n\nfunction extractRecentToolCalls(messages: readonly { role?: string; content?: unknown }[]): RecentToolCall[] {\n const calls: RecentToolCall[] = [];\n for (const message of messages) {\n if (message.role !== 'assistant' || !Array.isArray(message.content)) continue;\n for (const block of message.content) {\n if (block && typeof block === 'object' && (block as { type?: string }).type === 'toolCall') {\n const toolCall = block as { name: string; arguments: unknown };\n calls.push({ name: toolCall.name, params: toolCall.arguments });\n }\n }\n }\n return calls;\n}\n\nfunction getLastUserMessagePreview(messages: readonly { role?: string; content?: unknown }[]): string | undefined {\n for (let messageIndex = messages.length - 1; messageIndex >= 0; messageIndex--) {\n const message = messages[messageIndex];\n if (message?.role !== 'user') {\n continue;\n }\n const text = extractTextFromContent(message.content).trim();\n return text ? truncateForLog(text) : undefined;\n }\n return undefined;\n}\n\nfunction requireEmbeddedModel(model: Model<Api> | undefined, modelRef: string): Model<Api> {\n if (!model?.id || !model?.provider) {\n throw new Error(`Invalid model for embedded run: ${modelRef}`);\n }\n return model;\n}\n\nfunction userMessageToPromptText(message: AgentMessage): string {\n const content = (message as { content?: unknown }).content;\n if (typeof content === 'string') {\n return content;\n }\n if (Array.isArray(content)) {\n return content\n .filter((block): block is { type: 'text'; text: string } => {\n return !!block && typeof block === 'object' && (block as { type?: string }).type === 'text';\n })\n .map((block) => block.text)\n .join('');\n }\n return '';\n}\n\nfunction userMessageToPromptImages(message: AgentMessage): ImageContent[] {\n const content = (message as { content?: unknown }).content;\n if (!Array.isArray(content)) {\n return [];\n }\n\n const images: ImageContent[] = [];\n for (const block of content) {\n if (!block || typeof block !== 'object') {\n continue;\n }\n const typedBlock = block as { type?: string; data?: unknown; mimeType?: unknown };\n if (typedBlock.type !== 'image' || typeof typedBlock.data !== 'string' || typedBlock.data.length === 0) {\n continue;\n }\n images.push({\n type: 'image',\n data: typedBlock.data,\n mimeType: typeof typedBlock.mimeType === 'string' ? typedBlock.mimeType : 'image/png',\n });\n }\n return images;\n}\n\nexport async function runXopcEmbeddedTurn(params: RunXopcEmbeddedTurnParams): Promise<RunXopcEmbeddedTurnResult> {\n const {\n sessionKey,\n runId,\n userMessage,\n model,\n tools,\n systemPrompt,\n thinkingLevel,\n workspaceDir,\n sessionStore,\n onEvent,\n } = params;\n\n const timeoutMs = params.timeoutMs || resolveAgentTurnTimeoutMs();\n const resolvedModel = requireEmbeddedModel(model, params.modelRef);\n const transcript = await resolveEmbeddedTranscriptInputs(sessionStore, sessionKey);\n\n let runner: Awaited<ReturnType<typeof acquireEmbeddedSessionRunner>> | undefined;\n let unsubscribe: (() => void) | undefined;\n\n try {\n runner = await acquireEmbeddedSessionRunner({\n sessionKey,\n sessionId: transcript.sessionId,\n sessionFile: transcript.sessionFile,\n sessionsDir: transcript.sessionsDir,\n hadSessionFile: transcript.hadSessionFile,\n workspaceDir,\n model: resolvedModel,\n modelRef: params.modelRef,\n tools,\n systemPrompt,\n thinkingLevel: thinkingLevel ?? 'medium',\n });\n\n const { session, reused } = runner;\n\n const streamFnWithXopcExtensions = wrapStreamFnForXopcExtensions(session.agent.streamFn);\n const loggingStreamFn: typeof session.agent.streamFn = (streamModel, context, options) => {\n const recentToolCalls = extractRecentToolCalls(context.messages);\n const loopGuard = detectToolLoops(recentToolCalls);\n\n let effectiveContext = context;\n if (loopGuard.injection || loopGuard.hiddenTools.size > 0) {\n const messages = loopGuard.injection\n ? [...context.messages, { role: 'user' as const, content: loopGuard.injection, timestamp: Date.now() }]\n : context.messages;\n\n const contextTools = loopGuard.hiddenTools.size > 0 && context.tools\n ? context.tools.filter((t) => !loopGuard.hiddenTools.has(t.name))\n : context.tools;\n\n effectiveContext = { ...context, messages, tools: contextTools };\n }\n\n log.debug(\n {\n sessionKey,\n runId,\n reusedRunner: reused,\n modelRef: `${streamModel.provider}/${streamModel.id}`,\n systemPromptLength: effectiveContext.systemPrompt?.length ?? 0,\n messageCount: effectiveContext.messages.length,\n toolCount: effectiveContext.tools?.length ?? 0,\n lastUserMessagePreview: getLastUserMessagePreview(effectiveContext.messages),\n loopWarningInjected: !!loopGuard.injection,\n hiddenToolCount: loopGuard.hiddenTools.size,\n },\n 'Sending messages to AI',\n );\n return streamFnWithXopcExtensions(streamModel, effectiveContext, options);\n };\n session.agent.streamFn = loggingStreamFn;\n\n if (onEvent) {\n unsubscribe = subscribeEmbeddedSessionEvents(session, onEvent);\n }\n\n const handle = {\n sessionKey,\n sessionId: transcript.sessionId,\n runId,\n session,\n abort: async () => {\n await session.abort();\n },\n };\n registerEmbeddedRun(handle);\n\n const abortListener = () => {\n void session.abort();\n };\n if (params.abortSignal) {\n if (params.abortSignal.aborted) {\n await session.abort();\n return { ok: false, errorMessage: 'aborted' };\n }\n params.abortSignal.addEventListener('abort', abortListener, { once: true });\n }\n\n try {\n await runAgentTurnWithTimeout(\n session.agent,\n async () => {\n const text = userMessageToPromptText(userMessage);\n const images = [...(params.images ?? []), ...userMessageToPromptImages(userMessage)];\n await session.prompt(text, images.length > 0 ? { images } : undefined);\n await session.agent.waitForIdle();\n await maybeRetryTurnAfterTransientLlmFailure(session.agent, { sessionKey, log });\n },\n timeoutMs,\n );\n\n if (isAssistantTurnAborted(session.agent)) {\n return { ok: true, lastAssistantText: lastAssistantPlainText(session) };\n }\n if (isAssistantTurnFailed(session.agent)) {\n return {\n ok: false,\n errorMessage: getAssistantTurnErrorMessage(session.agent) ?? 'Assistant turn failed',\n lastAssistantText: lastAssistantPlainText(session),\n };\n }\n\n return { ok: true, lastAssistantText: lastAssistantPlainText(session) };\n } finally {\n params.abortSignal?.removeEventListener('abort', abortListener);\n unregisterEmbeddedRun(handle);\n }\n } catch (err) {\n const em = err instanceof Error ? err.message : String(err);\n log.error({ err, sessionKey, runId }, `Embedded run failed: ${em}`);\n onEvent?.({ type: 'error', content: em });\n return { ok: false, errorMessage: em };\n } finally {\n unsubscribe?.();\n try {\n runner?.piSm.flushPendingToolResults?.();\n } catch {\n /* ignore */\n }\n runner?.release();\n }\n}\n\nexport { abortEmbeddedRun, queueEmbeddedSteer } from './runs.js';\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"run-turn.js","names":[],"sources":["../../../../src/agent/embedded/run-turn.ts"],"sourcesContent":["import type { AgentMessage } from '@earendil-works/pi-agent-core';\nimport type { ImageContent, Model, Api } from '@earendil-works/pi-ai';\n\nimport { createLogger } from '../../utils/logger.js';\nimport { registerEmbeddedRun, unregisterEmbeddedRun } from './runs.js';\nimport { subscribeEmbeddedSessionEvents, lastAssistantPlainText } from './subscribe-session.js';\nimport type { RunXopcEmbeddedTurnParams, RunXopcEmbeddedTurnResult } from './types.js';\nimport {\n getAssistantTurnErrorMessage,\n isAssistantTurnAborted,\n isAssistantTurnFailed,\n maybeRetryTurnAfterTransientLlmFailure,\n} from '../orchestration/llm-turn-retry.js';\nimport { runAgentTurnWithTimeout, resolveAgentTurnTimeoutMs } from '../orchestration/run-agent-turn-with-timeout.js';\nimport { detectToolLoops, type RecentToolCall } from '../orchestration/loop-guard.js';\nimport {\n acquireEmbeddedSessionRunner,\n resolveEmbeddedTranscriptInputs,\n} from './session-runner.js';\nimport { wrapStreamFnForXopcExtensions } from './xopc-stream-bridge.js';\n\nconst log = createLogger('EmbeddedRun');\nconst LOG_PREVIEW_MAX_CHARS = 300;\n\nfunction truncateForLog(value: string, maxChars = LOG_PREVIEW_MAX_CHARS): string {\n return value.length > maxChars ? `${value.slice(0, maxChars)}…` : value;\n}\n\nfunction extractTextFromContent(content: unknown): string {\n if (typeof content === 'string') {\n return content;\n }\n if (!Array.isArray(content)) {\n return '';\n }\n return content\n .filter((block): block is { type: string; text: string } => {\n return !!block && typeof block === 'object' && (block as { type?: string }).type === 'text';\n })\n .map((block) => block.text)\n .join('');\n}\n\nfunction extractRecentToolCalls(messages: readonly { role?: string; content?: unknown }[]): RecentToolCall[] {\n const calls: RecentToolCall[] = [];\n for (const message of messages) {\n if (message.role !== 'assistant' || !Array.isArray(message.content)) continue;\n for (const block of message.content) {\n if (block && typeof block === 'object' && (block as { type?: string }).type === 'toolCall') {\n const toolCall = block as { name: string; arguments: unknown };\n calls.push({ name: toolCall.name, params: toolCall.arguments });\n }\n }\n }\n return calls;\n}\n\nfunction getLastUserMessagePreview(messages: readonly { role?: string; content?: unknown }[]): string | undefined {\n for (let messageIndex = messages.length - 1; messageIndex >= 0; messageIndex--) {\n const message = messages[messageIndex];\n if (message?.role !== 'user') {\n continue;\n }\n const text = extractTextFromContent(message.content).trim();\n return text ? truncateForLog(text) : undefined;\n }\n return undefined;\n}\n\nfunction requireEmbeddedModel(model: Model<Api> | undefined, modelRef: string): Model<Api> {\n if (!model?.id || !model?.provider) {\n throw new Error(`Invalid model for embedded run: ${modelRef}`);\n }\n return model;\n}\n\nfunction userMessageToPromptText(message: AgentMessage): string {\n const content = (message as { content?: unknown }).content;\n if (typeof content === 'string') {\n return content;\n }\n if (Array.isArray(content)) {\n return content\n .filter((block): block is { type: 'text'; text: string } => {\n return !!block && typeof block === 'object' && (block as { type?: string }).type === 'text';\n })\n .map((block) => block.text)\n .join('');\n }\n return '';\n}\n\nfunction userMessageToPromptImages(message: AgentMessage): ImageContent[] {\n const content = (message as { content?: unknown }).content;\n if (!Array.isArray(content)) {\n return [];\n }\n\n const images: ImageContent[] = [];\n for (const block of content) {\n if (!block || typeof block !== 'object') {\n continue;\n }\n const typedBlock = block as { type?: string; data?: unknown; mimeType?: unknown };\n if (typedBlock.type !== 'image' || typeof typedBlock.data !== 'string' || typedBlock.data.length === 0) {\n continue;\n }\n images.push({\n type: 'image',\n data: typedBlock.data,\n mimeType: typeof typedBlock.mimeType === 'string' ? typedBlock.mimeType : 'image/png',\n });\n }\n return images;\n}\n\nexport async function runXopcEmbeddedTurn(params: RunXopcEmbeddedTurnParams): Promise<RunXopcEmbeddedTurnResult> {\n const {\n sessionKey,\n runId,\n userMessage,\n model,\n tools,\n systemPrompt,\n thinkingLevel,\n workspaceDir,\n sessionStore,\n onEvent,\n } = params;\n\n const timeoutMs = params.timeoutMs || resolveAgentTurnTimeoutMs();\n const resolvedModel = requireEmbeddedModel(model, params.modelRef);\n const transcript = await resolveEmbeddedTranscriptInputs(sessionStore, sessionKey);\n\n let runner: Awaited<ReturnType<typeof acquireEmbeddedSessionRunner>> | undefined;\n let unsubscribe: (() => void) | undefined;\n\n try {\n runner = await acquireEmbeddedSessionRunner({\n sessionKey,\n sessionId: transcript.sessionId,\n workspaceDir,\n model: resolvedModel,\n modelRef: params.modelRef,\n tools,\n systemPrompt,\n thinkingLevel: thinkingLevel ?? 'medium',\n });\n\n const { session, reused } = runner;\n\n const streamFnWithXopcExtensions = wrapStreamFnForXopcExtensions(session.agent.streamFn);\n const loggingStreamFn: typeof session.agent.streamFn = (streamModel, context, options) => {\n const recentToolCalls = extractRecentToolCalls(context.messages);\n const loopGuard = detectToolLoops(recentToolCalls);\n\n let effectiveContext = context;\n if (loopGuard.injection || loopGuard.hiddenTools.size > 0) {\n const messages = loopGuard.injection\n ? [...context.messages, { role: 'user' as const, content: loopGuard.injection, timestamp: Date.now() }]\n : context.messages;\n\n const contextTools = loopGuard.hiddenTools.size > 0 && context.tools\n ? context.tools.filter((t) => !loopGuard.hiddenTools.has(t.name))\n : context.tools;\n\n effectiveContext = { ...context, messages, tools: contextTools };\n }\n\n log.debug(\n {\n sessionKey,\n runId,\n reusedRunner: reused,\n modelRef: `${streamModel.provider}/${streamModel.id}`,\n systemPromptLength: effectiveContext.systemPrompt?.length ?? 0,\n messageCount: effectiveContext.messages.length,\n toolCount: effectiveContext.tools?.length ?? 0,\n lastUserMessagePreview: getLastUserMessagePreview(effectiveContext.messages),\n loopWarningInjected: !!loopGuard.injection,\n hiddenToolCount: loopGuard.hiddenTools.size,\n },\n 'Sending messages to AI',\n );\n return streamFnWithXopcExtensions(streamModel, effectiveContext, options);\n };\n session.agent.streamFn = loggingStreamFn;\n\n if (onEvent) {\n unsubscribe = subscribeEmbeddedSessionEvents(session, onEvent);\n }\n\n const handle = {\n sessionKey,\n sessionId: transcript.sessionId,\n runId,\n session,\n abort: async () => {\n await session.abort();\n },\n };\n registerEmbeddedRun(handle);\n\n const abortListener = () => {\n void session.abort();\n };\n if (params.abortSignal) {\n if (params.abortSignal.aborted) {\n await session.abort();\n return { ok: false, errorMessage: 'aborted' };\n }\n params.abortSignal.addEventListener('abort', abortListener, { once: true });\n }\n\n try {\n await runAgentTurnWithTimeout(\n session.agent,\n async () => {\n const text = userMessageToPromptText(userMessage);\n const images = [...(params.images ?? []), ...userMessageToPromptImages(userMessage)];\n await session.prompt(text, images.length > 0 ? { images } : undefined);\n await session.agent.waitForIdle();\n await maybeRetryTurnAfterTransientLlmFailure(session.agent, { sessionKey, log });\n },\n timeoutMs,\n );\n\n if (isAssistantTurnAborted(session.agent)) {\n return { ok: true, lastAssistantText: lastAssistantPlainText(session) };\n }\n if (isAssistantTurnFailed(session.agent)) {\n return {\n ok: false,\n errorMessage: getAssistantTurnErrorMessage(session.agent) ?? 'Assistant turn failed',\n lastAssistantText: lastAssistantPlainText(session),\n };\n }\n\n return { ok: true, lastAssistantText: lastAssistantPlainText(session) };\n } finally {\n params.abortSignal?.removeEventListener('abort', abortListener);\n unregisterEmbeddedRun(handle);\n }\n } catch (err) {\n const em = err instanceof Error ? err.message : String(err);\n log.error({ err, sessionKey, runId }, `Embedded run failed: ${em}`);\n onEvent?.({ type: 'error', content: em });\n return { ok: false, errorMessage: em };\n } finally {\n unsubscribe?.();\n try {\n runner?.piSm.flushPendingToolResults?.();\n } catch {\n /* ignore */\n }\n runner?.release();\n }\n}\n\nexport { abortEmbeddedRun, queueEmbeddedSteer } from './runs.js';\n"],"mappings":";;;;;;;;;;aAGqD;AAkBrD,MAAM,MAAM,aAAa,cAAc;AACvC,MAAM,wBAAwB;AAE9B,SAAS,eAAe,OAAe,WAAW,uBAA+B;AAC/E,QAAO,MAAM,SAAS,WAAW,GAAG,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK;;AAGpE,SAAS,uBAAuB,SAA0B;AACxD,KAAI,OAAO,YAAY,SACrB,QAAO;AAET,KAAI,CAAC,MAAM,QAAQ,QAAQ,CACzB,QAAO;AAET,QAAO,QACJ,QAAQ,UAAmD;AAC1D,SAAO,CAAC,CAAC,SAAS,OAAO,UAAU,YAAa,MAA4B,SAAS;GACrF,CACD,KAAK,UAAU,MAAM,KAAK,CAC1B,KAAK,GAAG;;AAGb,SAAS,uBAAuB,UAA6E;CAC3G,MAAM,QAA0B,EAAE;AAClC,MAAK,MAAM,WAAW,UAAU;AAC9B,MAAI,QAAQ,SAAS,eAAe,CAAC,MAAM,QAAQ,QAAQ,QAAQ,CAAE;AACrE,OAAK,MAAM,SAAS,QAAQ,QAC1B,KAAI,SAAS,OAAO,UAAU,YAAa,MAA4B,SAAS,YAAY;GAC1F,MAAM,WAAW;AACjB,SAAM,KAAK;IAAE,MAAM,SAAS;IAAM,QAAQ,SAAS;IAAW,CAAC;;;AAIrE,QAAO;;AAGT,SAAS,0BAA0B,UAA+E;AAChH,MAAK,IAAI,eAAe,SAAS,SAAS,GAAG,gBAAgB,GAAG,gBAAgB;EAC9E,MAAM,UAAU,SAAS;AACzB,MAAI,SAAS,SAAS,OACpB;EAEF,MAAM,OAAO,uBAAuB,QAAQ,QAAQ,CAAC,MAAM;AAC3D,SAAO,OAAO,eAAe,KAAK,GAAG,KAAA;;;AAKzC,SAAS,qBAAqB,OAA+B,UAA8B;AACzF,KAAI,CAAC,OAAO,MAAM,CAAC,OAAO,SACxB,OAAM,IAAI,MAAM,mCAAmC,WAAW;AAEhE,QAAO;;AAGT,SAAS,wBAAwB,SAA+B;CAC9D,MAAM,UAAW,QAAkC;AACnD,KAAI,OAAO,YAAY,SACrB,QAAO;AAET,KAAI,MAAM,QAAQ,QAAQ,CACxB,QAAO,QACJ,QAAQ,UAAmD;AAC1D,SAAO,CAAC,CAAC,SAAS,OAAO,UAAU,YAAa,MAA4B,SAAS;GACrF,CACD,KAAK,UAAU,MAAM,KAAK,CAC1B,KAAK,GAAG;AAEb,QAAO;;AAGT,SAAS,0BAA0B,SAAuC;CACxE,MAAM,UAAW,QAAkC;AACnD,KAAI,CAAC,MAAM,QAAQ,QAAQ,CACzB,QAAO,EAAE;CAGX,MAAM,SAAyB,EAAE;AACjC,MAAK,MAAM,SAAS,SAAS;AAC3B,MAAI,CAAC,SAAS,OAAO,UAAU,SAC7B;EAEF,MAAM,aAAa;AACnB,MAAI,WAAW,SAAS,WAAW,OAAO,WAAW,SAAS,YAAY,WAAW,KAAK,WAAW,EACnG;AAEF,SAAO,KAAK;GACV,MAAM;GACN,MAAM,WAAW;GACjB,UAAU,OAAO,WAAW,aAAa,WAAW,WAAW,WAAW;GAC3E,CAAC;;AAEJ,QAAO;;AAGT,eAAsB,oBAAoB,QAAuE;CAC/G,MAAM,EACJ,YACA,OACA,aACA,OACA,OACA,cACA,eACA,cACA,cACA,YACE;CAEJ,MAAM,YAAY,OAAO,aAAa,2BAA2B;CACjE,MAAM,gBAAgB,qBAAqB,OAAO,OAAO,SAAS;CAClE,MAAM,aAAa,MAAM,gCAAgC,cAAc,WAAW;CAElF,IAAI;CACJ,IAAI;AAEJ,KAAI;AACF,WAAS,MAAM,6BAA6B;GAC1C;GACA,WAAW,WAAW;GACtB;GACA,OAAO;GACP,UAAU,OAAO;GACjB;GACA;GACA,eAAe,iBAAiB;GACjC,CAAC;EAEF,MAAM,EAAE,SAAS,WAAW;EAE5B,MAAM,6BAA6B,8BAA8B,QAAQ,MAAM,SAAS;EACxF,MAAM,mBAAkD,aAAa,SAAS,YAAY;GAExF,MAAM,YAAY,gBADM,uBAAuB,QAAQ,SACN,CAAC;GAElD,IAAI,mBAAmB;AACvB,OAAI,UAAU,aAAa,UAAU,YAAY,OAAO,GAAG;IACzD,MAAM,WAAW,UAAU,YACvB,CAAC,GAAG,QAAQ,UAAU;KAAE,MAAM;KAAiB,SAAS,UAAU;KAAW,WAAW,KAAK,KAAK;KAAE,CAAC,GACrG,QAAQ;IAEZ,MAAM,eAAe,UAAU,YAAY,OAAO,KAAK,QAAQ,QAC3D,QAAQ,MAAM,QAAQ,MAAM,CAAC,UAAU,YAAY,IAAI,EAAE,KAAK,CAAC,GAC/D,QAAQ;AAEZ,uBAAmB;KAAE,GAAG;KAAS;KAAU,OAAO;KAAc;;AAGlE,OAAI,MACF;IACE;IACA;IACA,cAAc;IACd,UAAU,GAAG,YAAY,SAAS,GAAG,YAAY;IACjD,oBAAoB,iBAAiB,cAAc,UAAU;IAC7D,cAAc,iBAAiB,SAAS;IACxC,WAAW,iBAAiB,OAAO,UAAU;IAC7C,wBAAwB,0BAA0B,iBAAiB,SAAS;IAC5E,qBAAqB,CAAC,CAAC,UAAU;IACjC,iBAAiB,UAAU,YAAY;IACxC,EACD,yBACD;AACD,UAAO,2BAA2B,aAAa,kBAAkB,QAAQ;;AAE3E,UAAQ,MAAM,WAAW;AAEzB,MAAI,QACF,eAAc,+BAA+B,SAAS,QAAQ;EAGhE,MAAM,SAAS;GACb;GACA,WAAW,WAAW;GACtB;GACA;GACA,OAAO,YAAY;AACjB,UAAM,QAAQ,OAAO;;GAExB;AACD,sBAAoB,OAAO;EAE3B,MAAM,sBAAsB;AACrB,WAAQ,OAAO;;AAEtB,MAAI,OAAO,aAAa;AACtB,OAAI,OAAO,YAAY,SAAS;AAC9B,UAAM,QAAQ,OAAO;AACrB,WAAO;KAAE,IAAI;KAAO,cAAc;KAAW;;AAE/C,UAAO,YAAY,iBAAiB,SAAS,eAAe,EAAE,MAAM,MAAM,CAAC;;AAG7E,MAAI;AACF,SAAM,wBACJ,QAAQ,OACR,YAAY;IACV,MAAM,OAAO,wBAAwB,YAAY;IACjD,MAAM,SAAS,CAAC,GAAI,OAAO,UAAU,EAAE,EAAG,GAAG,0BAA0B,YAAY,CAAC;AACpF,UAAM,QAAQ,OAAO,MAAM,OAAO,SAAS,IAAI,EAAE,QAAQ,GAAG,KAAA,EAAU;AACtE,UAAM,QAAQ,MAAM,aAAa;AACjC,UAAM,uCAAuC,QAAQ,OAAO;KAAE;KAAY;KAAK,CAAC;MAElF,UACD;AAED,OAAI,uBAAuB,QAAQ,MAAM,CACvC,QAAO;IAAE,IAAI;IAAM,mBAAmB,uBAAuB,QAAQ;IAAE;AAEzE,OAAI,sBAAsB,QAAQ,MAAM,CACtC,QAAO;IACL,IAAI;IACJ,cAAc,6BAA6B,QAAQ,MAAM,IAAI;IAC7D,mBAAmB,uBAAuB,QAAQ;IACnD;AAGH,UAAO;IAAE,IAAI;IAAM,mBAAmB,uBAAuB,QAAQ;IAAE;YAC/D;AACR,UAAO,aAAa,oBAAoB,SAAS,cAAc;AAC/D,yBAAsB,OAAO;;UAExB,KAAK;EACZ,MAAM,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC3D,MAAI,MAAM;GAAE;GAAK;GAAY;GAAO,EAAE,wBAAwB,KAAK;AACnE,YAAU;GAAE,MAAM;GAAS,SAAS;GAAI,CAAC;AACzC,SAAO;GAAE,IAAI;GAAO,cAAc;GAAI;WAC9B;AACR,iBAAe;AACf,MAAI;AACF,WAAQ,KAAK,2BAA2B;UAClC;AAGR,UAAQ,SAAS"}
|
|
@@ -2,20 +2,3 @@
|
|
|
2
2
|
* Legacy xopc transcripts may omit `usage` on assistant rows; pi AgentSession crashes on turn end.
|
|
3
3
|
*/
|
|
4
4
|
export declare function repairAssistantUsageInSessionManager(sessionManager: unknown): void;
|
|
5
|
-
/**
|
|
6
|
-
* pi-coding-agent SessionManager persistence quirk:
|
|
7
|
-
* - If the file exists but has no assistant message, SessionManager marks itself `flushed=true`
|
|
8
|
-
* and will never persist the initial user message.
|
|
9
|
-
* - If the file doesn't exist yet, SessionManager builds a new session in memory and flushes
|
|
10
|
-
* header+user+assistant once the first assistant arrives (good).
|
|
11
|
-
*
|
|
12
|
-
* This normalizes the file/session state so the first user prompt is persisted before the first
|
|
13
|
-
* assistant entry, even for pre-created session files.
|
|
14
|
-
*/
|
|
15
|
-
export declare function prepareSessionManagerForRun(params: {
|
|
16
|
-
sessionManager: unknown;
|
|
17
|
-
sessionFile: string;
|
|
18
|
-
hadSessionFile: boolean;
|
|
19
|
-
sessionId: string;
|
|
20
|
-
cwd: string;
|
|
21
|
-
}): Promise<void>;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
1
|
//#region src/agent/embedded/session-manager-init.ts
|
|
3
2
|
/** pi-coding-agent auto-compaction calls `calculateContextTokens(usage)` without a null check. */
|
|
4
3
|
const EMPTY_ASSISTANT_USAGE = {
|
|
@@ -45,41 +44,7 @@ function repairAssistantUsageInSessionManager(sessionManager) {
|
|
|
45
44
|
}
|
|
46
45
|
}
|
|
47
46
|
}
|
|
48
|
-
/**
|
|
49
|
-
* pi-coding-agent SessionManager persistence quirk:
|
|
50
|
-
* - If the file exists but has no assistant message, SessionManager marks itself `flushed=true`
|
|
51
|
-
* and will never persist the initial user message.
|
|
52
|
-
* - If the file doesn't exist yet, SessionManager builds a new session in memory and flushes
|
|
53
|
-
* header+user+assistant once the first assistant arrives (good).
|
|
54
|
-
*
|
|
55
|
-
* This normalizes the file/session state so the first user prompt is persisted before the first
|
|
56
|
-
* assistant entry, even for pre-created session files.
|
|
57
|
-
*/
|
|
58
|
-
async function prepareSessionManagerForRun(params) {
|
|
59
|
-
const sm = params.sessionManager;
|
|
60
|
-
const header = sm.fileEntries.find((e) => e.type === "session");
|
|
61
|
-
const hasAssistant = sm.fileEntries.some((e) => e.type === "message" && e.message?.role === "assistant");
|
|
62
|
-
if (!params.hadSessionFile && header) {
|
|
63
|
-
header.id = params.sessionId;
|
|
64
|
-
header.cwd = params.cwd;
|
|
65
|
-
sm.sessionId = params.sessionId;
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
if (params.hadSessionFile && header && !hasAssistant) {
|
|
69
|
-
try {
|
|
70
|
-
await fs.unlink(params.sessionFile);
|
|
71
|
-
} catch (err) {
|
|
72
|
-
if (err.code !== "ENOENT") throw err;
|
|
73
|
-
}
|
|
74
|
-
sm.fileEntries = [header];
|
|
75
|
-
sm.byId?.clear?.();
|
|
76
|
-
sm.labelsById?.clear?.();
|
|
77
|
-
sm.leafId = null;
|
|
78
|
-
sm.flushed = false;
|
|
79
|
-
}
|
|
80
|
-
repairAssistantUsageInSessionManager(sm);
|
|
81
|
-
}
|
|
82
47
|
//#endregion
|
|
83
|
-
export {
|
|
48
|
+
export { repairAssistantUsageInSessionManager };
|
|
84
49
|
|
|
85
50
|
//# sourceMappingURL=session-manager-init.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-manager-init.js","names":[],"sources":["../../../../src/agent/embedded/session-manager-init.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"session-manager-init.js","names":[],"sources":["../../../../src/agent/embedded/session-manager-init.ts"],"sourcesContent":["type SessionHeaderEntry = { type: \"session\"; id?: string; cwd?: string };\ntype SessionMessageEntry = {\n type: \"message\";\n message?: {\n role?: string;\n stopReason?: string;\n usage?: Record<string, unknown>;\n };\n};\n\n/** pi-coding-agent auto-compaction calls `calculateContextTokens(usage)` without a null check. */\nconst EMPTY_ASSISTANT_USAGE = {\n input: 0,\n output: 0,\n cacheRead: 0,\n cacheWrite: 0,\n totalTokens: 0,\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n};\n\n/**\n * Legacy xopc transcripts may omit `usage` on assistant rows; pi AgentSession crashes on turn end.\n */\nexport function repairAssistantUsageInSessionManager(sessionManager: unknown): void {\n const sm = sessionManager as { fileEntries?: Array<SessionHeaderEntry | SessionMessageEntry | { type: string }> };\n if (!Array.isArray(sm.fileEntries)) {\n return;\n }\n for (const entry of sm.fileEntries) {\n if (entry.type !== \"message\") {\n continue;\n }\n const msg = (entry as SessionMessageEntry).message;\n if (msg?.role !== \"assistant\") {\n continue;\n }\n if (msg.stopReason === \"aborted\" || msg.stopReason === \"error\") {\n continue;\n }\n if (!msg.usage || typeof msg.usage !== \"object\") {\n msg.usage = { ...EMPTY_ASSISTANT_USAGE };\n } else if (msg.usage.totalTokens === undefined) {\n const u = msg.usage;\n const input = typeof u.input === \"number\" ? u.input : 0;\n const output = typeof u.output === \"number\" ? u.output : 0;\n const cacheRead = typeof u.cacheRead === \"number\" ? u.cacheRead : 0;\n const cacheWrite = typeof u.cacheWrite === \"number\" ? u.cacheWrite : 0;\n msg.usage = {\n ...EMPTY_ASSISTANT_USAGE,\n ...u,\n input,\n output,\n cacheRead,\n cacheWrite,\n totalTokens: input + output + cacheRead + cacheWrite,\n };\n }\n }\n}\n"],"mappings":";;AAWA,MAAM,wBAAwB;CAC5B,OAAO;CACP,QAAQ;CACR,WAAW;CACX,YAAY;CACZ,aAAa;CACb,MAAM;EAAE,OAAO;EAAG,QAAQ;EAAG,WAAW;EAAG,YAAY;EAAG,OAAO;EAAG;CACrE;;;;AAKD,SAAgB,qCAAqC,gBAA+B;CAClF,MAAM,KAAK;AACX,KAAI,CAAC,MAAM,QAAQ,GAAG,YAAY,CAChC;AAEF,MAAK,MAAM,SAAS,GAAG,aAAa;AAClC,MAAI,MAAM,SAAS,UACjB;EAEF,MAAM,MAAO,MAA8B;AAC3C,MAAI,KAAK,SAAS,YAChB;AAEF,MAAI,IAAI,eAAe,aAAa,IAAI,eAAe,QACrD;AAEF,MAAI,CAAC,IAAI,SAAS,OAAO,IAAI,UAAU,SACrC,KAAI,QAAQ,EAAE,GAAG,uBAAuB;WAC/B,IAAI,MAAM,gBAAgB,KAAA,GAAW;GAC9C,MAAM,IAAI,IAAI;GACd,MAAM,QAAQ,OAAO,EAAE,UAAU,WAAW,EAAE,QAAQ;GACtD,MAAM,SAAS,OAAO,EAAE,WAAW,WAAW,EAAE,SAAS;GACzD,MAAM,YAAY,OAAO,EAAE,cAAc,WAAW,EAAE,YAAY;GAClE,MAAM,aAAa,OAAO,EAAE,eAAe,WAAW,EAAE,aAAa;AACrE,OAAI,QAAQ;IACV,GAAG;IACH,GAAG;IACH;IACA;IACA;IACA;IACA,aAAa,QAAQ,SAAS,YAAY;IAC3C"}
|
|
@@ -3,9 +3,8 @@ import type { ThinkingLevel } from '@earendil-works/pi-agent-core';
|
|
|
3
3
|
import { type AgentSession } from '@earendil-works/pi-coding-agent';
|
|
4
4
|
import type { Model, Api } from '@earendil-works/pi-ai';
|
|
5
5
|
import { type GuardedPiTranscriptManager } from './session-tool-result-guard.js';
|
|
6
|
-
import { type SessionManagerCache } from './session-manager-cache.js';
|
|
7
6
|
export type EmbeddedRunnerFingerprintInput = {
|
|
8
|
-
|
|
7
|
+
sessionId: string;
|
|
9
8
|
workspaceDir: string;
|
|
10
9
|
modelRef: string;
|
|
11
10
|
toolNames: readonly string[];
|
|
@@ -16,9 +15,6 @@ export declare function buildEmbeddedRunnerFingerprint(input: EmbeddedRunnerFing
|
|
|
16
15
|
export type AcquireEmbeddedSessionRunnerParams = {
|
|
17
16
|
sessionKey: string;
|
|
18
17
|
sessionId: string;
|
|
19
|
-
sessionFile: string;
|
|
20
|
-
sessionsDir: string;
|
|
21
|
-
hadSessionFile: boolean;
|
|
22
18
|
workspaceDir: string;
|
|
23
19
|
model: Model<Api>;
|
|
24
20
|
modelRef: string;
|
|
@@ -42,8 +38,6 @@ export interface EmbeddedSessionRunnerPoolStats {
|
|
|
42
38
|
export declare function isEmbeddedSessionRunnerEnabled(): boolean;
|
|
43
39
|
export declare function getEmbeddedSessionRunnerIdleTtlMs(): number;
|
|
44
40
|
export interface EmbeddedSessionRunnerPoolOptions {
|
|
45
|
-
/** File-exists cache shared with prewarmSessionFile callers. */
|
|
46
|
-
sessionManagerCache?: SessionManagerCache;
|
|
47
41
|
/** Override for the env-driven enable flag (testing). */
|
|
48
42
|
isEnabled?: () => boolean;
|
|
49
43
|
/** Override for the env-driven idle TTL (testing). */
|
|
@@ -56,7 +50,6 @@ export interface EmbeddedSessionRunnerPoolOptions {
|
|
|
56
50
|
*/
|
|
57
51
|
export declare class EmbeddedSessionRunnerPool {
|
|
58
52
|
private readonly pool;
|
|
59
|
-
private readonly cache;
|
|
60
53
|
private readonly isEnabledFn;
|
|
61
54
|
private readonly getIdleTtlMsFn;
|
|
62
55
|
private stats;
|
|
@@ -77,10 +70,8 @@ export declare function resetEmbeddedSessionRunnerForTest(): void;
|
|
|
77
70
|
export declare function evictEmbeddedSessionRunner(sessionKey: string, reason?: string): void;
|
|
78
71
|
export declare function evictAllEmbeddedSessionRunners(reason?: string): void;
|
|
79
72
|
export declare function acquireEmbeddedSessionRunner(params: AcquireEmbeddedSessionRunnerParams): Promise<AcquiredEmbeddedSessionRunner>;
|
|
80
|
-
/** Resolve
|
|
73
|
+
/** Resolve session identity used by embedded runner acquire and turn execution. */
|
|
81
74
|
export declare function resolveEmbeddedTranscriptInputs(sessionStore: import('../../session/store.js').SessionStore, sessionKey: string): Promise<{
|
|
82
75
|
sessionId: string;
|
|
83
|
-
|
|
84
|
-
sessionsDir: string;
|
|
85
|
-
hadSessionFile: boolean;
|
|
76
|
+
sessionKey: string;
|
|
86
77
|
}>;
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { createLogger } from "../../utils/logger/index.js";
|
|
2
2
|
import { init_logger } from "../../utils/logger.js";
|
|
3
3
|
import { guardSessionManager } from "./session-tool-result-guard.js";
|
|
4
|
-
import {
|
|
5
|
-
import { defaultSessionManagerCache } from "./session-manager-cache.js";
|
|
4
|
+
import { openSqliteHydratingSessionManager } from "./sqlite-hydrating-session-manager.js";
|
|
6
5
|
import { applyXopcProviderApiKey, createEmbeddedAuthStorage } from "./xopc-auth-storage.js";
|
|
7
6
|
import { wrapStreamFnForXopcExtensions } from "./xopc-stream-bridge.js";
|
|
8
7
|
import { xopcToolsToDefinitions } from "./xopc-tools-bridge.js";
|
|
9
8
|
import { applySystemPromptOverrideToSession } from "./system-prompt-override.js";
|
|
10
|
-
import {
|
|
11
|
-
import { DefaultResourceLoader, SessionManager, SettingsManager, createAgentSession, getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
9
|
+
import { DefaultResourceLoader, SettingsManager, createAgentSession, getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
12
10
|
//#region src/agent/embedded/session-runner.ts
|
|
13
11
|
init_logger();
|
|
14
12
|
const log = createLogger("EmbeddedSessionRunner");
|
|
@@ -17,7 +15,7 @@ function buildEmbeddedRunnerFingerprint(input) {
|
|
|
17
15
|
const tools = [...input.toolNames].sort().join("\0");
|
|
18
16
|
const promptMarker = `${input.systemPrompt.length}:${input.systemPrompt.slice(0, 128)}`;
|
|
19
17
|
return [
|
|
20
|
-
input.
|
|
18
|
+
input.sessionId,
|
|
21
19
|
input.workspaceDir,
|
|
22
20
|
input.modelRef,
|
|
23
21
|
tools,
|
|
@@ -48,7 +46,6 @@ function createEmbeddedSettingsManager(cwd) {
|
|
|
48
46
|
*/
|
|
49
47
|
var EmbeddedSessionRunnerPool = class {
|
|
50
48
|
pool = /* @__PURE__ */ new Map();
|
|
51
|
-
cache;
|
|
52
49
|
isEnabledFn;
|
|
53
50
|
getIdleTtlMsFn;
|
|
54
51
|
stats = {
|
|
@@ -58,7 +55,6 @@ var EmbeddedSessionRunnerPool = class {
|
|
|
58
55
|
evictions: 0
|
|
59
56
|
};
|
|
60
57
|
constructor(opts = {}) {
|
|
61
|
-
this.cache = opts.sessionManagerCache ?? defaultSessionManagerCache;
|
|
62
58
|
this.isEnabledFn = opts.isEnabled ?? isEmbeddedSessionRunnerEnabled;
|
|
63
59
|
this.getIdleTtlMsFn = opts.getIdleTtlMs ?? getEmbeddedSessionRunnerIdleTtlMs;
|
|
64
60
|
}
|
|
@@ -89,7 +85,7 @@ var EmbeddedSessionRunnerPool = class {
|
|
|
89
85
|
async acquire(params) {
|
|
90
86
|
this.stats.acquires += 1;
|
|
91
87
|
const fingerprint = buildEmbeddedRunnerFingerprint({
|
|
92
|
-
|
|
88
|
+
sessionId: params.sessionId,
|
|
93
89
|
workspaceDir: params.workspaceDir,
|
|
94
90
|
modelRef: params.modelRef,
|
|
95
91
|
toolNames: params.tools.map((t) => t.name),
|
|
@@ -157,19 +153,15 @@ var EmbeddedSessionRunnerPool = class {
|
|
|
157
153
|
}, "Embedded session runner evicted");
|
|
158
154
|
}
|
|
159
155
|
async createPooledRunner(params) {
|
|
160
|
-
const { sessionKey, sessionId,
|
|
161
|
-
await this.cache.prewarm(sessionFile);
|
|
156
|
+
const { sessionKey, sessionId, workspaceDir, model, thinkingLevel, tools, systemPrompt } = params;
|
|
162
157
|
const settingsManager = createEmbeddedSettingsManager(workspaceDir);
|
|
163
|
-
const piSm = guardSessionManager(
|
|
158
|
+
const piSm = guardSessionManager(openSqliteHydratingSessionManager({
|
|
164
159
|
sessionKey,
|
|
165
|
-
contextWindowTokens: model.contextWindow ?? 128e3
|
|
166
|
-
});
|
|
167
|
-
await prepareSessionManagerForRun({
|
|
168
|
-
sessionManager: piSm,
|
|
169
|
-
sessionFile,
|
|
170
|
-
hadSessionFile,
|
|
171
160
|
sessionId,
|
|
172
161
|
cwd: workspaceDir
|
|
162
|
+
}), {
|
|
163
|
+
sessionKey,
|
|
164
|
+
contextWindowTokens: model.contextWindow ?? 128e3
|
|
173
165
|
});
|
|
174
166
|
const toolDefs = xopcToolsToDefinitions(tools);
|
|
175
167
|
const toolNames = tools.map((t) => t.name);
|
|
@@ -200,7 +192,7 @@ var EmbeddedSessionRunnerPool = class {
|
|
|
200
192
|
return {
|
|
201
193
|
sessionKey,
|
|
202
194
|
fingerprint: buildEmbeddedRunnerFingerprint({
|
|
203
|
-
|
|
195
|
+
sessionId,
|
|
204
196
|
workspaceDir,
|
|
205
197
|
modelRef: params.modelRef,
|
|
206
198
|
toolNames,
|
|
@@ -232,15 +224,9 @@ function evictAllEmbeddedSessionRunners(reason = "dispose_all") {
|
|
|
232
224
|
function acquireEmbeddedSessionRunner(params) {
|
|
233
225
|
return defaultEmbeddedSessionRunnerPool.acquire(params);
|
|
234
226
|
}
|
|
235
|
-
/** Resolve
|
|
227
|
+
/** Resolve session identity used by embedded runner acquire and turn execution. */
|
|
236
228
|
async function resolveEmbeddedTranscriptInputs(sessionStore, sessionKey) {
|
|
237
|
-
|
|
238
|
-
return {
|
|
239
|
-
sessionId,
|
|
240
|
-
sessionFile,
|
|
241
|
-
sessionsDir,
|
|
242
|
-
hadSessionFile: existsSync(sessionFile)
|
|
243
|
-
};
|
|
229
|
+
return sessionStore.resolveTranscriptPath(sessionKey);
|
|
244
230
|
}
|
|
245
231
|
//#endregion
|
|
246
232
|
export { EmbeddedSessionRunnerPool, acquireEmbeddedSessionRunner, buildEmbeddedRunnerFingerprint, defaultEmbeddedSessionRunnerPool, evictAllEmbeddedSessionRunners, evictEmbeddedSessionRunner, getEmbeddedSessionRunnerIdleTtlMs, getEmbeddedSessionRunnerStats, isEmbeddedSessionRunnerEnabled, resetEmbeddedSessionRunnerForTest, resolveEmbeddedTranscriptInputs };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-runner.js","names":[],"sources":["../../../../src/agent/embedded/session-runner.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport type { AgentTool } from '@earendil-works/pi-agent-core';\nimport type { ThinkingLevel } from '@earendil-works/pi-agent-core';\nimport {\n createAgentSession,\n DefaultResourceLoader,\n getAgentDir,\n SessionManager,\n SettingsManager,\n type AgentSession,\n} from '@earendil-works/pi-coding-agent';\nimport type { Model, Api } from '@earendil-works/pi-ai';\n\nimport { createLogger } from '../../utils/logger.js';\nimport { guardSessionManager, type GuardedPiTranscriptManager } from './session-tool-result-guard.js';\nimport { prepareSessionManagerForRun } from './session-manager-init.js';\nimport { defaultSessionManagerCache, type SessionManagerCache } from './session-manager-cache.js';\nimport { applyXopcProviderApiKey, createEmbeddedAuthStorage } from './xopc-auth-storage.js';\nimport { wrapStreamFnForXopcExtensions } from './xopc-stream-bridge.js';\nimport { xopcToolsToDefinitions } from './xopc-tools-bridge.js';\nimport { applySystemPromptOverrideToSession } from './system-prompt-override.js';\n\nconst log = createLogger('EmbeddedSessionRunner');\n\nconst DEFAULT_IDLE_TTL_MS = 5 * 60_000;\n\nexport type EmbeddedRunnerFingerprintInput = {\n sessionFile: string;\n workspaceDir: string;\n modelRef: string;\n toolNames: readonly string[];\n systemPrompt: string;\n thinkingLevel: string;\n};\n\nexport function buildEmbeddedRunnerFingerprint(input: EmbeddedRunnerFingerprintInput): string {\n const tools = [...input.toolNames].sort().join('\\0');\n const promptMarker = `${input.systemPrompt.length}:${input.systemPrompt.slice(0, 128)}`;\n return [\n input.sessionFile,\n input.workspaceDir,\n input.modelRef,\n tools,\n promptMarker,\n input.thinkingLevel,\n ].join('\u001f');\n}\n\ntype PooledRunner = {\n sessionKey: string;\n fingerprint: string;\n session: AgentSession;\n piSm: GuardedPiTranscriptManager;\n settingsManager: SettingsManager;\n baseStreamFn: AgentSession['agent']['streamFn'];\n lastUsedAt: number;\n idleTimer: ReturnType<typeof setTimeout> | null;\n};\n\nexport type AcquireEmbeddedSessionRunnerParams = {\n sessionKey: string;\n sessionId: string;\n sessionFile: string;\n sessionsDir: string;\n hadSessionFile: boolean;\n workspaceDir: string;\n model: Model<Api>;\n modelRef: string;\n tools: AgentTool[];\n systemPrompt: string;\n thinkingLevel: ThinkingLevel;\n};\n\nexport type AcquiredEmbeddedSessionRunner = {\n session: AgentSession;\n piSm: GuardedPiTranscriptManager;\n reused: boolean;\n release: () => void;\n};\n\nexport interface EmbeddedSessionRunnerPoolStats {\n acquires: number;\n reuses: number;\n creates: number;\n evictions: number;\n pooled: number;\n}\n\nexport function isEmbeddedSessionRunnerEnabled(): boolean {\n const raw = process.env.XOPC_SESSION_RUNNER?.trim().toLowerCase();\n if (raw === '0' || raw === 'false' || raw === 'off') {\n return false;\n }\n return true;\n}\n\nexport function getEmbeddedSessionRunnerIdleTtlMs(): number {\n const raw = process.env.XOPC_SESSION_RUNNER_TTL_MS?.trim();\n if (!raw) {\n return DEFAULT_IDLE_TTL_MS;\n }\n const parsed = Number.parseInt(raw, 10);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_IDLE_TTL_MS;\n}\n\nfunction createEmbeddedSettingsManager(cwd: string): SettingsManager {\n const sm = SettingsManager.inMemory({ compaction: { enabled: false } });\n sm.setCompactionEnabled(false);\n void cwd;\n return sm;\n}\n\nexport interface EmbeddedSessionRunnerPoolOptions {\n /** File-exists cache shared with prewarmSessionFile callers. */\n sessionManagerCache?: SessionManagerCache;\n /** Override for the env-driven enable flag (testing). */\n isEnabled?: () => boolean;\n /** Override for the env-driven idle TTL (testing). */\n getIdleTtlMs?: () => number;\n}\n\n/**\n * Owns the per-session pool of pi `AgentSession` runners. The class is the supported,\n * injectable owner; {@link defaultEmbeddedSessionRunnerPool} keeps the historic\n * module-level free functions working until every caller is migrated to DI.\n */\nexport class EmbeddedSessionRunnerPool {\n private readonly pool = new Map<string, PooledRunner>();\n private readonly cache: SessionManagerCache;\n private readonly isEnabledFn: () => boolean;\n private readonly getIdleTtlMsFn: () => number;\n\n private stats: Omit<EmbeddedSessionRunnerPoolStats, 'pooled'> = {\n acquires: 0,\n reuses: 0,\n creates: 0,\n evictions: 0,\n };\n\n constructor(opts: EmbeddedSessionRunnerPoolOptions = {}) {\n this.cache = opts.sessionManagerCache ?? defaultSessionManagerCache;\n this.isEnabledFn = opts.isEnabled ?? isEmbeddedSessionRunnerEnabled;\n this.getIdleTtlMsFn = opts.getIdleTtlMs ?? getEmbeddedSessionRunnerIdleTtlMs;\n }\n\n getStats(): Readonly<EmbeddedSessionRunnerPoolStats> {\n return { ...this.stats, pooled: this.pool.size };\n }\n\n resetForTest(): void {\n for (const entry of this.pool.values()) {\n this.clearIdleTimer(entry);\n }\n this.pool.clear();\n this.stats = { acquires: 0, reuses: 0, creates: 0, evictions: 0 };\n }\n\n evict(sessionKey: string, reason = 'explicit'): void {\n const entry = this.pool.get(sessionKey);\n if (!entry) {\n return;\n }\n this.disposePooledRunner(sessionKey, entry, reason);\n }\n\n evictAll(reason = 'dispose_all'): void {\n for (const sessionKey of [...this.pool.keys()]) {\n this.evict(sessionKey, reason);\n }\n }\n\n async acquire(params: AcquireEmbeddedSessionRunnerParams): Promise<AcquiredEmbeddedSessionRunner> {\n this.stats.acquires += 1;\n\n const fingerprint = buildEmbeddedRunnerFingerprint({\n sessionFile: params.sessionFile,\n workspaceDir: params.workspaceDir,\n modelRef: params.modelRef,\n toolNames: params.tools.map((t) => t.name),\n systemPrompt: params.systemPrompt,\n thinkingLevel: params.thinkingLevel ?? 'medium',\n });\n\n const reuseEnabled = this.isEnabledFn();\n const existing = this.pool.get(params.sessionKey);\n\n let entry: PooledRunner;\n let reused = false;\n\n if (reuseEnabled && existing && existing.fingerprint === fingerprint) {\n this.clearIdleTimer(existing);\n entry = existing;\n entry.lastUsedAt = Date.now();\n reused = true;\n this.stats.reuses += 1;\n applySystemPromptOverrideToSession(entry.session, params.systemPrompt);\n entry.session.agent.streamFn = entry.baseStreamFn;\n log.debug({ sessionKey: params.sessionKey }, 'Reusing pooled embedded session runner');\n } else {\n if (existing) {\n this.disposePooledRunner(params.sessionKey, existing, 'fingerprint_mismatch');\n }\n entry = await this.createPooledRunner(params);\n this.pool.set(params.sessionKey, entry);\n this.stats.creates += 1;\n log.debug({ sessionKey: params.sessionKey }, 'Created embedded session runner');\n }\n\n return {\n session: entry.session,\n piSm: entry.piSm,\n reused,\n release: () => {\n if (!this.isEnabledFn()) {\n this.disposePooledRunner(params.sessionKey, entry, 'runner_disabled');\n return;\n }\n entry.lastUsedAt = Date.now();\n this.scheduleIdleEviction(params.sessionKey, entry);\n },\n };\n }\n\n private clearIdleTimer(entry: PooledRunner): void {\n if (entry.idleTimer) {\n clearTimeout(entry.idleTimer);\n entry.idleTimer = null;\n }\n }\n\n private scheduleIdleEviction(sessionKey: string, entry: PooledRunner): void {\n this.clearIdleTimer(entry);\n const ttlMs = this.getIdleTtlMsFn();\n entry.idleTimer = setTimeout(() => {\n const current = this.pool.get(sessionKey);\n if (current === entry) {\n this.disposePooledRunner(sessionKey, entry, 'idle_ttl');\n }\n }, ttlMs);\n entry.idleTimer.unref?.();\n }\n\n private disposePooledRunner(sessionKey: string, entry: PooledRunner, reason: string): void {\n this.clearIdleTimer(entry);\n this.pool.delete(sessionKey);\n this.stats.evictions += 1;\n try {\n entry.piSm.flushPendingToolResults?.();\n } catch {\n /* ignore */\n }\n log.debug({ sessionKey, reason }, 'Embedded session runner evicted');\n }\n\n private async createPooledRunner(params: AcquireEmbeddedSessionRunnerParams): Promise<PooledRunner> {\n const {\n sessionKey,\n sessionId,\n sessionFile,\n sessionsDir,\n hadSessionFile,\n workspaceDir,\n model,\n thinkingLevel,\n tools,\n systemPrompt,\n } = params;\n\n await this.cache.prewarm(sessionFile);\n const settingsManager = createEmbeddedSettingsManager(workspaceDir);\n\n const piSm = guardSessionManager(SessionManager.open(sessionFile, sessionsDir, workspaceDir), {\n sessionKey,\n contextWindowTokens: model.contextWindow ?? 128_000,\n });\n\n await prepareSessionManagerForRun({\n sessionManager: piSm,\n sessionFile,\n hadSessionFile,\n sessionId,\n cwd: workspaceDir,\n });\n\n const toolDefs = xopcToolsToDefinitions(tools);\n const toolNames = tools.map((t) => t.name);\n\n const authStorage = createEmbeddedAuthStorage();\n applyXopcProviderApiKey(authStorage, model.provider);\n\n const resourceLoader = new DefaultResourceLoader({\n cwd: workspaceDir,\n agentDir: getAgentDir(),\n settingsManager,\n noContextFiles: true,\n });\n await resourceLoader.reload();\n\n const { session } = await createAgentSession({\n cwd: workspaceDir,\n model,\n thinkingLevel: thinkingLevel ?? 'medium',\n sessionManager: piSm,\n settingsManager,\n authStorage,\n resourceLoader,\n noTools: 'builtin',\n customTools: toolDefs,\n tools: toolNames,\n });\n\n applySystemPromptOverrideToSession(session, systemPrompt);\n const baseStreamFn = wrapStreamFnForXopcExtensions(session.agent.streamFn);\n session.agent.streamFn = baseStreamFn;\n\n const fingerprint = buildEmbeddedRunnerFingerprint({\n sessionFile,\n workspaceDir,\n modelRef: params.modelRef,\n toolNames,\n systemPrompt,\n thinkingLevel: thinkingLevel ?? 'medium',\n });\n\n return {\n sessionKey,\n fingerprint,\n session,\n piSm,\n settingsManager,\n baseStreamFn,\n lastUsedAt: Date.now(),\n idleTimer: null,\n };\n }\n}\n\nexport const defaultEmbeddedSessionRunnerPool = new EmbeddedSessionRunnerPool();\n\nexport function getEmbeddedSessionRunnerStats(): Readonly<EmbeddedSessionRunnerPoolStats> {\n return defaultEmbeddedSessionRunnerPool.getStats();\n}\n\nexport function resetEmbeddedSessionRunnerForTest(): void {\n defaultEmbeddedSessionRunnerPool.resetForTest();\n}\n\nexport function evictEmbeddedSessionRunner(sessionKey: string, reason = 'explicit'): void {\n defaultEmbeddedSessionRunnerPool.evict(sessionKey, reason);\n}\n\nexport function evictAllEmbeddedSessionRunners(reason = 'dispose_all'): void {\n defaultEmbeddedSessionRunnerPool.evictAll(reason);\n}\n\nexport function acquireEmbeddedSessionRunner(\n params: AcquireEmbeddedSessionRunnerParams,\n): Promise<AcquiredEmbeddedSessionRunner> {\n return defaultEmbeddedSessionRunnerPool.acquire(params);\n}\n\n/** Resolve transcript path inputs used by both runner acquire and turn execution. */\nexport async function resolveEmbeddedTranscriptInputs(\n sessionStore: import('../../session/store.js').SessionStore,\n sessionKey: string,\n): Promise<{\n sessionId: string;\n sessionFile: string;\n sessionsDir: string;\n hadSessionFile: boolean;\n}> {\n const { sessionId, absPath: sessionFile, sessionsDir } = await sessionStore.resolveTranscriptPath(sessionKey);\n return {\n sessionId,\n sessionFile,\n sessionsDir,\n hadSessionFile: existsSync(sessionFile),\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"session-runner.js","names":[],"sources":["../../../../src/agent/embedded/session-runner.ts"],"sourcesContent":["import type { AgentTool } from '@earendil-works/pi-agent-core';\nimport type { ThinkingLevel } from '@earendil-works/pi-agent-core';\nimport {\n createAgentSession,\n DefaultResourceLoader,\n getAgentDir,\n SettingsManager,\n type AgentSession,\n} from '@earendil-works/pi-coding-agent';\nimport type { Model, Api } from '@earendil-works/pi-ai';\n\nimport { createLogger } from '../../utils/logger.js';\nimport { guardSessionManager, type GuardedPiTranscriptManager } from './session-tool-result-guard.js';\nimport { openSqliteHydratingSessionManager } from './sqlite-hydrating-session-manager.js';\nimport { applyXopcProviderApiKey, createEmbeddedAuthStorage } from './xopc-auth-storage.js';\nimport { wrapStreamFnForXopcExtensions } from './xopc-stream-bridge.js';\nimport { xopcToolsToDefinitions } from './xopc-tools-bridge.js';\nimport { applySystemPromptOverrideToSession } from './system-prompt-override.js';\n\nconst log = createLogger('EmbeddedSessionRunner');\n\nconst DEFAULT_IDLE_TTL_MS = 5 * 60_000;\n\nexport type EmbeddedRunnerFingerprintInput = {\n sessionId: string;\n workspaceDir: string;\n modelRef: string;\n toolNames: readonly string[];\n systemPrompt: string;\n thinkingLevel: string;\n};\n\nexport function buildEmbeddedRunnerFingerprint(input: EmbeddedRunnerFingerprintInput): string {\n const tools = [...input.toolNames].sort().join('\\0');\n const promptMarker = `${input.systemPrompt.length}:${input.systemPrompt.slice(0, 128)}`;\n return [\n input.sessionId,\n input.workspaceDir,\n input.modelRef,\n tools,\n promptMarker,\n input.thinkingLevel,\n ].join('\u001f');\n}\n\ntype PooledRunner = {\n sessionKey: string;\n fingerprint: string;\n session: AgentSession;\n piSm: GuardedPiTranscriptManager;\n settingsManager: SettingsManager;\n baseStreamFn: AgentSession['agent']['streamFn'];\n lastUsedAt: number;\n idleTimer: ReturnType<typeof setTimeout> | null;\n};\n\nexport type AcquireEmbeddedSessionRunnerParams = {\n sessionKey: string;\n sessionId: string;\n workspaceDir: string;\n model: Model<Api>;\n modelRef: string;\n tools: AgentTool[];\n systemPrompt: string;\n thinkingLevel: ThinkingLevel;\n};\n\nexport type AcquiredEmbeddedSessionRunner = {\n session: AgentSession;\n piSm: GuardedPiTranscriptManager;\n reused: boolean;\n release: () => void;\n};\n\nexport interface EmbeddedSessionRunnerPoolStats {\n acquires: number;\n reuses: number;\n creates: number;\n evictions: number;\n pooled: number;\n}\n\nexport function isEmbeddedSessionRunnerEnabled(): boolean {\n const raw = process.env.XOPC_SESSION_RUNNER?.trim().toLowerCase();\n if (raw === '0' || raw === 'false' || raw === 'off') {\n return false;\n }\n return true;\n}\n\nexport function getEmbeddedSessionRunnerIdleTtlMs(): number {\n const raw = process.env.XOPC_SESSION_RUNNER_TTL_MS?.trim();\n if (!raw) {\n return DEFAULT_IDLE_TTL_MS;\n }\n const parsed = Number.parseInt(raw, 10);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_IDLE_TTL_MS;\n}\n\nfunction createEmbeddedSettingsManager(cwd: string): SettingsManager {\n const sm = SettingsManager.inMemory({ compaction: { enabled: false } });\n sm.setCompactionEnabled(false);\n void cwd;\n return sm;\n}\n\nexport interface EmbeddedSessionRunnerPoolOptions {\n /** Override for the env-driven enable flag (testing). */\n isEnabled?: () => boolean;\n /** Override for the env-driven idle TTL (testing). */\n getIdleTtlMs?: () => number;\n}\n\n/**\n * Owns the per-session pool of pi `AgentSession` runners. The class is the supported,\n * injectable owner; {@link defaultEmbeddedSessionRunnerPool} keeps the historic\n * module-level free functions working until every caller is migrated to DI.\n */\nexport class EmbeddedSessionRunnerPool {\n private readonly pool = new Map<string, PooledRunner>();\n private readonly isEnabledFn: () => boolean;\n private readonly getIdleTtlMsFn: () => number;\n\n private stats: Omit<EmbeddedSessionRunnerPoolStats, 'pooled'> = {\n acquires: 0,\n reuses: 0,\n creates: 0,\n evictions: 0,\n };\n\n constructor(opts: EmbeddedSessionRunnerPoolOptions = {}) {\n this.isEnabledFn = opts.isEnabled ?? isEmbeddedSessionRunnerEnabled;\n this.getIdleTtlMsFn = opts.getIdleTtlMs ?? getEmbeddedSessionRunnerIdleTtlMs;\n }\n\n getStats(): Readonly<EmbeddedSessionRunnerPoolStats> {\n return { ...this.stats, pooled: this.pool.size };\n }\n\n resetForTest(): void {\n for (const entry of this.pool.values()) {\n this.clearIdleTimer(entry);\n }\n this.pool.clear();\n this.stats = { acquires: 0, reuses: 0, creates: 0, evictions: 0 };\n }\n\n evict(sessionKey: string, reason = 'explicit'): void {\n const entry = this.pool.get(sessionKey);\n if (!entry) {\n return;\n }\n this.disposePooledRunner(sessionKey, entry, reason);\n }\n\n evictAll(reason = 'dispose_all'): void {\n for (const sessionKey of [...this.pool.keys()]) {\n this.evict(sessionKey, reason);\n }\n }\n\n async acquire(params: AcquireEmbeddedSessionRunnerParams): Promise<AcquiredEmbeddedSessionRunner> {\n this.stats.acquires += 1;\n\n const fingerprint = buildEmbeddedRunnerFingerprint({\n sessionId: params.sessionId,\n workspaceDir: params.workspaceDir,\n modelRef: params.modelRef,\n toolNames: params.tools.map((t) => t.name),\n systemPrompt: params.systemPrompt,\n thinkingLevel: params.thinkingLevel ?? 'medium',\n });\n\n const reuseEnabled = this.isEnabledFn();\n const existing = this.pool.get(params.sessionKey);\n\n let entry: PooledRunner;\n let reused = false;\n\n if (reuseEnabled && existing && existing.fingerprint === fingerprint) {\n this.clearIdleTimer(existing);\n entry = existing;\n entry.lastUsedAt = Date.now();\n reused = true;\n this.stats.reuses += 1;\n applySystemPromptOverrideToSession(entry.session, params.systemPrompt);\n entry.session.agent.streamFn = entry.baseStreamFn;\n log.debug({ sessionKey: params.sessionKey }, 'Reusing pooled embedded session runner');\n } else {\n if (existing) {\n this.disposePooledRunner(params.sessionKey, existing, 'fingerprint_mismatch');\n }\n entry = await this.createPooledRunner(params);\n this.pool.set(params.sessionKey, entry);\n this.stats.creates += 1;\n log.debug({ sessionKey: params.sessionKey }, 'Created embedded session runner');\n }\n\n return {\n session: entry.session,\n piSm: entry.piSm,\n reused,\n release: () => {\n if (!this.isEnabledFn()) {\n this.disposePooledRunner(params.sessionKey, entry, 'runner_disabled');\n return;\n }\n entry.lastUsedAt = Date.now();\n this.scheduleIdleEviction(params.sessionKey, entry);\n },\n };\n }\n\n private clearIdleTimer(entry: PooledRunner): void {\n if (entry.idleTimer) {\n clearTimeout(entry.idleTimer);\n entry.idleTimer = null;\n }\n }\n\n private scheduleIdleEviction(sessionKey: string, entry: PooledRunner): void {\n this.clearIdleTimer(entry);\n const ttlMs = this.getIdleTtlMsFn();\n entry.idleTimer = setTimeout(() => {\n const current = this.pool.get(sessionKey);\n if (current === entry) {\n this.disposePooledRunner(sessionKey, entry, 'idle_ttl');\n }\n }, ttlMs);\n entry.idleTimer.unref?.();\n }\n\n private disposePooledRunner(sessionKey: string, entry: PooledRunner, reason: string): void {\n this.clearIdleTimer(entry);\n this.pool.delete(sessionKey);\n this.stats.evictions += 1;\n try {\n entry.piSm.flushPendingToolResults?.();\n } catch {\n /* ignore */\n }\n log.debug({ sessionKey, reason }, 'Embedded session runner evicted');\n }\n\n private async createPooledRunner(params: AcquireEmbeddedSessionRunnerParams): Promise<PooledRunner> {\n const { sessionKey, sessionId, workspaceDir, model, thinkingLevel, tools, systemPrompt } = params;\n\n const settingsManager = createEmbeddedSettingsManager(workspaceDir);\n\n const piSm = guardSessionManager(\n openSqliteHydratingSessionManager({\n sessionKey,\n sessionId,\n cwd: workspaceDir,\n }),\n {\n sessionKey,\n contextWindowTokens: model.contextWindow ?? 128_000,\n },\n );\n\n const toolDefs = xopcToolsToDefinitions(tools);\n const toolNames = tools.map((t) => t.name);\n\n const authStorage = createEmbeddedAuthStorage();\n applyXopcProviderApiKey(authStorage, model.provider);\n\n const resourceLoader = new DefaultResourceLoader({\n cwd: workspaceDir,\n agentDir: getAgentDir(),\n settingsManager,\n noContextFiles: true,\n });\n await resourceLoader.reload();\n\n const { session } = await createAgentSession({\n cwd: workspaceDir,\n model,\n thinkingLevel: thinkingLevel ?? 'medium',\n sessionManager: piSm,\n settingsManager,\n authStorage,\n resourceLoader,\n noTools: 'builtin',\n customTools: toolDefs,\n tools: toolNames,\n });\n\n applySystemPromptOverrideToSession(session, systemPrompt);\n const baseStreamFn = wrapStreamFnForXopcExtensions(session.agent.streamFn);\n session.agent.streamFn = baseStreamFn;\n\n const fingerprint = buildEmbeddedRunnerFingerprint({\n sessionId,\n workspaceDir,\n modelRef: params.modelRef,\n toolNames,\n systemPrompt,\n thinkingLevel: thinkingLevel ?? 'medium',\n });\n\n return {\n sessionKey,\n fingerprint,\n session,\n piSm,\n settingsManager,\n baseStreamFn,\n lastUsedAt: Date.now(),\n idleTimer: null,\n };\n }\n}\n\nexport const defaultEmbeddedSessionRunnerPool = new EmbeddedSessionRunnerPool();\n\nexport function getEmbeddedSessionRunnerStats(): Readonly<EmbeddedSessionRunnerPoolStats> {\n return defaultEmbeddedSessionRunnerPool.getStats();\n}\n\nexport function resetEmbeddedSessionRunnerForTest(): void {\n defaultEmbeddedSessionRunnerPool.resetForTest();\n}\n\nexport function evictEmbeddedSessionRunner(sessionKey: string, reason = 'explicit'): void {\n defaultEmbeddedSessionRunnerPool.evict(sessionKey, reason);\n}\n\nexport function evictAllEmbeddedSessionRunners(reason = 'dispose_all'): void {\n defaultEmbeddedSessionRunnerPool.evictAll(reason);\n}\n\nexport function acquireEmbeddedSessionRunner(\n params: AcquireEmbeddedSessionRunnerParams,\n): Promise<AcquiredEmbeddedSessionRunner> {\n return defaultEmbeddedSessionRunnerPool.acquire(params);\n}\n\n/** Resolve session identity used by embedded runner acquire and turn execution. */\nexport async function resolveEmbeddedTranscriptInputs(\n sessionStore: import('../../session/store.js').SessionStore,\n sessionKey: string,\n): Promise<{\n sessionId: string;\n sessionKey: string;\n}> {\n return sessionStore.resolveTranscriptPath(sessionKey);\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { init_string_coerce, normalizeOptionalString } from "../../utils/string-coerce.js";
|
|
2
|
-
import { emitSessionTranscriptUpdate } from "../../session/transcript-events.js";
|
|
3
2
|
import { boundedJsonUtf8Bytes, firstEnumerableOwnKeys, jsonUtf8BytesOrInfinity } from "../../infra/json-utf8-bytes.js";
|
|
3
|
+
import { emitSessionTranscriptUpdate } from "../../session/transcript-events.js";
|
|
4
4
|
import { formatContextLimitTruncationNotice } from "./tool-result-context-guard.js";
|
|
5
5
|
import { resolveLiveToolResultMaxChars, truncateToolResultMessage } from "./tool-result-truncation.js";
|
|
6
6
|
import { extractToolCallsFromAssistant, extractToolResultId } from "../transcript/tool-call-id.js";
|
|
@@ -345,9 +345,7 @@ var ToolResultGuard = class {
|
|
|
345
345
|
const finalMessage = this.applyBeforeWriteHook(this.persistMessage(nextMessage));
|
|
346
346
|
if (!finalMessage) return;
|
|
347
347
|
const result = this.originalAppend(finalMessage);
|
|
348
|
-
|
|
349
|
-
if (sessionFile) emitSessionTranscriptUpdate({
|
|
350
|
-
sessionFile,
|
|
348
|
+
if (this.opts.sessionKey) emitSessionTranscriptUpdate({
|
|
351
349
|
sessionKey: this.opts.sessionKey,
|
|
352
350
|
message: finalMessage,
|
|
353
351
|
messageId: typeof result === "string" ? result : void 0
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-tool-result-guard.js","names":[],"sources":["../../../../src/agent/embedded/session-tool-result-guard.ts"],"sourcesContent":["/**\n * Session tool-result guard — wraps a pi `SessionManager.appendMessage` to:\n * 1. cap oversized tool-result text + details so they cannot blow up the next\n * LLM request (size + persistence limits).\n * 2. track pending tool-call IDs so missing tool results can be synthesised\n * (some providers refuse a turn that has an orphan tool_use block).\n * 3. drop assistant `toolCall` blocks whose tool name is not in the allowlist\n * (these would also trigger provider 400s).\n * 4. broadcast `xopc:transcript-row` updates so the gateway UI can stream them.\n *\n * Previously this module shipped with three sibling files\n * (`session-tool-result-state.ts`, `session-raw-append-message.ts`,\n * `session-tool-result-guard-wrapper.ts`). They are now consolidated here as\n * private constructs around the `ToolResultGuard` class. Pi-coding-agent owns\n * the `SessionManager` instance and calls `appendMessage` from inside the\n * runtime, so we still need to monkey-patch that method — but the patched\n * implementation is just `guard.guardedAppend.bind(guard)` and all state lives\n * on the class.\n */\n\nimport type { AgentMessage } from '@earendil-works/pi-agent-core';\nimport type { SessionManager } from '@earendil-works/pi-coding-agent';\n\nimport type { Config } from '../../config/schema.js';\nimport {\n boundedJsonUtf8Bytes,\n firstEnumerableOwnKeys,\n jsonUtf8BytesOrInfinity,\n type BoundedJsonUtf8Bytes,\n} from '../../infra/json-utf8-bytes.js';\nimport { emitSessionTranscriptUpdate } from '../../session/transcript-events.js';\nimport { normalizeOptionalString } from '../../utils/string-coerce.js';\nimport { formatContextLimitTruncationNotice } from './tool-result-context-guard.js';\nimport {\n DEFAULT_MAX_LIVE_TOOL_RESULT_CHARS,\n resolveLiveToolResultMaxChars,\n truncateToolResultMessage,\n} from './tool-result-truncation.js';\nimport {\n makeMissingToolResult,\n sanitizeToolCallInputs,\n} from '../transcript/session-transcript-repair.js';\nimport {\n extractToolCallsFromAssistant,\n extractToolResultId,\n} from '../transcript/tool-call-id.js';\n\n// ── Public surface ──────────────────────────────────────────────────────────\n\nexport type BeforeMessageWriteHookEvent = { message: AgentMessage };\nexport type BeforeMessageWriteHookResult =\n | { block?: boolean; message?: AgentMessage }\n | undefined;\n\nexport interface ToolResultGuardOptions {\n /** Optional session key for transcript update broadcasts. */\n sessionKey?: string;\n /** Optional transform applied to any message before persistence. */\n transformMessageForPersistence?: (message: AgentMessage) => AgentMessage;\n /**\n * Optional, synchronous transform applied to toolResult messages *before* they are\n * persisted to the session transcript.\n */\n transformToolResultForPersistence?: (\n message: AgentMessage,\n meta: { toolCallId?: string; toolName?: string; isSynthetic?: boolean },\n ) => AgentMessage;\n /**\n * Whether to synthesize missing tool results to satisfy strict providers.\n * Defaults to true.\n */\n allowSyntheticToolResults?: boolean;\n missingToolResultText?: string;\n /**\n * Optional set/list of tool names accepted for assistant toolCall/toolUse blocks.\n * When set, tool calls with unknown names are dropped before persistence.\n */\n allowedToolNames?: Iterable<string>;\n /**\n * Synchronous hook invoked before any message is written to the session JSONL.\n * If the hook returns { block: true }, the message is silently dropped.\n * If it returns { message }, the modified message is written instead.\n */\n beforeMessageWriteHook?: (event: BeforeMessageWriteHookEvent) => BeforeMessageWriteHookResult;\n maxToolResultChars?: number;\n}\n\nexport interface InstallSessionToolResultGuardResult {\n flushPendingToolResults: () => void;\n clearPendingToolResults: () => void;\n getPendingIds: () => string[];\n}\n\n/** Idempotent wrapper that also adds the helper methods consumers expect. */\nexport type GuardedPiTranscriptManager = SessionManager & {\n flushPendingToolResults?: () => void;\n clearPendingToolResults?: () => void;\n};\n\n/**\n * Install the guard on a SessionManager and return its control API.\n * Subsequent assistant/toolResult writes by pi-coding-agent flow through the\n * guard transparently.\n */\nexport function installSessionToolResultGuard(\n sessionManager: SessionManager,\n opts: ToolResultGuardOptions = {},\n): InstallSessionToolResultGuardResult {\n const guard = new ToolResultGuard(sessionManager, opts);\n guard.attach();\n return {\n flushPendingToolResults: () => guard.flushPending(),\n clearPendingToolResults: () => guard.clearPending(),\n getPendingIds: () => guard.getPendingIds(),\n };\n}\n\n/**\n * Convenience wrapper used by the embedded runner pool: install the guard\n * (idempotent), pin the size cap from the model context window, and expose\n * `flushPendingToolResults` / `clearPendingToolResults` directly on the\n * SessionManager instance so callers do not need to keep the install result.\n */\nexport function guardSessionManager(\n sessionManager: SessionManager,\n opts?: {\n agentId?: string;\n sessionKey?: string;\n config?: Config;\n contextWindowTokens?: number;\n allowSyntheticToolResults?: boolean;\n missingToolResultText?: string;\n allowedToolNames?: Iterable<string>;\n },\n): GuardedPiTranscriptManager {\n if (typeof (sessionManager as GuardedPiTranscriptManager).flushPendingToolResults === 'function') {\n return sessionManager as GuardedPiTranscriptManager;\n }\n\n const result = installSessionToolResultGuard(sessionManager, {\n sessionKey: opts?.sessionKey,\n allowSyntheticToolResults: opts?.allowSyntheticToolResults,\n missingToolResultText: opts?.missingToolResultText,\n allowedToolNames: opts?.allowedToolNames,\n maxToolResultChars:\n typeof opts?.contextWindowTokens === 'number'\n ? resolveLiveToolResultMaxChars({\n contextWindowTokens: opts.contextWindowTokens,\n cfg: opts?.config,\n agentId: opts?.agentId,\n })\n : undefined,\n });\n const tagged = sessionManager as GuardedPiTranscriptManager;\n tagged.flushPendingToolResults = result.flushPendingToolResults;\n tagged.clearPendingToolResults = result.clearPendingToolResults;\n return tagged;\n}\n\n/**\n * Recover the original (un-guarded) appendMessage for a session manager.\n * Useful for callers that need a low-level \"bypass the guard\" write path.\n */\nexport function getRawSessionAppendMessage(\n sessionManager: SessionManager,\n): SessionManager['appendMessage'] {\n const stored = (sessionManager as SessionManagerWithRawAppend)[RAW_APPEND_MESSAGE];\n return stored ?? sessionManager.appendMessage.bind(sessionManager);\n}\n\n// ── Internal: persistence + truncation helpers ──────────────────────────────\n\nfunction resolveMaxToolResultChars(opts: { maxToolResultChars?: number }): number {\n return Math.max(1, opts.maxToolResultChars ?? DEFAULT_MAX_LIVE_TOOL_RESULT_CHARS);\n}\n\nfunction capToolResultSize(msg: AgentMessage, maxChars: number): AgentMessage {\n if ((msg as { role?: string }).role !== 'toolResult') {\n return msg;\n }\n return truncateToolResultMessage(msg, maxChars, {\n suffix: (truncatedChars) => formatContextLimitTruncationNotice(truncatedChars),\n minKeepChars: 2_000,\n });\n}\n\n// `details` is runtime/UI metadata, not model-visible tool output. Keep the\n// session JSONL useful for debugging without letting metadata blobs dominate\n// disk, replay repair, transcript broadcasts, or future tooling that reads raw\n// sessions. Model-visible text belongs in tool result `content`.\nconst MAX_PERSISTED_TOOL_RESULT_DETAILS_BYTES = 8_192;\nconst MAX_PERSISTED_DETAIL_STRING_CHARS = 2_000;\nconst MAX_PERSISTED_DETAIL_SESSION_COUNT = 10;\nconst MAX_PERSISTED_DETAIL_FALLBACK_STRING_CHARS = 200;\n\nfunction originalDetailsSizeFields(size: BoundedJsonUtf8Bytes): Record<string, number> {\n return size.complete\n ? { originalDetailsBytes: size.bytes }\n : { originalDetailsBytesAtLeast: size.bytes };\n}\n\nfunction truncatePersistedDetailString(\n value: string,\n maxChars = MAX_PERSISTED_DETAIL_STRING_CHARS,\n): string {\n if (value.length <= maxChars) {\n return value;\n }\n return `${value.slice(0, maxChars)}\\n\\n[xopc persisted detail truncated: ${\n value.length - maxChars\n } chars omitted]`;\n}\n\nfunction sanitizePersistedSessionDetail(value: unknown): unknown {\n if (!value || typeof value !== 'object') {\n return value;\n }\n const src = value as Record<string, unknown>;\n const out: Record<string, unknown> = {};\n for (const key of [\n 'sessionId',\n 'status',\n 'pid',\n 'startedAt',\n 'endedAt',\n 'runtimeMs',\n 'cwd',\n 'name',\n 'truncated',\n 'exitCode',\n 'exitSignal',\n ]) {\n const field = src[key];\n if (field !== undefined) {\n out[key] = typeof field === 'string' ? truncatePersistedDetailString(field, 500) : field;\n }\n }\n if (typeof src.command === 'string') {\n out.command = truncatePersistedDetailString(src.command, 500);\n }\n return out;\n}\n\nfunction buildPersistedDetailsFallback(\n src: Record<string, unknown> | undefined,\n originalSize: BoundedJsonUtf8Bytes,\n sanitizedBytes?: number,\n): Record<string, unknown> {\n const fallback: Record<string, unknown> = {\n persistedDetailsTruncated: true,\n finalDetailsTruncated: true,\n ...originalDetailsSizeFields(originalSize),\n };\n if (sanitizedBytes !== undefined) {\n fallback.sanitizedDetailsBytes = sanitizedBytes;\n }\n if (src) {\n fallback.originalDetailKeys = firstEnumerableOwnKeys(src, 40);\n for (const key of ['status', 'sessionId', 'pid', 'exitCode', 'exitSignal', 'truncated']) {\n const field = src[key];\n if (field !== undefined) {\n fallback[key] =\n typeof field === 'string'\n ? truncatePersistedDetailString(field, MAX_PERSISTED_DETAIL_FALLBACK_STRING_CHARS)\n : field;\n }\n }\n }\n return fallback;\n}\n\nfunction enforcePersistedDetailsByteCap(\n value: Record<string, unknown>,\n src: Record<string, unknown> | undefined,\n originalSize: BoundedJsonUtf8Bytes,\n): Record<string, unknown> {\n const sanitizedBytes = jsonUtf8BytesOrInfinity(value);\n if (sanitizedBytes <= MAX_PERSISTED_TOOL_RESULT_DETAILS_BYTES) {\n return value;\n }\n const fallback = buildPersistedDetailsFallback(src, originalSize, sanitizedBytes);\n if (jsonUtf8BytesOrInfinity(fallback) <= MAX_PERSISTED_TOOL_RESULT_DETAILS_BYTES) {\n return fallback;\n }\n return {\n persistedDetailsTruncated: true,\n finalDetailsTruncated: true,\n ...originalDetailsSizeFields(originalSize),\n sanitizedDetailsBytes: sanitizedBytes,\n };\n}\n\nfunction sanitizeToolResultDetailsForPersistence(details: unknown): unknown {\n if (details === undefined || details === null) {\n return details;\n }\n const originalSize = boundedJsonUtf8Bytes(details, MAX_PERSISTED_TOOL_RESULT_DETAILS_BYTES);\n if (originalSize.complete && originalSize.bytes <= MAX_PERSISTED_TOOL_RESULT_DETAILS_BYTES) {\n return details;\n }\n if (typeof details !== 'object') {\n return enforcePersistedDetailsByteCap(\n {\n persistedDetailsTruncated: true,\n ...originalDetailsSizeFields(originalSize),\n valueType: typeof details,\n },\n undefined,\n originalSize,\n );\n }\n const src = details as Record<string, unknown>;\n const out: Record<string, unknown> = {\n persistedDetailsTruncated: true,\n ...originalDetailsSizeFields(originalSize),\n originalDetailKeys: firstEnumerableOwnKeys(src, 40),\n };\n for (const key of [\n 'status',\n 'sessionId',\n 'pid',\n 'startedAt',\n 'endedAt',\n 'cwd',\n 'name',\n 'exitCode',\n 'exitSignal',\n 'retryInMs',\n 'total',\n 'totalLines',\n 'totalChars',\n 'truncated',\n 'fullOutputPath',\n 'truncation',\n ]) {\n const field = src[key];\n if (field !== undefined) {\n out[key] = typeof field === 'string' ? truncatePersistedDetailString(field) : field;\n }\n }\n if (typeof src.tail === 'string') {\n out.tail = truncatePersistedDetailString(src.tail);\n }\n if (Array.isArray(src.sessions)) {\n out.sessions = src.sessions\n .slice(0, MAX_PERSISTED_DETAIL_SESSION_COUNT)\n .map(sanitizePersistedSessionDetail);\n if (src.sessions.length > MAX_PERSISTED_DETAIL_SESSION_COUNT) {\n out.sessionsTruncated = src.sessions.length - MAX_PERSISTED_DETAIL_SESSION_COUNT;\n }\n }\n return enforcePersistedDetailsByteCap(out, src, originalSize);\n}\n\nfunction capToolResultDetails(msg: AgentMessage): AgentMessage {\n if ((msg as { role?: string }).role !== 'toolResult') {\n return msg;\n }\n const details = (msg as { details?: unknown }).details;\n const sanitizedDetails = sanitizeToolResultDetailsForPersistence(details);\n if (sanitizedDetails === details) {\n return msg;\n }\n const next = { ...msg } as AgentMessage & { details?: unknown };\n next.details = sanitizedDetails;\n return next;\n}\n\nfunction capToolResultForPersistence(msg: AgentMessage, maxChars: number): AgentMessage {\n return capToolResultDetails(capToolResultSize(msg, maxChars));\n}\n\nfunction normalizePersistedToolResultName(\n message: AgentMessage,\n fallbackName?: string,\n): AgentMessage {\n if ((message as { role?: unknown }).role !== 'toolResult') {\n return message;\n }\n const toolResult = message as Extract<AgentMessage, { role: 'toolResult' }>;\n const rawToolName = (toolResult as { toolName?: unknown }).toolName;\n const normalizedToolName = normalizeOptionalString(rawToolName);\n if (normalizedToolName) {\n if (rawToolName === normalizedToolName) {\n return toolResult;\n }\n return { ...toolResult, toolName: normalizedToolName };\n }\n\n const normalizedFallback = normalizeOptionalString(fallbackName);\n if (normalizedFallback) {\n return { ...toolResult, toolName: normalizedFallback };\n }\n\n if (typeof rawToolName === 'string') {\n return { ...toolResult, toolName: 'unknown' };\n }\n return toolResult;\n}\n\n// ── Internal: raw-append symbol storage ─────────────────────────────────────\n\nconst RAW_APPEND_MESSAGE = Symbol('xopc.session.rawAppendMessage');\n\ntype SessionManagerWithRawAppend = SessionManager & {\n [RAW_APPEND_MESSAGE]?: SessionManager['appendMessage'];\n};\n\nfunction rememberOriginalAppend(\n sessionManager: SessionManager,\n): SessionManager['appendMessage'] {\n const tagged = sessionManager as SessionManagerWithRawAppend;\n const stored = tagged[RAW_APPEND_MESSAGE];\n if (stored) {\n return stored;\n }\n const original = sessionManager.appendMessage.bind(sessionManager);\n tagged[RAW_APPEND_MESSAGE] = original;\n return original;\n}\n\n// ── Internal: pending tool-call state ──────────────────────────────────────\n\ntype PendingToolCall = { id: string; name?: string };\n\nclass PendingToolCallTracker {\n private readonly pending = new Map<string, string | undefined>();\n\n size(): number {\n return this.pending.size;\n }\n\n getToolName(id: string): string | undefined {\n return this.pending.get(id);\n }\n\n delete(id: string): void {\n this.pending.delete(id);\n }\n\n clear(): void {\n this.pending.clear();\n }\n\n trackToolCalls(calls: readonly PendingToolCall[]): void {\n for (const call of calls) {\n this.pending.set(call.id, call.name);\n }\n }\n\n entries(): IterableIterator<[string, string | undefined]> {\n return this.pending.entries();\n }\n\n getPendingIds(): string[] {\n return Array.from(this.pending.keys());\n }\n\n shouldFlushForSanitizedDrop(): boolean {\n return this.pending.size > 0;\n }\n\n shouldFlushBeforeNonToolResult(nextRole: unknown, toolCallCount: number): boolean {\n return this.pending.size > 0 && (toolCallCount === 0 || nextRole !== 'assistant');\n }\n\n shouldFlushBeforeNewToolCalls(toolCallCount: number): boolean {\n return this.pending.size > 0 && toolCallCount > 0;\n }\n}\n\n// ── ToolResultGuard class ──────────────────────────────────────────────────\n\nclass ToolResultGuard {\n private readonly sessionManager: SessionManager;\n private readonly opts: ToolResultGuardOptions;\n private readonly pending = new PendingToolCallTracker();\n private readonly originalAppend: SessionManager['appendMessage'];\n private readonly allowSyntheticToolResults: boolean;\n private readonly maxToolResultChars: number;\n\n constructor(sessionManager: SessionManager, opts: ToolResultGuardOptions) {\n this.sessionManager = sessionManager;\n this.opts = opts;\n this.originalAppend = rememberOriginalAppend(sessionManager);\n this.allowSyntheticToolResults = opts.allowSyntheticToolResults ?? true;\n this.maxToolResultChars = resolveMaxToolResultChars(opts);\n }\n\n /** Monkey-patch the session manager so pi-coding-agent's internal appendMessage flows through us. */\n attach(): void {\n const bound = this.guardedAppend.bind(this);\n this.sessionManager.appendMessage = bound as SessionManager['appendMessage'];\n }\n\n flushPending(): void {\n if (this.pending.size() === 0) {\n return;\n }\n if (this.allowSyntheticToolResults) {\n for (const [id, name] of this.pending.entries()) {\n const synthetic = makeMissingToolResult({\n toolCallId: id,\n toolName: name,\n text: this.opts.missingToolResultText,\n });\n const flushed = this.applyBeforeWriteHook(\n this.persistToolResult(this.persistMessage(synthetic), {\n toolCallId: id,\n toolName: name,\n isSynthetic: true,\n }),\n );\n if (flushed) {\n this.originalAppend(capToolResultForPersistence(flushed, this.maxToolResultChars) as never);\n }\n }\n }\n this.pending.clear();\n }\n\n clearPending(): void {\n this.pending.clear();\n }\n\n getPendingIds(): string[] {\n return this.pending.getPendingIds();\n }\n\n private persistMessage(message: AgentMessage): AgentMessage {\n const transformer = this.opts.transformMessageForPersistence;\n return transformer ? transformer(message) : message;\n }\n\n private persistToolResult(\n message: AgentMessage,\n meta: { toolCallId?: string; toolName?: string; isSynthetic?: boolean },\n ): AgentMessage {\n const transformer = this.opts.transformToolResultForPersistence;\n return transformer ? transformer(message, meta) : message;\n }\n\n /**\n * Run the before_message_write hook. Returns the (possibly modified) message,\n * or null if the message should be blocked.\n */\n private applyBeforeWriteHook(msg: AgentMessage): AgentMessage | null {\n const beforeWrite = this.opts.beforeMessageWriteHook;\n if (!beforeWrite) {\n return msg;\n }\n const result = beforeWrite({ message: msg });\n if (result?.block) {\n return null;\n }\n if (result?.message) {\n return result.message;\n }\n return msg;\n }\n\n private guardedAppend(message: AgentMessage): unknown {\n let nextMessage = message;\n const role = (message as { role?: unknown }).role;\n if (role === 'assistant') {\n const sanitized = sanitizeToolCallInputs([message], {\n allowedToolNames: this.opts.allowedToolNames,\n });\n if (sanitized.length === 0) {\n if (this.pending.shouldFlushForSanitizedDrop()) {\n this.flushPending();\n }\n return undefined;\n }\n nextMessage = sanitized[0];\n }\n const nextRole = (nextMessage as { role?: unknown }).role;\n\n if (nextRole === 'toolResult') {\n const id = extractToolResultId(nextMessage as Extract<AgentMessage, { role: 'toolResult' }>);\n const toolName = id ? this.pending.getToolName(id) : undefined;\n if (id) {\n this.pending.delete(id);\n }\n const normalizedToolResult = normalizePersistedToolResultName(nextMessage, toolName);\n // Apply hard size cap before persistence to prevent oversized tool results\n // from consuming the entire context window on subsequent LLM calls.\n const capped = capToolResultForPersistence(\n this.persistMessage(normalizedToolResult),\n this.maxToolResultChars,\n );\n const persisted = this.applyBeforeWriteHook(\n this.persistToolResult(capped, {\n toolCallId: id ?? undefined,\n toolName,\n isSynthetic: false,\n }),\n );\n if (!persisted) {\n return undefined;\n }\n return this.originalAppend(capToolResultForPersistence(persisted, this.maxToolResultChars) as never);\n }\n\n // Skip tool call extraction for aborted/errored assistant messages.\n // When stopReason is \"error\" or \"aborted\", the tool_use blocks may be incomplete\n // and should not have synthetic tool_results created. Creating synthetic results\n // for incomplete tool calls causes API 400 errors:\n // \"unexpected tool_use_id found in tool_result blocks\"\n // This matches the behavior in repairToolUseResultPairing (session-transcript-repair.ts)\n const stopReason = (nextMessage as { stopReason?: string }).stopReason;\n const toolCalls =\n nextRole === 'assistant' && stopReason !== 'aborted' && stopReason !== 'error'\n ? extractToolCallsFromAssistant(nextMessage as Extract<AgentMessage, { role: 'assistant' }>)\n : [];\n\n // Always clear pending tool call state before appending non-tool-result messages.\n // flushPendingToolResults() only inserts synthetic results when allowSyntheticToolResults\n // is true; it always clears the pending map. Without this, providers that disable\n // synthetic results (e.g. OpenAI) accumulate stale pending state when a user message\n // interrupts in-flight tool calls, leaving orphaned tool_use blocks in the transcript\n // that cause API 400 errors on subsequent requests.\n if (this.pending.shouldFlushBeforeNonToolResult(nextRole, toolCalls.length)) {\n this.flushPending();\n }\n // If new tool calls arrive while older ones are pending, flush the old ones first.\n if (this.pending.shouldFlushBeforeNewToolCalls(toolCalls.length)) {\n this.flushPending();\n }\n\n const finalMessage = this.applyBeforeWriteHook(this.persistMessage(nextMessage));\n if (!finalMessage) {\n return undefined;\n }\n const result = this.originalAppend(finalMessage as never);\n\n const sessionFile = (\n this.sessionManager as { getSessionFile?: () => string | null }\n ).getSessionFile?.();\n if (sessionFile) {\n emitSessionTranscriptUpdate({\n sessionFile,\n sessionKey: this.opts.sessionKey,\n message: finalMessage,\n messageId: typeof result === 'string' ? result : undefined,\n });\n }\n\n if (toolCalls.length > 0) {\n this.pending.trackToolCalls(toolCalls);\n }\n\n return result;\n }\n}\n"],"mappings":";;;;;;;;oBA+BuE;;;;;;AAyEvE,SAAgB,8BACd,gBACA,OAA+B,EAAE,EACI;CACrC,MAAM,QAAQ,IAAI,gBAAgB,gBAAgB,KAAK;AACvD,OAAM,QAAQ;AACd,QAAO;EACL,+BAA+B,MAAM,cAAc;EACnD,+BAA+B,MAAM,cAAc;EACnD,qBAAqB,MAAM,eAAe;EAC3C;;;;;;;;AASH,SAAgB,oBACd,gBACA,MAS4B;AAC5B,KAAI,OAAQ,eAA8C,4BAA4B,WACpF,QAAO;CAGT,MAAM,SAAS,8BAA8B,gBAAgB;EAC3D,YAAY,MAAM;EAClB,2BAA2B,MAAM;EACjC,uBAAuB,MAAM;EAC7B,kBAAkB,MAAM;EACxB,oBACE,OAAO,MAAM,wBAAwB,WACjC,8BAA8B;GAC5B,qBAAqB,KAAK;GAC1B,KAAK,MAAM;GACX,SAAS,MAAM;GAChB,CAAC,GACF,KAAA;EACP,CAAC;CACF,MAAM,SAAS;AACf,QAAO,0BAA0B,OAAO;AACxC,QAAO,0BAA0B,OAAO;AACxC,QAAO;;;;;;AAOT,SAAgB,2BACd,gBACiC;AAEjC,QADgB,eAA+C,uBAC9C,eAAe,cAAc,KAAK,eAAe;;AAKpE,SAAS,0BAA0B,MAA+C;AAChF,QAAO,KAAK,IAAI,GAAG,KAAK,sBAAA,KAAyD;;AAGnF,SAAS,kBAAkB,KAAmB,UAAgC;AAC5E,KAAK,IAA0B,SAAS,aACtC,QAAO;AAET,QAAO,0BAA0B,KAAK,UAAU;EAC9C,SAAS,mBAAmB,mCAAmC,eAAe;EAC9E,cAAc;EACf,CAAC;;AAOJ,MAAM,0CAA0C;AAChD,MAAM,oCAAoC;AAC1C,MAAM,qCAAqC;AAC3C,MAAM,6CAA6C;AAEnD,SAAS,0BAA0B,MAAoD;AACrF,QAAO,KAAK,WACR,EAAE,sBAAsB,KAAK,OAAO,GACpC,EAAE,6BAA6B,KAAK,OAAO;;AAGjD,SAAS,8BACP,OACA,WAAW,mCACH;AACR,KAAI,MAAM,UAAU,SAClB,QAAO;AAET,QAAO,GAAG,MAAM,MAAM,GAAG,SAAS,CAAC,wCACjC,MAAM,SAAS,SAChB;;AAGH,SAAS,+BAA+B,OAAyB;AAC/D,KAAI,CAAC,SAAS,OAAO,UAAU,SAC7B,QAAO;CAET,MAAM,MAAM;CACZ,MAAM,MAA+B,EAAE;AACvC,MAAK,MAAM,OAAO;EAChB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,EAAE;EACD,MAAM,QAAQ,IAAI;AAClB,MAAI,UAAU,KAAA,EACZ,KAAI,OAAO,OAAO,UAAU,WAAW,8BAA8B,OAAO,IAAI,GAAG;;AAGvF,KAAI,OAAO,IAAI,YAAY,SACzB,KAAI,UAAU,8BAA8B,IAAI,SAAS,IAAI;AAE/D,QAAO;;AAGT,SAAS,8BACP,KACA,cACA,gBACyB;CACzB,MAAM,WAAoC;EACxC,2BAA2B;EAC3B,uBAAuB;EACvB,GAAG,0BAA0B,aAAa;EAC3C;AACD,KAAI,mBAAmB,KAAA,EACrB,UAAS,wBAAwB;AAEnC,KAAI,KAAK;AACP,WAAS,qBAAqB,uBAAuB,KAAK,GAAG;AAC7D,OAAK,MAAM,OAAO;GAAC;GAAU;GAAa;GAAO;GAAY;GAAc;GAAY,EAAE;GACvF,MAAM,QAAQ,IAAI;AAClB,OAAI,UAAU,KAAA,EACZ,UAAS,OACP,OAAO,UAAU,WACb,8BAA8B,OAAO,2CAA2C,GAChF;;;AAIZ,QAAO;;AAGT,SAAS,+BACP,OACA,KACA,cACyB;CACzB,MAAM,iBAAiB,wBAAwB,MAAM;AACrD,KAAI,kBAAkB,wCACpB,QAAO;CAET,MAAM,WAAW,8BAA8B,KAAK,cAAc,eAAe;AACjF,KAAI,wBAAwB,SAAS,IAAI,wCACvC,QAAO;AAET,QAAO;EACL,2BAA2B;EAC3B,uBAAuB;EACvB,GAAG,0BAA0B,aAAa;EAC1C,uBAAuB;EACxB;;AAGH,SAAS,wCAAwC,SAA2B;AAC1E,KAAI,YAAY,KAAA,KAAa,YAAY,KACvC,QAAO;CAET,MAAM,eAAe,qBAAqB,SAAS,wCAAwC;AAC3F,KAAI,aAAa,YAAY,aAAa,SAAS,wCACjD,QAAO;AAET,KAAI,OAAO,YAAY,SACrB,QAAO,+BACL;EACE,2BAA2B;EAC3B,GAAG,0BAA0B,aAAa;EAC1C,WAAW,OAAO;EACnB,EACD,KAAA,GACA,aACD;CAEH,MAAM,MAAM;CACZ,MAAM,MAA+B;EACnC,2BAA2B;EAC3B,GAAG,0BAA0B,aAAa;EAC1C,oBAAoB,uBAAuB,KAAK,GAAG;EACpD;AACD,MAAK,MAAM,OAAO;EAChB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,EAAE;EACD,MAAM,QAAQ,IAAI;AAClB,MAAI,UAAU,KAAA,EACZ,KAAI,OAAO,OAAO,UAAU,WAAW,8BAA8B,MAAM,GAAG;;AAGlF,KAAI,OAAO,IAAI,SAAS,SACtB,KAAI,OAAO,8BAA8B,IAAI,KAAK;AAEpD,KAAI,MAAM,QAAQ,IAAI,SAAS,EAAE;AAC/B,MAAI,WAAW,IAAI,SAChB,MAAM,GAAG,mCAAmC,CAC5C,IAAI,+BAA+B;AACtC,MAAI,IAAI,SAAS,SAAS,mCACxB,KAAI,oBAAoB,IAAI,SAAS,SAAS;;AAGlD,QAAO,+BAA+B,KAAK,KAAK,aAAa;;AAG/D,SAAS,qBAAqB,KAAiC;AAC7D,KAAK,IAA0B,SAAS,aACtC,QAAO;CAET,MAAM,UAAW,IAA8B;CAC/C,MAAM,mBAAmB,wCAAwC,QAAQ;AACzE,KAAI,qBAAqB,QACvB,QAAO;CAET,MAAM,OAAO,EAAE,GAAG,KAAK;AACvB,MAAK,UAAU;AACf,QAAO;;AAGT,SAAS,4BAA4B,KAAmB,UAAgC;AACtF,QAAO,qBAAqB,kBAAkB,KAAK,SAAS,CAAC;;AAG/D,SAAS,iCACP,SACA,cACc;AACd,KAAK,QAA+B,SAAS,aAC3C,QAAO;CAET,MAAM,aAAa;CACnB,MAAM,cAAe,WAAsC;CAC3D,MAAM,qBAAqB,wBAAwB,YAAY;AAC/D,KAAI,oBAAoB;AACtB,MAAI,gBAAgB,mBAClB,QAAO;AAET,SAAO;GAAE,GAAG;GAAY,UAAU;GAAoB;;CAGxD,MAAM,qBAAqB,wBAAwB,aAAa;AAChE,KAAI,mBACF,QAAO;EAAE,GAAG;EAAY,UAAU;EAAoB;AAGxD,KAAI,OAAO,gBAAgB,SACzB,QAAO;EAAE,GAAG;EAAY,UAAU;EAAW;AAE/C,QAAO;;AAKT,MAAM,qBAAqB,OAAO,gCAAgC;AAMlE,SAAS,uBACP,gBACiC;CACjC,MAAM,SAAS;CACf,MAAM,SAAS,OAAO;AACtB,KAAI,OACF,QAAO;CAET,MAAM,WAAW,eAAe,cAAc,KAAK,eAAe;AAClE,QAAO,sBAAsB;AAC7B,QAAO;;AAOT,IAAM,yBAAN,MAA6B;CAC3B,0BAA2B,IAAI,KAAiC;CAEhE,OAAe;AACb,SAAO,KAAK,QAAQ;;CAGtB,YAAY,IAAgC;AAC1C,SAAO,KAAK,QAAQ,IAAI,GAAG;;CAG7B,OAAO,IAAkB;AACvB,OAAK,QAAQ,OAAO,GAAG;;CAGzB,QAAc;AACZ,OAAK,QAAQ,OAAO;;CAGtB,eAAe,OAAyC;AACtD,OAAK,MAAM,QAAQ,MACjB,MAAK,QAAQ,IAAI,KAAK,IAAI,KAAK,KAAK;;CAIxC,UAA0D;AACxD,SAAO,KAAK,QAAQ,SAAS;;CAG/B,gBAA0B;AACxB,SAAO,MAAM,KAAK,KAAK,QAAQ,MAAM,CAAC;;CAGxC,8BAAuC;AACrC,SAAO,KAAK,QAAQ,OAAO;;CAG7B,+BAA+B,UAAmB,eAAgC;AAChF,SAAO,KAAK,QAAQ,OAAO,MAAM,kBAAkB,KAAK,aAAa;;CAGvE,8BAA8B,eAAgC;AAC5D,SAAO,KAAK,QAAQ,OAAO,KAAK,gBAAgB;;;AAMpD,IAAM,kBAAN,MAAsB;CACpB;CACA;CACA,UAA2B,IAAI,wBAAwB;CACvD;CACA;CACA;CAEA,YAAY,gBAAgC,MAA8B;AACxE,OAAK,iBAAiB;AACtB,OAAK,OAAO;AACZ,OAAK,iBAAiB,uBAAuB,eAAe;AAC5D,OAAK,4BAA4B,KAAK,6BAA6B;AACnE,OAAK,qBAAqB,0BAA0B,KAAK;;;CAI3D,SAAe;EACb,MAAM,QAAQ,KAAK,cAAc,KAAK,KAAK;AAC3C,OAAK,eAAe,gBAAgB;;CAGtC,eAAqB;AACnB,MAAI,KAAK,QAAQ,MAAM,KAAK,EAC1B;AAEF,MAAI,KAAK,0BACP,MAAK,MAAM,CAAC,IAAI,SAAS,KAAK,QAAQ,SAAS,EAAE;GAC/C,MAAM,YAAY,sBAAsB;IACtC,YAAY;IACZ,UAAU;IACV,MAAM,KAAK,KAAK;IACjB,CAAC;GACF,MAAM,UAAU,KAAK,qBACnB,KAAK,kBAAkB,KAAK,eAAe,UAAU,EAAE;IACrD,YAAY;IACZ,UAAU;IACV,aAAa;IACd,CAAC,CACH;AACD,OAAI,QACF,MAAK,eAAe,4BAA4B,SAAS,KAAK,mBAAmB,CAAU;;AAIjG,OAAK,QAAQ,OAAO;;CAGtB,eAAqB;AACnB,OAAK,QAAQ,OAAO;;CAGtB,gBAA0B;AACxB,SAAO,KAAK,QAAQ,eAAe;;CAGrC,eAAuB,SAAqC;EAC1D,MAAM,cAAc,KAAK,KAAK;AAC9B,SAAO,cAAc,YAAY,QAAQ,GAAG;;CAG9C,kBACE,SACA,MACc;EACd,MAAM,cAAc,KAAK,KAAK;AAC9B,SAAO,cAAc,YAAY,SAAS,KAAK,GAAG;;;;;;CAOpD,qBAA6B,KAAwC;EACnE,MAAM,cAAc,KAAK,KAAK;AAC9B,MAAI,CAAC,YACH,QAAO;EAET,MAAM,SAAS,YAAY,EAAE,SAAS,KAAK,CAAC;AAC5C,MAAI,QAAQ,MACV,QAAO;AAET,MAAI,QAAQ,QACV,QAAO,OAAO;AAEhB,SAAO;;CAGT,cAAsB,SAAgC;EACpD,IAAI,cAAc;AAElB,MADc,QAA+B,SAChC,aAAa;GACxB,MAAM,YAAY,uBAAuB,CAAC,QAAQ,EAAE,EAClD,kBAAkB,KAAK,KAAK,kBAC7B,CAAC;AACF,OAAI,UAAU,WAAW,GAAG;AAC1B,QAAI,KAAK,QAAQ,6BAA6B,CAC5C,MAAK,cAAc;AAErB;;AAEF,iBAAc,UAAU;;EAE1B,MAAM,WAAY,YAAmC;AAErD,MAAI,aAAa,cAAc;GAC7B,MAAM,KAAK,oBAAoB,YAA6D;GAC5F,MAAM,WAAW,KAAK,KAAK,QAAQ,YAAY,GAAG,GAAG,KAAA;AACrD,OAAI,GACF,MAAK,QAAQ,OAAO,GAAG;GAEzB,MAAM,uBAAuB,iCAAiC,aAAa,SAAS;GAGpF,MAAM,SAAS,4BACb,KAAK,eAAe,qBAAqB,EACzC,KAAK,mBACN;GACD,MAAM,YAAY,KAAK,qBACrB,KAAK,kBAAkB,QAAQ;IAC7B,YAAY,MAAM,KAAA;IAClB;IACA,aAAa;IACd,CAAC,CACH;AACD,OAAI,CAAC,UACH;AAEF,UAAO,KAAK,eAAe,4BAA4B,WAAW,KAAK,mBAAmB,CAAU;;EAStG,MAAM,aAAc,YAAwC;EAC5D,MAAM,YACJ,aAAa,eAAe,eAAe,aAAa,eAAe,UACnE,8BAA8B,YAA4D,GAC1F,EAAE;AAQR,MAAI,KAAK,QAAQ,+BAA+B,UAAU,UAAU,OAAO,CACzE,MAAK,cAAc;AAGrB,MAAI,KAAK,QAAQ,8BAA8B,UAAU,OAAO,CAC9D,MAAK,cAAc;EAGrB,MAAM,eAAe,KAAK,qBAAqB,KAAK,eAAe,YAAY,CAAC;AAChF,MAAI,CAAC,aACH;EAEF,MAAM,SAAS,KAAK,eAAe,aAAsB;EAEzD,MAAM,cACJ,KAAK,eACL,kBAAkB;AACpB,MAAI,YACF,6BAA4B;GAC1B;GACA,YAAY,KAAK,KAAK;GACtB,SAAS;GACT,WAAW,OAAO,WAAW,WAAW,SAAS,KAAA;GAClD,CAAC;AAGJ,MAAI,UAAU,SAAS,EACrB,MAAK,QAAQ,eAAe,UAAU;AAGxC,SAAO"}
|
|
1
|
+
{"version":3,"file":"session-tool-result-guard.js","names":[],"sources":["../../../../src/agent/embedded/session-tool-result-guard.ts"],"sourcesContent":["/**\n * Session tool-result guard — wraps a pi `SessionManager.appendMessage` to:\n * 1. cap oversized tool-result text + details so they cannot blow up the next\n * LLM request (size + persistence limits).\n * 2. track pending tool-call IDs so missing tool results can be synthesised\n * (some providers refuse a turn that has an orphan tool_use block).\n * 3. drop assistant `toolCall` blocks whose tool name is not in the allowlist\n * (these would also trigger provider 400s).\n * 4. broadcast `xopc:transcript-row` updates so the gateway UI can stream them.\n *\n * Previously this module shipped with three sibling files\n * (`session-tool-result-state.ts`, `session-raw-append-message.ts`,\n * `session-tool-result-guard-wrapper.ts`). They are now consolidated here as\n * private constructs around the `ToolResultGuard` class. Pi-coding-agent owns\n * the `SessionManager` instance and calls `appendMessage` from inside the\n * runtime, so we still need to monkey-patch that method — but the patched\n * implementation is just `guard.guardedAppend.bind(guard)` and all state lives\n * on the class.\n */\n\nimport type { AgentMessage } from '@earendil-works/pi-agent-core';\nimport type { SessionManager } from '@earendil-works/pi-coding-agent';\n\nimport type { Config } from '../../config/schema.js';\nimport {\n boundedJsonUtf8Bytes,\n firstEnumerableOwnKeys,\n jsonUtf8BytesOrInfinity,\n type BoundedJsonUtf8Bytes,\n} from '../../infra/json-utf8-bytes.js';\nimport { emitSessionTranscriptUpdate } from '../../session/transcript-events.js';\nimport { normalizeOptionalString } from '../../utils/string-coerce.js';\nimport { formatContextLimitTruncationNotice } from './tool-result-context-guard.js';\nimport {\n DEFAULT_MAX_LIVE_TOOL_RESULT_CHARS,\n resolveLiveToolResultMaxChars,\n truncateToolResultMessage,\n} from './tool-result-truncation.js';\nimport {\n makeMissingToolResult,\n sanitizeToolCallInputs,\n} from '../transcript/session-transcript-repair.js';\nimport {\n extractToolCallsFromAssistant,\n extractToolResultId,\n} from '../transcript/tool-call-id.js';\n\n// ── Public surface ──────────────────────────────────────────────────────────\n\nexport type BeforeMessageWriteHookEvent = { message: AgentMessage };\nexport type BeforeMessageWriteHookResult =\n | { block?: boolean; message?: AgentMessage }\n | undefined;\n\nexport interface ToolResultGuardOptions {\n /** Optional session key for transcript update broadcasts. */\n sessionKey?: string;\n /** Optional transform applied to any message before persistence. */\n transformMessageForPersistence?: (message: AgentMessage) => AgentMessage;\n /**\n * Optional, synchronous transform applied to toolResult messages *before* they are\n * persisted to the session transcript.\n */\n transformToolResultForPersistence?: (\n message: AgentMessage,\n meta: { toolCallId?: string; toolName?: string; isSynthetic?: boolean },\n ) => AgentMessage;\n /**\n * Whether to synthesize missing tool results to satisfy strict providers.\n * Defaults to true.\n */\n allowSyntheticToolResults?: boolean;\n missingToolResultText?: string;\n /**\n * Optional set/list of tool names accepted for assistant toolCall/toolUse blocks.\n * When set, tool calls with unknown names are dropped before persistence.\n */\n allowedToolNames?: Iterable<string>;\n /**\n * Synchronous hook invoked before any message is written to the session JSONL.\n * If the hook returns { block: true }, the message is silently dropped.\n * If it returns { message }, the modified message is written instead.\n */\n beforeMessageWriteHook?: (event: BeforeMessageWriteHookEvent) => BeforeMessageWriteHookResult;\n maxToolResultChars?: number;\n}\n\nexport interface InstallSessionToolResultGuardResult {\n flushPendingToolResults: () => void;\n clearPendingToolResults: () => void;\n getPendingIds: () => string[];\n}\n\n/** Idempotent wrapper that also adds the helper methods consumers expect. */\nexport type GuardedPiTranscriptManager = SessionManager & {\n flushPendingToolResults?: () => void;\n clearPendingToolResults?: () => void;\n};\n\n/**\n * Install the guard on a SessionManager and return its control API.\n * Subsequent assistant/toolResult writes by pi-coding-agent flow through the\n * guard transparently.\n */\nexport function installSessionToolResultGuard(\n sessionManager: SessionManager,\n opts: ToolResultGuardOptions = {},\n): InstallSessionToolResultGuardResult {\n const guard = new ToolResultGuard(sessionManager, opts);\n guard.attach();\n return {\n flushPendingToolResults: () => guard.flushPending(),\n clearPendingToolResults: () => guard.clearPending(),\n getPendingIds: () => guard.getPendingIds(),\n };\n}\n\n/**\n * Convenience wrapper used by the embedded runner pool: install the guard\n * (idempotent), pin the size cap from the model context window, and expose\n * `flushPendingToolResults` / `clearPendingToolResults` directly on the\n * SessionManager instance so callers do not need to keep the install result.\n */\nexport function guardSessionManager(\n sessionManager: SessionManager,\n opts?: {\n agentId?: string;\n sessionKey?: string;\n config?: Config;\n contextWindowTokens?: number;\n allowSyntheticToolResults?: boolean;\n missingToolResultText?: string;\n allowedToolNames?: Iterable<string>;\n },\n): GuardedPiTranscriptManager {\n if (typeof (sessionManager as GuardedPiTranscriptManager).flushPendingToolResults === 'function') {\n return sessionManager as GuardedPiTranscriptManager;\n }\n\n const result = installSessionToolResultGuard(sessionManager, {\n sessionKey: opts?.sessionKey,\n allowSyntheticToolResults: opts?.allowSyntheticToolResults,\n missingToolResultText: opts?.missingToolResultText,\n allowedToolNames: opts?.allowedToolNames,\n maxToolResultChars:\n typeof opts?.contextWindowTokens === 'number'\n ? resolveLiveToolResultMaxChars({\n contextWindowTokens: opts.contextWindowTokens,\n cfg: opts?.config,\n agentId: opts?.agentId,\n })\n : undefined,\n });\n const tagged = sessionManager as GuardedPiTranscriptManager;\n tagged.flushPendingToolResults = result.flushPendingToolResults;\n tagged.clearPendingToolResults = result.clearPendingToolResults;\n return tagged;\n}\n\n/**\n * Recover the original (un-guarded) appendMessage for a session manager.\n * Useful for callers that need a low-level \"bypass the guard\" write path.\n */\nexport function getRawSessionAppendMessage(\n sessionManager: SessionManager,\n): SessionManager['appendMessage'] {\n const stored = (sessionManager as SessionManagerWithRawAppend)[RAW_APPEND_MESSAGE];\n return stored ?? sessionManager.appendMessage.bind(sessionManager);\n}\n\n// ── Internal: persistence + truncation helpers ──────────────────────────────\n\nfunction resolveMaxToolResultChars(opts: { maxToolResultChars?: number }): number {\n return Math.max(1, opts.maxToolResultChars ?? DEFAULT_MAX_LIVE_TOOL_RESULT_CHARS);\n}\n\nfunction capToolResultSize(msg: AgentMessage, maxChars: number): AgentMessage {\n if ((msg as { role?: string }).role !== 'toolResult') {\n return msg;\n }\n return truncateToolResultMessage(msg, maxChars, {\n suffix: (truncatedChars) => formatContextLimitTruncationNotice(truncatedChars),\n minKeepChars: 2_000,\n });\n}\n\n// `details` is runtime/UI metadata, not model-visible tool output. Keep the\n// session JSONL useful for debugging without letting metadata blobs dominate\n// disk, replay repair, transcript broadcasts, or future tooling that reads raw\n// sessions. Model-visible text belongs in tool result `content`.\nconst MAX_PERSISTED_TOOL_RESULT_DETAILS_BYTES = 8_192;\nconst MAX_PERSISTED_DETAIL_STRING_CHARS = 2_000;\nconst MAX_PERSISTED_DETAIL_SESSION_COUNT = 10;\nconst MAX_PERSISTED_DETAIL_FALLBACK_STRING_CHARS = 200;\n\nfunction originalDetailsSizeFields(size: BoundedJsonUtf8Bytes): Record<string, number> {\n return size.complete\n ? { originalDetailsBytes: size.bytes }\n : { originalDetailsBytesAtLeast: size.bytes };\n}\n\nfunction truncatePersistedDetailString(\n value: string,\n maxChars = MAX_PERSISTED_DETAIL_STRING_CHARS,\n): string {\n if (value.length <= maxChars) {\n return value;\n }\n return `${value.slice(0, maxChars)}\\n\\n[xopc persisted detail truncated: ${\n value.length - maxChars\n } chars omitted]`;\n}\n\nfunction sanitizePersistedSessionDetail(value: unknown): unknown {\n if (!value || typeof value !== 'object') {\n return value;\n }\n const src = value as Record<string, unknown>;\n const out: Record<string, unknown> = {};\n for (const key of [\n 'sessionId',\n 'status',\n 'pid',\n 'startedAt',\n 'endedAt',\n 'runtimeMs',\n 'cwd',\n 'name',\n 'truncated',\n 'exitCode',\n 'exitSignal',\n ]) {\n const field = src[key];\n if (field !== undefined) {\n out[key] = typeof field === 'string' ? truncatePersistedDetailString(field, 500) : field;\n }\n }\n if (typeof src.command === 'string') {\n out.command = truncatePersistedDetailString(src.command, 500);\n }\n return out;\n}\n\nfunction buildPersistedDetailsFallback(\n src: Record<string, unknown> | undefined,\n originalSize: BoundedJsonUtf8Bytes,\n sanitizedBytes?: number,\n): Record<string, unknown> {\n const fallback: Record<string, unknown> = {\n persistedDetailsTruncated: true,\n finalDetailsTruncated: true,\n ...originalDetailsSizeFields(originalSize),\n };\n if (sanitizedBytes !== undefined) {\n fallback.sanitizedDetailsBytes = sanitizedBytes;\n }\n if (src) {\n fallback.originalDetailKeys = firstEnumerableOwnKeys(src, 40);\n for (const key of ['status', 'sessionId', 'pid', 'exitCode', 'exitSignal', 'truncated']) {\n const field = src[key];\n if (field !== undefined) {\n fallback[key] =\n typeof field === 'string'\n ? truncatePersistedDetailString(field, MAX_PERSISTED_DETAIL_FALLBACK_STRING_CHARS)\n : field;\n }\n }\n }\n return fallback;\n}\n\nfunction enforcePersistedDetailsByteCap(\n value: Record<string, unknown>,\n src: Record<string, unknown> | undefined,\n originalSize: BoundedJsonUtf8Bytes,\n): Record<string, unknown> {\n const sanitizedBytes = jsonUtf8BytesOrInfinity(value);\n if (sanitizedBytes <= MAX_PERSISTED_TOOL_RESULT_DETAILS_BYTES) {\n return value;\n }\n const fallback = buildPersistedDetailsFallback(src, originalSize, sanitizedBytes);\n if (jsonUtf8BytesOrInfinity(fallback) <= MAX_PERSISTED_TOOL_RESULT_DETAILS_BYTES) {\n return fallback;\n }\n return {\n persistedDetailsTruncated: true,\n finalDetailsTruncated: true,\n ...originalDetailsSizeFields(originalSize),\n sanitizedDetailsBytes: sanitizedBytes,\n };\n}\n\nfunction sanitizeToolResultDetailsForPersistence(details: unknown): unknown {\n if (details === undefined || details === null) {\n return details;\n }\n const originalSize = boundedJsonUtf8Bytes(details, MAX_PERSISTED_TOOL_RESULT_DETAILS_BYTES);\n if (originalSize.complete && originalSize.bytes <= MAX_PERSISTED_TOOL_RESULT_DETAILS_BYTES) {\n return details;\n }\n if (typeof details !== 'object') {\n return enforcePersistedDetailsByteCap(\n {\n persistedDetailsTruncated: true,\n ...originalDetailsSizeFields(originalSize),\n valueType: typeof details,\n },\n undefined,\n originalSize,\n );\n }\n const src = details as Record<string, unknown>;\n const out: Record<string, unknown> = {\n persistedDetailsTruncated: true,\n ...originalDetailsSizeFields(originalSize),\n originalDetailKeys: firstEnumerableOwnKeys(src, 40),\n };\n for (const key of [\n 'status',\n 'sessionId',\n 'pid',\n 'startedAt',\n 'endedAt',\n 'cwd',\n 'name',\n 'exitCode',\n 'exitSignal',\n 'retryInMs',\n 'total',\n 'totalLines',\n 'totalChars',\n 'truncated',\n 'fullOutputPath',\n 'truncation',\n ]) {\n const field = src[key];\n if (field !== undefined) {\n out[key] = typeof field === 'string' ? truncatePersistedDetailString(field) : field;\n }\n }\n if (typeof src.tail === 'string') {\n out.tail = truncatePersistedDetailString(src.tail);\n }\n if (Array.isArray(src.sessions)) {\n out.sessions = src.sessions\n .slice(0, MAX_PERSISTED_DETAIL_SESSION_COUNT)\n .map(sanitizePersistedSessionDetail);\n if (src.sessions.length > MAX_PERSISTED_DETAIL_SESSION_COUNT) {\n out.sessionsTruncated = src.sessions.length - MAX_PERSISTED_DETAIL_SESSION_COUNT;\n }\n }\n return enforcePersistedDetailsByteCap(out, src, originalSize);\n}\n\nfunction capToolResultDetails(msg: AgentMessage): AgentMessage {\n if ((msg as { role?: string }).role !== 'toolResult') {\n return msg;\n }\n const details = (msg as { details?: unknown }).details;\n const sanitizedDetails = sanitizeToolResultDetailsForPersistence(details);\n if (sanitizedDetails === details) {\n return msg;\n }\n const next = { ...msg } as AgentMessage & { details?: unknown };\n next.details = sanitizedDetails;\n return next;\n}\n\nfunction capToolResultForPersistence(msg: AgentMessage, maxChars: number): AgentMessage {\n return capToolResultDetails(capToolResultSize(msg, maxChars));\n}\n\nfunction normalizePersistedToolResultName(\n message: AgentMessage,\n fallbackName?: string,\n): AgentMessage {\n if ((message as { role?: unknown }).role !== 'toolResult') {\n return message;\n }\n const toolResult = message as Extract<AgentMessage, { role: 'toolResult' }>;\n const rawToolName = (toolResult as { toolName?: unknown }).toolName;\n const normalizedToolName = normalizeOptionalString(rawToolName);\n if (normalizedToolName) {\n if (rawToolName === normalizedToolName) {\n return toolResult;\n }\n return { ...toolResult, toolName: normalizedToolName };\n }\n\n const normalizedFallback = normalizeOptionalString(fallbackName);\n if (normalizedFallback) {\n return { ...toolResult, toolName: normalizedFallback };\n }\n\n if (typeof rawToolName === 'string') {\n return { ...toolResult, toolName: 'unknown' };\n }\n return toolResult;\n}\n\n// ── Internal: raw-append symbol storage ─────────────────────────────────────\n\nconst RAW_APPEND_MESSAGE = Symbol('xopc.session.rawAppendMessage');\n\ntype SessionManagerWithRawAppend = SessionManager & {\n [RAW_APPEND_MESSAGE]?: SessionManager['appendMessage'];\n};\n\nfunction rememberOriginalAppend(\n sessionManager: SessionManager,\n): SessionManager['appendMessage'] {\n const tagged = sessionManager as SessionManagerWithRawAppend;\n const stored = tagged[RAW_APPEND_MESSAGE];\n if (stored) {\n return stored;\n }\n const original = sessionManager.appendMessage.bind(sessionManager);\n tagged[RAW_APPEND_MESSAGE] = original;\n return original;\n}\n\n// ── Internal: pending tool-call state ──────────────────────────────────────\n\ntype PendingToolCall = { id: string; name?: string };\n\nclass PendingToolCallTracker {\n private readonly pending = new Map<string, string | undefined>();\n\n size(): number {\n return this.pending.size;\n }\n\n getToolName(id: string): string | undefined {\n return this.pending.get(id);\n }\n\n delete(id: string): void {\n this.pending.delete(id);\n }\n\n clear(): void {\n this.pending.clear();\n }\n\n trackToolCalls(calls: readonly PendingToolCall[]): void {\n for (const call of calls) {\n this.pending.set(call.id, call.name);\n }\n }\n\n entries(): IterableIterator<[string, string | undefined]> {\n return this.pending.entries();\n }\n\n getPendingIds(): string[] {\n return Array.from(this.pending.keys());\n }\n\n shouldFlushForSanitizedDrop(): boolean {\n return this.pending.size > 0;\n }\n\n shouldFlushBeforeNonToolResult(nextRole: unknown, toolCallCount: number): boolean {\n return this.pending.size > 0 && (toolCallCount === 0 || nextRole !== 'assistant');\n }\n\n shouldFlushBeforeNewToolCalls(toolCallCount: number): boolean {\n return this.pending.size > 0 && toolCallCount > 0;\n }\n}\n\n// ── ToolResultGuard class ──────────────────────────────────────────────────\n\nclass ToolResultGuard {\n private readonly sessionManager: SessionManager;\n private readonly opts: ToolResultGuardOptions;\n private readonly pending = new PendingToolCallTracker();\n private readonly originalAppend: SessionManager['appendMessage'];\n private readonly allowSyntheticToolResults: boolean;\n private readonly maxToolResultChars: number;\n\n constructor(sessionManager: SessionManager, opts: ToolResultGuardOptions) {\n this.sessionManager = sessionManager;\n this.opts = opts;\n this.originalAppend = rememberOriginalAppend(sessionManager);\n this.allowSyntheticToolResults = opts.allowSyntheticToolResults ?? true;\n this.maxToolResultChars = resolveMaxToolResultChars(opts);\n }\n\n /** Monkey-patch the session manager so pi-coding-agent's internal appendMessage flows through us. */\n attach(): void {\n const bound = this.guardedAppend.bind(this);\n this.sessionManager.appendMessage = bound as SessionManager['appendMessage'];\n }\n\n flushPending(): void {\n if (this.pending.size() === 0) {\n return;\n }\n if (this.allowSyntheticToolResults) {\n for (const [id, name] of this.pending.entries()) {\n const synthetic = makeMissingToolResult({\n toolCallId: id,\n toolName: name,\n text: this.opts.missingToolResultText,\n });\n const flushed = this.applyBeforeWriteHook(\n this.persistToolResult(this.persistMessage(synthetic), {\n toolCallId: id,\n toolName: name,\n isSynthetic: true,\n }),\n );\n if (flushed) {\n this.originalAppend(capToolResultForPersistence(flushed, this.maxToolResultChars) as never);\n }\n }\n }\n this.pending.clear();\n }\n\n clearPending(): void {\n this.pending.clear();\n }\n\n getPendingIds(): string[] {\n return this.pending.getPendingIds();\n }\n\n private persistMessage(message: AgentMessage): AgentMessage {\n const transformer = this.opts.transformMessageForPersistence;\n return transformer ? transformer(message) : message;\n }\n\n private persistToolResult(\n message: AgentMessage,\n meta: { toolCallId?: string; toolName?: string; isSynthetic?: boolean },\n ): AgentMessage {\n const transformer = this.opts.transformToolResultForPersistence;\n return transformer ? transformer(message, meta) : message;\n }\n\n /**\n * Run the before_message_write hook. Returns the (possibly modified) message,\n * or null if the message should be blocked.\n */\n private applyBeforeWriteHook(msg: AgentMessage): AgentMessage | null {\n const beforeWrite = this.opts.beforeMessageWriteHook;\n if (!beforeWrite) {\n return msg;\n }\n const result = beforeWrite({ message: msg });\n if (result?.block) {\n return null;\n }\n if (result?.message) {\n return result.message;\n }\n return msg;\n }\n\n private guardedAppend(message: AgentMessage): unknown {\n let nextMessage = message;\n const role = (message as { role?: unknown }).role;\n if (role === 'assistant') {\n const sanitized = sanitizeToolCallInputs([message], {\n allowedToolNames: this.opts.allowedToolNames,\n });\n if (sanitized.length === 0) {\n if (this.pending.shouldFlushForSanitizedDrop()) {\n this.flushPending();\n }\n return undefined;\n }\n nextMessage = sanitized[0];\n }\n const nextRole = (nextMessage as { role?: unknown }).role;\n\n if (nextRole === 'toolResult') {\n const id = extractToolResultId(nextMessage as Extract<AgentMessage, { role: 'toolResult' }>);\n const toolName = id ? this.pending.getToolName(id) : undefined;\n if (id) {\n this.pending.delete(id);\n }\n const normalizedToolResult = normalizePersistedToolResultName(nextMessage, toolName);\n // Apply hard size cap before persistence to prevent oversized tool results\n // from consuming the entire context window on subsequent LLM calls.\n const capped = capToolResultForPersistence(\n this.persistMessage(normalizedToolResult),\n this.maxToolResultChars,\n );\n const persisted = this.applyBeforeWriteHook(\n this.persistToolResult(capped, {\n toolCallId: id ?? undefined,\n toolName,\n isSynthetic: false,\n }),\n );\n if (!persisted) {\n return undefined;\n }\n return this.originalAppend(capToolResultForPersistence(persisted, this.maxToolResultChars) as never);\n }\n\n // Skip tool call extraction for aborted/errored assistant messages.\n // When stopReason is \"error\" or \"aborted\", the tool_use blocks may be incomplete\n // and should not have synthetic tool_results created. Creating synthetic results\n // for incomplete tool calls causes API 400 errors:\n // \"unexpected tool_use_id found in tool_result blocks\"\n // This matches the behavior in repairToolUseResultPairing (session-transcript-repair.ts)\n const stopReason = (nextMessage as { stopReason?: string }).stopReason;\n const toolCalls =\n nextRole === 'assistant' && stopReason !== 'aborted' && stopReason !== 'error'\n ? extractToolCallsFromAssistant(nextMessage as Extract<AgentMessage, { role: 'assistant' }>)\n : [];\n\n // Always clear pending tool call state before appending non-tool-result messages.\n // flushPendingToolResults() only inserts synthetic results when allowSyntheticToolResults\n // is true; it always clears the pending map. Without this, providers that disable\n // synthetic results (e.g. OpenAI) accumulate stale pending state when a user message\n // interrupts in-flight tool calls, leaving orphaned tool_use blocks in the transcript\n // that cause API 400 errors on subsequent requests.\n if (this.pending.shouldFlushBeforeNonToolResult(nextRole, toolCalls.length)) {\n this.flushPending();\n }\n // If new tool calls arrive while older ones are pending, flush the old ones first.\n if (this.pending.shouldFlushBeforeNewToolCalls(toolCalls.length)) {\n this.flushPending();\n }\n\n const finalMessage = this.applyBeforeWriteHook(this.persistMessage(nextMessage));\n if (!finalMessage) {\n return undefined;\n }\n const result = this.originalAppend(finalMessage as never);\n\n if (this.opts.sessionKey) {\n emitSessionTranscriptUpdate({\n sessionKey: this.opts.sessionKey,\n message: finalMessage,\n messageId: typeof result === 'string' ? result : undefined,\n });\n }\n\n if (toolCalls.length > 0) {\n this.pending.trackToolCalls(toolCalls);\n }\n\n return result;\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { SessionManager } from '@earendil-works/pi-coding-agent';
|
|
2
|
+
/**
|
|
3
|
+
* Open an in-memory pi SessionManager hydrated from the SQLite transcript.
|
|
4
|
+
* File persistence is disabled; writes flow through the tool-result guard into SQLite.
|
|
5
|
+
*/
|
|
6
|
+
export declare function openSqliteHydratingSessionManager(params: {
|
|
7
|
+
sessionKey: string;
|
|
8
|
+
sessionId: string;
|
|
9
|
+
cwd: string;
|
|
10
|
+
}): SessionManager;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { requireXopcDatabase } from "../../storage/sqlite/connection.js";
|
|
2
|
+
import { ensureSessionRecord } from "../../storage/sqlite/session-repository.js";
|
|
3
|
+
import { loadTranscriptRowsForSession } from "../../storage/sqlite/transcript-repository.js";
|
|
4
|
+
import "../../storage/sqlite/index.js";
|
|
5
|
+
import { storedRowsToFileEntries } from "../../session/stored-rows-to-file-entries.js";
|
|
6
|
+
import { repairAssistantUsageInSessionManager } from "./session-manager-init.js";
|
|
7
|
+
import { SessionManager } from "@earendil-works/pi-coding-agent";
|
|
8
|
+
//#region src/agent/embedded/sqlite-hydrating-session-manager.ts
|
|
9
|
+
/**
|
|
10
|
+
* Open an in-memory pi SessionManager hydrated from the SQLite transcript.
|
|
11
|
+
* File persistence is disabled; writes flow through the tool-result guard into SQLite.
|
|
12
|
+
*/
|
|
13
|
+
function openSqliteHydratingSessionManager(params) {
|
|
14
|
+
requireXopcDatabase();
|
|
15
|
+
ensureSessionRecord(params.sessionKey, params.cwd);
|
|
16
|
+
const rows = loadTranscriptRowsForSession(params.sessionKey);
|
|
17
|
+
const sm = SessionManager.inMemory(params.cwd);
|
|
18
|
+
const entries = storedRowsToFileEntries({
|
|
19
|
+
sessionId: params.sessionId,
|
|
20
|
+
cwd: params.cwd,
|
|
21
|
+
rows
|
|
22
|
+
});
|
|
23
|
+
const internal = sm;
|
|
24
|
+
internal.fileEntries = entries;
|
|
25
|
+
internal.sessionId = params.sessionId;
|
|
26
|
+
internal.flushed = true;
|
|
27
|
+
internal._buildIndex();
|
|
28
|
+
repairAssistantUsageInSessionManager(sm);
|
|
29
|
+
return sm;
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
export { openSqliteHydratingSessionManager };
|
|
33
|
+
|
|
34
|
+
//# sourceMappingURL=sqlite-hydrating-session-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite-hydrating-session-manager.js","names":[],"sources":["../../../../src/agent/embedded/sqlite-hydrating-session-manager.ts"],"sourcesContent":["import { SessionManager, type FileEntry } from '@earendil-works/pi-coding-agent';\n\nimport {\n ensureSessionRecord,\n loadTranscriptRowsForSession,\n requireXopcDatabase,\n} from '../../storage/sqlite/index.js';\nimport { storedRowsToFileEntries } from '../../session/stored-rows-to-file-entries.js';\nimport { repairAssistantUsageInSessionManager } from './session-manager-init.js';\n\ntype SessionManagerHydrationTarget = {\n fileEntries: FileEntry[];\n sessionId: string;\n flushed: boolean;\n byId: Map<string, unknown>;\n labelsById: Map<string, unknown>;\n leafId: string | null;\n _buildIndex: () => void;\n};\n\n/**\n * Open an in-memory pi SessionManager hydrated from the SQLite transcript.\n * File persistence is disabled; writes flow through the tool-result guard into SQLite.\n */\nexport function openSqliteHydratingSessionManager(params: {\n sessionKey: string;\n sessionId: string;\n cwd: string;\n}): SessionManager {\n requireXopcDatabase();\n\n ensureSessionRecord(params.sessionKey, params.cwd);\n const rows = loadTranscriptRowsForSession(params.sessionKey);\n const sm = SessionManager.inMemory(params.cwd);\n const entries = storedRowsToFileEntries({\n sessionId: params.sessionId,\n cwd: params.cwd,\n rows,\n });\n\n const internal = sm as unknown as SessionManagerHydrationTarget;\n internal.fileEntries = entries;\n internal.sessionId = params.sessionId;\n internal.flushed = true;\n internal._buildIndex();\n repairAssistantUsageInSessionManager(sm);\n return sm;\n}\n"],"mappings":";;;;;;;;;;;;AAwBA,SAAgB,kCAAkC,QAI/B;AACjB,sBAAqB;AAErB,qBAAoB,OAAO,YAAY,OAAO,IAAI;CAClD,MAAM,OAAO,6BAA6B,OAAO,WAAW;CAC5D,MAAM,KAAK,eAAe,SAAS,OAAO,IAAI;CAC9C,MAAM,UAAU,wBAAwB;EACtC,WAAW,OAAO;EAClB,KAAK,OAAO;EACZ;EACD,CAAC;CAEF,MAAM,WAAW;AACjB,UAAS,cAAc;AACvB,UAAS,YAAY,OAAO;AAC5B,UAAS,UAAU;AACnB,UAAS,aAAa;AACtB,sCAAqC,GAAG;AACxC,QAAO"}
|