@xopcai/xopc 0.0.122 → 0.0.124
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/README.md +28 -11
- package/README.zh-CN.md +28 -11
- package/dist/browser-ext/manifest.json +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/{agent-avatar-dicebear-CK3s5cJo.js → agent-avatar-dicebear-B5uSVAyJ.js} +1 -1
- package/dist/gateway/static/root/assets/agents-BjIxt67Z.js +2 -0
- package/dist/gateway/static/root/assets/{app-management-settings-panel-CWsNHtTs.js → app-management-settings-panel-CYCONLIL.js} +1 -1
- package/dist/gateway/static/root/assets/{appearance-settings-C4b8y6iZ.js → appearance-settings-Clw3ryKg.js} +1 -1
- package/dist/gateway/static/root/assets/apps-page-QICTeGjX.js +1 -0
- package/dist/gateway/static/root/assets/{archive-plugin-DAtNW3Ia.js → archive-plugin-CXUGRp2i.js} +1 -1
- package/dist/gateway/static/root/assets/{attachment-load-Dm-RY54y.js → attachment-load-Cmiub3Wg.js} +1 -1
- package/dist/gateway/static/root/assets/{automation-suggestion-card-BKHewSVT.js → automation-suggestion-card-CU0-4nQ-.js} +1 -1
- package/dist/gateway/static/root/assets/automations-page-Car-jtiz.js +1 -0
- package/dist/gateway/static/root/assets/{binary-plugins-BG6Xnk71.js → binary-plugins-0Izz-N3p.js} +1 -1
- package/dist/gateway/static/root/assets/{block-editor-DfndiO3h.js → block-editor-C1Kv0IkJ.js} +28 -28
- package/dist/gateway/static/root/assets/browser-settings-page-CMeBJzlS.js +8 -0
- package/dist/gateway/static/root/assets/{capability-presets-api-Cx93GLdw.js → capability-presets-api-BmQTM9Il.js} +1 -1
- package/dist/gateway/static/root/assets/capability-presets-settings-panel-DZEJEoCF.js +1 -0
- package/dist/gateway/static/root/assets/{channel-recipient-api-DdPTtEs8.js → channel-recipient-api-BeA2aVor.js} +1 -1
- package/dist/gateway/static/root/assets/channels-settings-kkW-xFau.js +1 -0
- package/dist/gateway/static/root/assets/connectors-page-BvaQvO5F.js +2 -0
- package/dist/gateway/static/root/assets/{directory-picker-path-field-D835EHJd.js → directory-picker-path-field-2rjzUu57.js} +1 -1
- package/dist/gateway/static/root/assets/{dreaming-settings-CCa1F7Fg.js → dreaming-settings-DnHO686d.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-debug-page-W2ITCPSA.js → extension-debug-page-BGLRuWVN.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-DrvUytvn.js → extension-page-D978ePp9.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-qM8aLT5u.js → extension-settings-page-B6Ih4Swx.js} +1 -1
- package/dist/gateway/static/root/assets/{fetch-ovkvM8KQ.js → fetch-BU5S0Sn7.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-config-api-BB3biuD0.js → gateway-config-api-BIPGwrl1.js} +1 -1
- package/dist/gateway/static/root/assets/gateway-settings-CMEA3r5u.js +1 -0
- package/dist/gateway/static/root/assets/{gateway-startup-retry-CanQ8UcR.js → gateway-startup-retry-Cd0Zz30N.js} +1 -1
- package/dist/gateway/static/root/assets/{goal-create-dialog-CNqNdEkQ.js → goal-create-dialog-B-KHKeXN.js} +1 -1
- package/dist/gateway/static/root/assets/goal-detail-page-BxrSoWdj.js +1 -0
- package/dist/gateway/static/root/assets/{goals-config-api-BBOX6mUl.js → goals-config-api-B95KdGMj.js} +1 -1
- package/dist/gateway/static/root/assets/{goals-page-BP1kfvWv.js → goals-page-BTJzIl28.js} +2 -2
- package/dist/gateway/static/root/assets/{goals-settings-Bh3A7OZV.js → goals-settings-BJyEuLLm.js} +1 -1
- package/dist/gateway/static/root/assets/heartbeat-settings-wkOjYNQT.js +1 -0
- package/dist/gateway/static/root/assets/index-BLaXr6Ev.css +1 -0
- package/dist/gateway/static/root/assets/index-c_9TVr_C.js +191 -0
- package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-B4TKJHla.js → keyboard-shortcuts-settings-BT8T4DyC.js} +1 -1
- package/dist/gateway/static/root/assets/logs-page-CrxBJUgI.js +2 -0
- package/dist/gateway/static/root/assets/{markdown-split-DcFtdLci.js → markdown-split-Dv66bO2b.js} +1 -1
- package/dist/gateway/static/root/assets/{media-plugins-B6SmeLVB.js → media-plugins-BhIQU5yS.js} +1 -1
- package/dist/gateway/static/root/assets/messages-O6TAtdFC.js +3 -0
- package/dist/gateway/static/root/assets/models-hub-panel-DOOpz6y1.js +2 -0
- package/dist/gateway/static/root/assets/notes-page-Br16pK9v.js +1 -0
- package/dist/gateway/static/root/assets/notes-workbench-PSJAi0ZC.js +2 -0
- package/dist/gateway/static/root/assets/{preview-open-alternatives-C29O19s7.js → preview-open-alternatives-DGzet1d7.js} +1 -1
- package/dist/gateway/static/root/assets/project-detail-page-CIGGgKuw.js +1 -0
- package/dist/gateway/static/root/assets/projects-page-B2grhsBt.js +1 -0
- package/dist/gateway/static/root/assets/remote-access-hub-CXMORxBD.js +1 -0
- package/dist/gateway/static/root/assets/{save-bar-controls-B4ad9GfB.js → save-bar-controls-Cqgb-jqa.js} +1 -1
- package/dist/gateway/static/root/assets/{schedule-field-DdYncfLR.js → schedule-field-DVZcI8Yt.js} +1 -1
- package/dist/gateway/static/root/assets/{schema-form-BMTrDE-7.js → schema-form-Bj7sOxC8.js} +1 -1
- package/dist/gateway/static/root/assets/sessions-page-Bi2wkBzU.js +1 -0
- package/dist/gateway/static/root/assets/settings-advanced-gate-B1L3G4IA.js +1 -0
- package/dist/gateway/static/root/assets/{settings-collapsible-section-MwTW2eK4.js → settings-collapsible-section-BmXn-8N6.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-form-section-Dg6LBbcr.js → settings-form-section-BnqYMWHR.js} +1 -1
- package/dist/gateway/static/root/assets/settings-page-BfRSHJmO.js +1 -0
- package/dist/gateway/static/root/assets/{settings-page-layout-BX4T-OLF.js → settings-page-layout-CdWZU1hd.js} +1 -1
- package/dist/gateway/static/root/assets/{setup-status-panel-Ce1pcFtR.js → setup-status-panel-CWtPM7RK.js} +1 -1
- package/dist/gateway/static/root/assets/{share-preview-page-DWE-dUFh.js → share-preview-page-IB1v8i86.js} +1 -1
- package/dist/gateway/static/root/assets/shares-settings-ngbsBgij.js +1 -0
- package/dist/gateway/static/root/assets/skill-reload-api-BIe442Gv.js +1 -0
- package/dist/gateway/static/root/assets/skills-page-BfchZRVa.js +2 -0
- package/dist/gateway/static/root/assets/{system-settings-panel-Dd6Y35Cm.js → system-settings-panel-BxQ384RC.js} +1 -1
- package/dist/gateway/static/root/assets/{typed-models-lib-Cx6XV5A2.js → typed-models-lib-BkcpvFYO.js} +1 -1
- package/dist/gateway/static/root/assets/{use-channel-catalog-D4sMbmy7.js → use-channel-catalog-Cd0FaEQ4.js} +1 -1
- package/dist/gateway/static/root/assets/{user-profile-settings-panel-Bx-hjMQc.js → user-profile-settings-panel-DQ7NnRSY.js} +1 -1
- package/dist/gateway/static/root/assets/{utils-D7b_vZnv.js → utils-YKhVf7Ka.js} +1 -1
- package/dist/gateway/static/root/assets/voice-api-key-field-C5Ov8Uk_.js +1 -0
- package/dist/gateway/static/root/assets/{workflow-run-setup-panel--TdohF5N.js → workflow-run-setup-panel-BYD9BtOq.js} +2 -2
- package/dist/gateway/static/root/assets/{workflow-start-dialog-B0lavx_o.js → workflow-start-dialog-BNJVCWoS.js} +1 -1
- package/dist/gateway/static/root/assets/workflows-page-BYF29ANm.js +18 -0
- package/dist/gateway/static/root/index.html +5 -5
- package/dist/package.js +1 -1
- package/dist/src/agent/agent-manager.d.ts +6 -1
- package/dist/src/agent/agent-manager.js +28 -12
- package/dist/src/agent/agent-manager.js.map +1 -1
- package/dist/src/agent/context/workspace-templates/AGENTS.md +1 -1
- package/dist/src/agent/embedded/xopc-tools-bridge.js +11 -7
- package/dist/src/agent/embedded/xopc-tools-bridge.js.map +1 -1
- package/dist/src/agent/lifecycle/progress.js +3 -1
- package/dist/src/agent/lifecycle/progress.js.map +1 -1
- package/dist/src/agent/lifecycle/timeout-wrapper.d.ts +2 -2
- package/dist/src/agent/lifecycle/timeout-wrapper.js +4 -4
- package/dist/src/agent/lifecycle/timeout-wrapper.js.map +1 -1
- package/dist/src/agent/memory/summary-generator.d.ts +1 -1
- package/dist/src/agent/memory/summary-generator.js +1 -2
- package/dist/src/agent/memory/summary-generator.js.map +1 -1
- package/dist/src/agent/middleware/index.d.ts +1 -1
- package/dist/src/agent/middleware/self-verify.d.ts +44 -14
- package/dist/src/agent/middleware/self-verify.js +214 -44
- package/dist/src/agent/middleware/self-verify.js.map +1 -1
- package/dist/src/agent/orchestration/agent-event-handler.js +57 -15
- package/dist/src/agent/orchestration/agent-event-handler.js.map +1 -1
- package/dist/src/agent/prompt/sections/tooling.js +18 -13
- package/dist/src/agent/prompt/sections/tooling.js.map +1 -1
- package/dist/src/agent/prompt/system-reminder.js +2 -2
- package/dist/src/agent/prompt/system-reminder.js.map +1 -1
- package/dist/src/agent/sandbox/sanitize-env-vars.d.ts +2 -2
- package/dist/src/agent/sandbox/sanitize-env-vars.js +2 -2
- package/dist/src/agent/sandbox/sanitize-env-vars.js.map +1 -1
- package/dist/src/agent/sandbox/types.d.ts +2 -2
- package/dist/src/agent/sandbox/types.js.map +1 -1
- package/dist/src/agent/service/btw-query.js +1 -1
- package/dist/src/agent/service/btw-query.js.map +1 -1
- package/dist/src/agent/service/process-direct-one-shot.js +6 -2
- package/dist/src/agent/service/process-direct-one-shot.js.map +1 -1
- package/dist/src/agent/service/process-direct-streaming.js +12 -7
- package/dist/src/agent/service/process-direct-streaming.js.map +1 -1
- package/dist/src/agent/service.js +2 -1
- package/dist/src/agent/service.js.map +1 -1
- package/dist/src/agent/skills/skill-tool-gating.js +1 -1
- package/dist/src/agent/skills/skill-tool-gating.js.map +1 -1
- package/dist/src/agent/starter-agents.js +9 -9
- package/dist/src/agent/starter-agents.js.map +1 -1
- package/dist/src/agent/tools/apply-patch.d.ts +25 -0
- package/dist/src/agent/tools/apply-patch.js +256 -0
- package/dist/src/agent/tools/apply-patch.js.map +1 -0
- package/dist/src/agent/tools/delegate-tool.d.ts +1 -1
- package/dist/src/agent/tools/delegate-tool.js +3 -3
- package/dist/src/agent/tools/delegate-tool.js.map +1 -1
- package/dist/src/agent/tools/exec-command.d.ts +30 -0
- package/dist/src/agent/tools/exec-command.js +224 -0
- package/dist/src/agent/tools/exec-command.js.map +1 -0
- package/dist/src/agent/tools/execute-code-tool.js +7 -4
- package/dist/src/agent/tools/execute-code-tool.js.map +1 -1
- package/dist/src/agent/tools/factory.d.ts +3 -3
- package/dist/src/agent/tools/factory.js +8 -9
- package/dist/src/agent/tools/factory.js.map +1 -1
- package/dist/src/agent/tools/goal-tool.d.ts +5 -1
- package/dist/src/agent/tools/goal-tool.js +8 -2
- package/dist/src/agent/tools/goal-tool.js.map +1 -1
- package/dist/src/agent/tools/index.d.ts +4 -3
- package/dist/src/agent/tools/index.js +5 -4
- package/dist/src/agent/tools/skills-tools.d.ts +1 -1
- package/dist/src/agent/tools/skills-tools.js.map +1 -1
- package/dist/src/agent/tools/update-plan-tool.d.ts +11 -0
- package/dist/src/agent/tools/update-plan-tool.js +72 -0
- package/dist/src/agent/tools/update-plan-tool.js.map +1 -0
- package/dist/src/agent/workflow/builtins/debug-incident.d.ts +1 -1
- package/dist/src/agent/workflow/builtins/debug-incident.js +1 -1
- package/dist/src/agent/workflow/builtins/debug-incident.js.map +1 -1
- package/dist/src/agent/workflow/builtins/pr-review.d.ts +1 -1
- package/dist/src/agent/workflow/builtins/pr-review.js +1 -1
- package/dist/src/agent/workflow/builtins/pr-review.js.map +1 -1
- package/dist/src/agent/workflow/builtins/release-check.d.ts +1 -1
- package/dist/src/agent/workflow/builtins/release-check.js +1 -1
- package/dist/src/agent/workflow/builtins/release-check.js.map +1 -1
- package/dist/src/agent/workflow/step-labels.js +7 -3
- package/dist/src/agent/workflow/step-labels.js.map +1 -1
- package/dist/src/chat-commands/agent-edit.js +1 -1
- package/dist/src/chat-commands/agent-edit.js.map +1 -1
- package/dist/src/chat-commands/builtins/goal.js +4 -1
- package/dist/src/chat-commands/builtins/goal.js.map +1 -1
- package/dist/src/chat-commands/builtins/review.d.ts +1 -0
- package/dist/src/chat-commands/builtins/review.js +167 -0
- package/dist/src/chat-commands/builtins/review.js.map +1 -0
- package/dist/src/chat-commands/index.d.ts +1 -0
- package/dist/src/chat-commands/index.js +3 -1
- package/dist/src/chat-commands/index.js.map +1 -1
- package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
- package/dist/src/cli/commands/gateway/lifecycle.js +1 -1
- package/dist/src/config/agent-profile.js +1 -1
- package/dist/src/gateway/agent-builtin-tools.d.ts +1 -1
- package/dist/src/gateway/agent-builtin-tools.js +3 -2
- package/dist/src/gateway/agent-builtin-tools.js.map +1 -1
- package/dist/src/gateway/agents-admin.js +1 -1
- package/dist/src/gateway/chat-stream/mapper.d.ts +6 -0
- package/dist/src/gateway/chat-stream/mapper.js +149 -6
- package/dist/src/gateway/chat-stream/mapper.js.map +1 -1
- package/dist/src/gateway/chat-stream/protocol.d.ts +51 -1
- package/dist/src/gateway/hono/routes/commands-skills.js +21 -0
- package/dist/src/gateway/hono/routes/commands-skills.js.map +1 -1
- package/dist/src/gateway/hono/routes/index.js +2 -0
- package/dist/src/gateway/hono/routes/index.js.map +1 -1
- package/dist/src/gateway/hono/routes/lazy-bundles.js +1 -0
- package/dist/src/gateway/hono/routes/lazy-bundles.js.map +1 -1
- package/dist/src/gateway/hono/routes/projects.js +136 -14
- package/dist/src/gateway/hono/routes/projects.js.map +1 -1
- package/dist/src/gateway/hono/routes/search.d.ts +3 -0
- package/dist/src/gateway/hono/routes/search.js +47 -0
- package/dist/src/gateway/hono/routes/search.js.map +1 -0
- package/dist/src/gateway/security/dangerous-tools.js +1 -1
- package/dist/src/gateway/security/dangerous-tools.js.map +1 -1
- package/dist/src/infra/update-restart.js +1 -1
- package/dist/src/projects/index.d.ts +3 -0
- package/dist/src/projects/index.js +4 -1
- package/dist/src/projects/project-agent-suggestion.d.ts +14 -0
- package/dist/src/projects/project-agent-suggestion.js +19 -0
- package/dist/src/projects/project-agent-suggestion.js.map +1 -0
- package/dist/src/projects/project-kind.d.ts +13 -0
- package/dist/src/projects/project-kind.js +147 -0
- package/dist/src/projects/project-kind.js.map +1 -0
- package/dist/src/projects/project-service.d.ts +15 -0
- package/dist/src/projects/project-service.js +96 -3
- package/dist/src/projects/project-service.js.map +1 -1
- package/dist/src/projects/project-store.js +32 -8
- package/dist/src/projects/project-store.js.map +1 -1
- package/dist/src/projects/types.d.ts +4 -1
- package/dist/src/projects/workspace-project.d.ts +22 -0
- package/dist/src/projects/workspace-project.js +143 -0
- package/dist/src/projects/workspace-project.js.map +1 -0
- package/dist/src/review/review-git.d.ts +48 -0
- package/dist/src/review/review-git.js +333 -0
- package/dist/src/review/review-git.js.map +1 -0
- package/dist/src/review/review-types.d.ts +22 -0
- package/dist/src/review/review-types.js +29 -0
- package/dist/src/review/review-types.js.map +1 -0
- package/dist/src/session/session-context-for-llm.js +151 -0
- package/dist/src/session/session-context-for-llm.js.map +1 -1
- package/dist/src/storage/sqlite/migrations/021_projects_fts.sql +21 -0
- package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
- package/dist/src/storage/sqlite/migrations/runner.js +3 -3
- package/dist/src/storage/sqlite/migrations/runner.js.map +1 -1
- package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
- package/dist/src/storage/sqlite/schema.js +1 -1
- package/dist/src/tui/backends/embedded-backend.d.ts +8 -1
- package/dist/src/tui/backends/embedded-backend.js +60 -1
- package/dist/src/tui/backends/embedded-backend.js.map +1 -1
- package/dist/src/tui/backends/gateway-sse-backend.d.ts +9 -1
- package/dist/src/tui/backends/gateway-sse-backend.js +29 -0
- package/dist/src/tui/backends/gateway-sse-backend.js.map +1 -1
- package/dist/src/tui/components/assistant-message.js +35 -0
- package/dist/src/tui/components/assistant-message.js.map +1 -1
- package/dist/src/tui/tui-agent-events.js +25 -0
- package/dist/src/tui/tui-agent-events.js.map +1 -1
- package/dist/src/tui/tui-backend.d.ts +20 -0
- package/dist/src/tui/tui-commands.d.ts +1 -0
- package/dist/src/tui/tui-commands.js +110 -4
- package/dist/src/tui/tui-commands.js.map +1 -1
- package/dist/src/tui/tui-picker-overlay.d.ts +2 -0
- package/dist/src/tui/tui-picker-overlay.js +153 -1
- package/dist/src/tui/tui-picker-overlay.js.map +1 -1
- package/dist/src/tui/tui.js +36 -2
- package/dist/src/tui/tui.js.map +1 -1
- package/dist/src/workflows/service/workflow-run-service.js +1 -1
- package/dist/src/workflows/service/workflow-run-service.js.map +1 -1
- package/package.json +1 -1
- package/dist/gateway/static/root/assets/agents-SsQrj9B-.js +0 -2
- package/dist/gateway/static/root/assets/apps-page-CYb6fame.js +0 -1
- package/dist/gateway/static/root/assets/automations-page-DpOOq1nk.js +0 -1
- package/dist/gateway/static/root/assets/browser-settings-page-9nXxgh4u.js +0 -8
- package/dist/gateway/static/root/assets/capability-presets-settings-panel-D528_YFy.js +0 -1
- package/dist/gateway/static/root/assets/channels-settings-Ck-3h5lI.js +0 -1
- package/dist/gateway/static/root/assets/connectors-page-DL-fAqE_.js +0 -2
- package/dist/gateway/static/root/assets/gateway-settings-mmmnIH27.js +0 -1
- package/dist/gateway/static/root/assets/goal-detail-page-Ceu5zrN6.js +0 -1
- package/dist/gateway/static/root/assets/heartbeat-settings-YoYgKlfX.js +0 -1
- package/dist/gateway/static/root/assets/index-DdqSO9R2.js +0 -191
- package/dist/gateway/static/root/assets/index-jGB-2nI-.css +0 -1
- package/dist/gateway/static/root/assets/logs-page-BgVk5DvZ.js +0 -2
- package/dist/gateway/static/root/assets/messages-B5O4OIEo.js +0 -3
- package/dist/gateway/static/root/assets/models-hub-panel-BU0x8PGP.js +0 -2
- package/dist/gateway/static/root/assets/notes-page-D6JYVP4I.js +0 -1
- package/dist/gateway/static/root/assets/notes-workbench-mqRJYnJS.js +0 -2
- package/dist/gateway/static/root/assets/project-detail-page-DOTgzoeh.js +0 -1
- package/dist/gateway/static/root/assets/projects-page-DGUIb4y5.js +0 -1
- package/dist/gateway/static/root/assets/remote-access-hub-DVKKUj3u.js +0 -1
- package/dist/gateway/static/root/assets/sessions-page-BVbzUWxW.js +0 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-DrNNoSqE.js +0 -1
- package/dist/gateway/static/root/assets/settings-page-CMDqgeMR.js +0 -1
- package/dist/gateway/static/root/assets/shares-settings-aKSraR8l.js +0 -1
- package/dist/gateway/static/root/assets/skill-reload-api-CS-FU_B-.js +0 -1
- package/dist/gateway/static/root/assets/skills-page-BaF3u0LA.js +0 -2
- package/dist/gateway/static/root/assets/voice-api-key-field-CkhbRt1q.js +0 -1
- package/dist/gateway/static/root/assets/workflows-page-CZR05Bu_.js +0 -18
- package/dist/src/agent/tools/edit.d.ts +0 -14
- package/dist/src/agent/tools/edit.js +0 -132
- package/dist/src/agent/tools/edit.js.map +0 -1
- package/dist/src/agent/tools/shell.d.ts +0 -15
- package/dist/src/agent/tools/shell.js +0 -158
- package/dist/src/agent/tools/shell.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-event-handler.js","names":[],"sources":["../../../../src/agent/orchestration/agent-event-handler.ts"],"sourcesContent":["/**\n * Agent Event Handler — coordinates listeners on pi-agent events.\n *\n * Previously a single god-handler with ten manager fields and one `switch`. Now a\n * thin façade around {@link SessionEventBus}: each concern (progress, lifecycle\n * hooks, tool-chain recording, error tracking, self-verify, etc.) registers its\n * own listener at construction time, and external code can add new listeners via\n * {@link AgentEventHandler.registerListener} without modifying this file (OCP).\n *\n * Ordering note: a few listeners are order-sensitive (notably the\n * `tool_execution_end` chain — `SystemReminder` mutates `event.result` in place\n * and downstream listeners read the mutated value). The `installX` calls below\n * preserve the exact order from the previous implementation.\n */\n\nimport type { AgentEvent } from '@earendil-works/pi-agent-core';\nimport type { SessionContext } from '../session/session-context.js';\nimport type { ProgressFeedbackManager } from '../lifecycle/progress.js';\nimport type { ToolErrorTracker } from '../tools/error-tracker.js';\nimport type { RequestLimiter } from '../models/request-limiter.js';\nimport type { LifecycleManager } from '../lifecycle/index.js';\nimport type { ToolChainTracker } from '../tools/chain-tracker.js';\nimport type { SelfVerifyMiddleware } from '../middleware/index.js';\nimport type { SystemReminder } from '../prompt/system-reminder.js';\nimport type { ToolUsageAnalyzer } from '../tools/usage-analyzer.js';\nimport type { ErrorPatternMatcher } from '../tools/error-pattern-matcher.js';\nimport { extractTextContent } from '../context/workspace.js';\nimport { createLogger } from '../../utils/logger.js';\n\nconst log = createLogger('AgentEventHandler');\n\n// ── Event bus ──────────────────────────────────────────────────────────────\n\nexport type SessionEventListener = (event: AgentEvent, context: SessionContext) => void;\nexport type SessionEventTypeFilter = AgentEvent['type'] | 'all';\n\n/**\n * Typed pub/sub for agent events. Listeners run in registration order; the bus\n * itself is synchronous — listeners that need async work must dispatch their own\n * promises (typically by awaiting and logging on error, like the lifecycle hooks).\n */\nexport class SessionEventBus {\n private readonly listeners = new Map<SessionEventTypeFilter, SessionEventListener[]>();\n\n on(type: SessionEventTypeFilter, listener: SessionEventListener): () => void {\n const bucket = this.listeners.get(type) ?? [];\n bucket.push(listener);\n this.listeners.set(type, bucket);\n return () => {\n const current = this.listeners.get(type);\n if (!current) return;\n const idx = current.indexOf(listener);\n if (idx >= 0) current.splice(idx, 1);\n };\n }\n\n dispatch(event: AgentEvent, context: SessionContext | null): void {\n if (!context) {\n if (event.type !== 'message_update') {\n log.warn(\n { eventType: event.type },\n `Agent event ignored (no SessionContext): ${event.type}`,\n );\n }\n return;\n }\n const exact = this.listeners.get(event.type);\n if (exact) {\n for (const listener of exact) {\n listener(event, context);\n }\n }\n const all = this.listeners.get('all');\n if (all) {\n for (const listener of all) {\n listener(event, context);\n }\n }\n }\n}\n\n// ── Built-in listener installers ───────────────────────────────────────────\n\nfunction installProgressListener(bus: SessionEventBus, progressManager: ProgressFeedbackManager): void {\n bus.on('agent_start', () => {\n log.debug('Agent turn started');\n progressManager.startTask();\n });\n bus.on('turn_start', () => {\n log.debug('Turn started');\n progressManager.onTurnStart();\n });\n bus.on('tool_execution_start', (event) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_start' }>;\n log.debug({ tool: e.toolName, args: e.args }, 'Tool execution started');\n progressManager.onToolStart(e.toolName, e.args || {});\n });\n bus.on('tool_execution_update', (event) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_update' }>;\n progressManager.onToolUpdate(e.toolName, e.partialResult);\n });\n bus.on('tool_execution_end', (event) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_end' }>;\n log.debug({ tool: e.toolName, isError: e.isError }, 'Tool execution complete');\n progressManager.onToolEnd(e.toolName, e.result, e.isError);\n });\n bus.on('agent_end', () => {\n progressManager.endTask();\n });\n}\n\nfunction installRequestLimitListener(\n bus: SessionEventBus,\n deps: {\n requestLimiter: RequestLimiter;\n progressManager: ProgressFeedbackManager;\n lifecycleManager: LifecycleManager;\n },\n): void {\n bus.on('agent_start', (_event, context) => {\n const result = deps.requestLimiter.recordRequest();\n\n deps.lifecycleManager\n .emit(\n 'llm_request',\n context.sessionKey,\n { requestNumber: result.count, maxRequests: result.limit },\n context,\n )\n .catch((err) => {\n const em = err instanceof Error ? err.message : String(err);\n log.warn(\n {\n err,\n errorMessage: em,\n sessionKey: context.sessionKey,\n requestNumber: result.count,\n maxRequests: result.limit,\n },\n `Lifecycle emit llm_request failed: ${em}`,\n );\n });\n\n deps.progressManager.onRequestLimitStatus(\n result.count,\n result.limit,\n result.remaining,\n result.isWarning,\n result.shouldStop,\n );\n\n if (result.shouldStop) {\n log.error(\n { count: result.count, limit: result.limit, sessionKey: context.sessionKey },\n `Request limit reached (${result.count}/${result.limit}) for session`,\n );\n }\n });\n\n bus.on('turn_end', () => {\n deps.requestLimiter.reset();\n });\n}\n\nfunction installLifecycleHookListener(bus: SessionEventBus, lifecycleManager: LifecycleManager): void {\n bus.on('message_end', (event, context) => {\n const e = event as Extract<AgentEvent, { type: 'message_end' }>;\n if (e.message?.role !== 'assistant') return;\n const content = e.message.content;\n const text = Array.isArray(content)\n ? extractTextContent(content as Array<{ type: string; text?: string }>)\n : String(content);\n log.debug({ contentLength: text.length }, 'Assistant response complete');\n\n lifecycleManager\n .emit(\n 'llm_response',\n context.sessionKey,\n { response: text, usage: (e.message as { usage?: unknown }).usage },\n context,\n )\n .catch((err) => {\n const em = err instanceof Error ? err.message : String(err);\n log.warn(\n { err, errorMessage: em, sessionKey: context.sessionKey, responseChars: text.length },\n `Lifecycle emit llm_response failed: ${em}`,\n );\n });\n });\n\n bus.on('tool_execution_start', (event, context) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_start' }>;\n lifecycleManager\n .emit(\n 'tool_call_start',\n context.sessionKey,\n {\n toolName: e.toolName,\n arguments: e.args || {},\n attemptNumber: 1,\n maxAttempts: 3,\n },\n context,\n )\n .catch((err) => {\n const em = err instanceof Error ? err.message : String(err);\n log.warn(\n { err, errorMessage: em, sessionKey: context.sessionKey, tool: e.toolName },\n `Lifecycle emit tool_call_start failed: ${em}`,\n );\n });\n });\n\n bus.on('tool_execution_end', (event, context) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_end' }>;\n const durationMs = (e as { durationMs?: number }).durationMs || 0;\n lifecycleManager\n .emit(\n 'tool_call_end',\n context.sessionKey,\n {\n toolName: e.toolName,\n success: !e.isError,\n result: e.result,\n error: e.isError ? String(e.result) : undefined,\n durationMs,\n },\n context,\n )\n .catch((err) => {\n const em = err instanceof Error ? err.message : String(err);\n log.warn(\n { err, errorMessage: em, sessionKey: context.sessionKey, tool: e.toolName },\n `Lifecycle hook tool_call_end failed: ${em}`,\n );\n });\n });\n}\n\nfunction installSystemReminderListener(bus: SessionEventBus, systemReminder: SystemReminder): void {\n // MUTATES `event.result` so downstream listeners (tool-chain, error-tracking) see\n // the decorated text. Must run AFTER progress + lifecycle hooks (they consume the\n // original) and BEFORE tool-chain + error-tracking.\n bus.on('tool_execution_end', (event) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_end' }> & { result: unknown };\n e.result = systemReminder.appendToResult(e.result, e.toolName);\n });\n}\n\nfunction appendTextToToolResult(result: unknown, text: string): unknown {\n if (!text.trim()) {\n return result;\n }\n if (result && typeof result === 'object') {\n const res = result as Record<string, unknown>;\n if (Array.isArray(res.content)) {\n res.content = [\n ...res.content,\n { type: 'text', text: `\\n${text}` },\n ];\n return res;\n }\n if (typeof res.content === 'string') {\n res.content = `${res.content}\\n${text}`;\n return res;\n }\n }\n return result;\n}\n\nfunction installToolChainListener(bus: SessionEventBus, toolChainTracker: ToolChainTracker): void {\n bus.on('turn_start', (_event, context) => {\n // One chain per LLM turn (pi-agent emits turn_start each round; turn_end clears the chain).\n toolChainTracker.startChain(context.sessionKey);\n });\n bus.on('tool_execution_start', (event, context) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_start' }>;\n toolChainTracker.recordCall(context.sessionKey, e.toolName, e.args || {}, 0);\n });\n bus.on('tool_execution_end', (event, context) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_end' }>;\n const durationMs = (e as { durationMs?: number }).durationMs || 0;\n const chain = toolChainTracker.getCurrentChain(context.sessionKey);\n if (!chain) return;\n const lastNode = chain.nodes[chain.nodes.length - 1];\n if (lastNode && lastNode.toolName === e.toolName) {\n toolChainTracker.recordResult(\n context.sessionKey,\n lastNode.id,\n e.result,\n e.isError ? 'Tool execution failed' : undefined,\n durationMs,\n );\n }\n });\n bus.on('turn_end', (_event, context) => {\n toolChainTracker.endChain(context.sessionKey);\n });\n}\n\nfunction installToolUsageListener(bus: SessionEventBus, toolUsageAnalyzer: ToolUsageAnalyzer): void {\n bus.on('tool_execution_end', (event) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_end' }>;\n const durationMs = (e as { durationMs?: number }).durationMs || 0;\n toolUsageAnalyzer.recordUsage(e.toolName, !e.isError, durationMs);\n });\n}\n\nfunction extractErrorText(result: unknown): string {\n if (typeof result === 'string') return result;\n if (result && typeof result === 'object') {\n const obj = result as Record<string, unknown>;\n if (typeof obj.error === 'string') return obj.error;\n if (typeof obj.message === 'string') return obj.message;\n return JSON.stringify(result);\n }\n return String(result);\n}\n\nfunction installErrorTrackingListener(\n bus: SessionEventBus,\n deps: { errorTracker: ToolErrorTracker; errorPatternMatcher: ErrorPatternMatcher },\n): void {\n bus.on('tool_execution_end', (event) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_end' }>;\n if (!e.isError) return;\n const errorText = extractErrorText(e.result);\n deps.errorTracker.recordFailure(e.toolName, errorText);\n\n const errorMatch = deps.errorPatternMatcher.matchError(errorText);\n if (errorMatch.matched && errorMatch.pattern) {\n const preview = errorText.length > 120 ? `${errorText.slice(0, 120)}…` : errorText;\n log.warn(\n { tool: e.toolName, pattern: errorMatch.pattern.name, errorPreview: preview },\n `Tool error matched pattern \"${errorMatch.pattern.name}\" (${e.toolName}): ${preview}`,\n );\n }\n });\n bus.on('turn_end', () => {\n deps.errorTracker.reset();\n });\n}\n\nfunction installSelfVerifyListener(bus: SessionEventBus, selfVerifyMiddleware: SelfVerifyMiddleware): void {\n bus.on('tool_execution_start', (event) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_start' }>;\n const args = e.args;\n const toolName = e.toolName;\n if (!toolName || !args) return;\n const name = toolName.toLowerCase();\n if (name.includes('write') && args.path) {\n selfVerifyMiddleware.recordEdit(String(args.path), 'write');\n } else if (name.includes('edit') && args.path) {\n selfVerifyMiddleware.recordEdit(String(args.path), 'edit');\n } else {\n selfVerifyMiddleware.recordVerification(toolName, args);\n }\n });\n bus.on('tool_execution_end', (event) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_end' }> & { result: unknown };\n const name = e.toolName?.toLowerCase() ?? '';\n if (!name.includes('write') && !name.includes('edit')) {\n return;\n }\n e.result = appendTextToToolResult(e.result, selfVerifyMiddleware.consumePostEditReminder());\n });\n bus.on('turn_start', () => {\n selfVerifyMiddleware.onTurnStart();\n });\n}\n\n// ── Public façade ───────────────────────────────────────────────────────────\n\nexport interface AgentEventHandlerConfig {\n progressManager: ProgressFeedbackManager;\n errorTracker: ToolErrorTracker;\n requestLimiter: RequestLimiter;\n lifecycleManager: LifecycleManager;\n toolChainTracker: ToolChainTracker;\n selfVerifyMiddleware: SelfVerifyMiddleware;\n systemReminder: SystemReminder;\n toolUsageAnalyzer: ToolUsageAnalyzer;\n errorPatternMatcher: ErrorPatternMatcher;\n}\n\n/**\n * Thin façade over {@link SessionEventBus} that pre-registers all built-in\n * listeners in their required order. `handle(event, ctx)` is the entry point\n * used by `AgentService.handleSessionEvent`; extensions can hook in via\n * {@link registerListener} without touching this class.\n */\nexport class AgentEventHandler {\n private readonly bus: SessionEventBus;\n\n constructor(config: AgentEventHandlerConfig) {\n this.bus = new SessionEventBus();\n\n // Order matters for `tool_execution_end`: SystemReminder mutates `event.result`\n // and ToolChain + ErrorTracking depend on the mutated value. Keep these in this\n // exact sequence.\n installProgressListener(this.bus, config.progressManager);\n installRequestLimitListener(this.bus, {\n requestLimiter: config.requestLimiter,\n progressManager: config.progressManager,\n lifecycleManager: config.lifecycleManager,\n });\n installLifecycleHookListener(this.bus, config.lifecycleManager);\n installSystemReminderListener(this.bus, config.systemReminder);\n installSelfVerifyListener(this.bus, config.selfVerifyMiddleware);\n installToolUsageListener(this.bus, config.toolUsageAnalyzer);\n installToolChainListener(this.bus, config.toolChainTracker);\n installErrorTrackingListener(this.bus, {\n errorTracker: config.errorTracker,\n errorPatternMatcher: config.errorPatternMatcher,\n });\n }\n\n /** Dispatch a pi-agent event to all registered listeners. */\n handle(event: AgentEvent, context: SessionContext | null): void {\n this.bus.dispatch(event, context);\n }\n\n /**\n * Register an additional listener (e.g. from an extension). Returns an\n * unsubscribe function. Listeners run after the built-ins in registration\n * order; if you need to run before a built-in, you must create your own\n * {@link SessionEventBus} instance.\n */\n registerListener(type: SessionEventTypeFilter, listener: SessionEventListener): () => void {\n return this.bus.on(type, listener);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"agent-event-handler.js","names":[],"sources":["../../../../src/agent/orchestration/agent-event-handler.ts"],"sourcesContent":["/**\n * Agent Event Handler — coordinates listeners on pi-agent events.\n *\n * Previously a single god-handler with ten manager fields and one `switch`. Now a\n * thin façade around {@link SessionEventBus}: each concern (progress, lifecycle\n * hooks, tool-chain recording, error tracking, self-verify, etc.) registers its\n * own listener at construction time, and external code can add new listeners via\n * {@link AgentEventHandler.registerListener} without modifying this file (OCP).\n *\n * Ordering note: a few listeners are order-sensitive (notably the\n * `tool_execution_end` chain — `SystemReminder` mutates `event.result` in place\n * and downstream listeners read the mutated value). The `installX` calls below\n * preserve the exact order from the previous implementation.\n */\n\nimport type { AgentEvent } from '@earendil-works/pi-agent-core';\nimport type { SessionContext } from '../session/session-context.js';\nimport type { ProgressFeedbackManager } from '../lifecycle/progress.js';\nimport type { ToolErrorTracker } from '../tools/error-tracker.js';\nimport type { RequestLimiter } from '../models/request-limiter.js';\nimport type { LifecycleManager } from '../lifecycle/index.js';\nimport type { ToolChainTracker } from '../tools/chain-tracker.js';\nimport type { SelfVerifyMiddleware } from '../middleware/index.js';\nimport type { SystemReminder } from '../prompt/system-reminder.js';\nimport type { ToolUsageAnalyzer } from '../tools/usage-analyzer.js';\nimport type { ErrorPatternMatcher } from '../tools/error-pattern-matcher.js';\nimport { extractTextContent } from '../context/workspace.js';\nimport { createLogger } from '../../utils/logger.js';\n\nconst log = createLogger('AgentEventHandler');\n\n// ── Event bus ──────────────────────────────────────────────────────────────\n\nexport type SessionEventListener = (event: AgentEvent, context: SessionContext) => void;\nexport type SessionEventTypeFilter = AgentEvent['type'] | 'all';\n\n/**\n * Typed pub/sub for agent events. Listeners run in registration order; the bus\n * itself is synchronous — listeners that need async work must dispatch their own\n * promises (typically by awaiting and logging on error, like the lifecycle hooks).\n */\nexport class SessionEventBus {\n private readonly listeners = new Map<SessionEventTypeFilter, SessionEventListener[]>();\n\n on(type: SessionEventTypeFilter, listener: SessionEventListener): () => void {\n const bucket = this.listeners.get(type) ?? [];\n bucket.push(listener);\n this.listeners.set(type, bucket);\n return () => {\n const current = this.listeners.get(type);\n if (!current) return;\n const idx = current.indexOf(listener);\n if (idx >= 0) current.splice(idx, 1);\n };\n }\n\n dispatch(event: AgentEvent, context: SessionContext | null): void {\n if (!context) {\n if (event.type !== 'message_update') {\n log.warn(\n { eventType: event.type },\n `Agent event ignored (no SessionContext): ${event.type}`,\n );\n }\n return;\n }\n const exact = this.listeners.get(event.type);\n if (exact) {\n for (const listener of exact) {\n listener(event, context);\n }\n }\n const all = this.listeners.get('all');\n if (all) {\n for (const listener of all) {\n listener(event, context);\n }\n }\n }\n}\n\n// ── Built-in listener installers ───────────────────────────────────────────\n\nfunction installProgressListener(bus: SessionEventBus, progressManager: ProgressFeedbackManager): void {\n bus.on('agent_start', () => {\n log.debug('Agent turn started');\n progressManager.startTask();\n });\n bus.on('turn_start', () => {\n log.debug('Turn started');\n progressManager.onTurnStart();\n });\n bus.on('tool_execution_start', (event) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_start' }>;\n log.debug({ tool: e.toolName, args: e.args }, 'Tool execution started');\n progressManager.onToolStart(e.toolName, e.args || {});\n });\n bus.on('tool_execution_update', (event) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_update' }>;\n progressManager.onToolUpdate(e.toolName, e.partialResult);\n });\n bus.on('tool_execution_end', (event) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_end' }>;\n log.debug({ tool: e.toolName, isError: e.isError }, 'Tool execution complete');\n progressManager.onToolEnd(e.toolName, e.result, e.isError);\n });\n bus.on('agent_end', () => {\n progressManager.endTask();\n });\n}\n\nfunction installRequestLimitListener(\n bus: SessionEventBus,\n deps: {\n requestLimiter: RequestLimiter;\n progressManager: ProgressFeedbackManager;\n lifecycleManager: LifecycleManager;\n },\n): void {\n bus.on('agent_start', (_event, context) => {\n const result = deps.requestLimiter.recordRequest();\n\n deps.lifecycleManager\n .emit(\n 'llm_request',\n context.sessionKey,\n { requestNumber: result.count, maxRequests: result.limit },\n context,\n )\n .catch((err) => {\n const em = err instanceof Error ? err.message : String(err);\n log.warn(\n {\n err,\n errorMessage: em,\n sessionKey: context.sessionKey,\n requestNumber: result.count,\n maxRequests: result.limit,\n },\n `Lifecycle emit llm_request failed: ${em}`,\n );\n });\n\n deps.progressManager.onRequestLimitStatus(\n result.count,\n result.limit,\n result.remaining,\n result.isWarning,\n result.shouldStop,\n );\n\n if (result.shouldStop) {\n log.error(\n { count: result.count, limit: result.limit, sessionKey: context.sessionKey },\n `Request limit reached (${result.count}/${result.limit}) for session`,\n );\n }\n });\n\n bus.on('turn_end', () => {\n deps.requestLimiter.reset();\n });\n}\n\nfunction installLifecycleHookListener(bus: SessionEventBus, lifecycleManager: LifecycleManager): void {\n bus.on('message_end', (event, context) => {\n const e = event as Extract<AgentEvent, { type: 'message_end' }>;\n if (e.message?.role !== 'assistant') return;\n const content = e.message.content;\n const text = Array.isArray(content)\n ? extractTextContent(content as Array<{ type: string; text?: string }>)\n : String(content);\n log.debug({ contentLength: text.length }, 'Assistant response complete');\n\n lifecycleManager\n .emit(\n 'llm_response',\n context.sessionKey,\n { response: text, usage: (e.message as { usage?: unknown }).usage },\n context,\n )\n .catch((err) => {\n const em = err instanceof Error ? err.message : String(err);\n log.warn(\n { err, errorMessage: em, sessionKey: context.sessionKey, responseChars: text.length },\n `Lifecycle emit llm_response failed: ${em}`,\n );\n });\n });\n\n bus.on('tool_execution_start', (event, context) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_start' }>;\n lifecycleManager\n .emit(\n 'tool_call_start',\n context.sessionKey,\n {\n toolName: e.toolName,\n arguments: e.args || {},\n attemptNumber: 1,\n maxAttempts: 3,\n },\n context,\n )\n .catch((err) => {\n const em = err instanceof Error ? err.message : String(err);\n log.warn(\n { err, errorMessage: em, sessionKey: context.sessionKey, tool: e.toolName },\n `Lifecycle emit tool_call_start failed: ${em}`,\n );\n });\n });\n\n bus.on('tool_execution_end', (event, context) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_end' }>;\n const durationMs = (e as { durationMs?: number }).durationMs || 0;\n lifecycleManager\n .emit(\n 'tool_call_end',\n context.sessionKey,\n {\n toolName: e.toolName,\n success: !e.isError,\n result: e.result,\n error: e.isError ? String(e.result) : undefined,\n durationMs,\n },\n context,\n )\n .catch((err) => {\n const em = err instanceof Error ? err.message : String(err);\n log.warn(\n { err, errorMessage: em, sessionKey: context.sessionKey, tool: e.toolName },\n `Lifecycle hook tool_call_end failed: ${em}`,\n );\n });\n });\n}\n\nfunction installSystemReminderListener(bus: SessionEventBus, systemReminder: SystemReminder): void {\n // MUTATES `event.result` so downstream listeners (tool-chain, error-tracking) see\n // the decorated text. Must run AFTER progress + lifecycle hooks (they consume the\n // original) and BEFORE tool-chain + error-tracking.\n bus.on('tool_execution_end', (event) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_end' }> & { result: unknown };\n e.result = systemReminder.appendToResult(e.result, e.toolName);\n });\n}\n\nfunction appendTextToToolResult(result: unknown, text: string): unknown {\n if (!text.trim()) {\n return result;\n }\n if (result && typeof result === 'object') {\n const res = result as Record<string, unknown>;\n if (Array.isArray(res.content)) {\n res.content = [\n ...res.content,\n { type: 'text', text: `\\n${text}` },\n ];\n return res;\n }\n if (typeof res.content === 'string') {\n res.content = `${res.content}\\n${text}`;\n return res;\n }\n }\n return result;\n}\n\nfunction readRecord(value: unknown): Record<string, unknown> | null {\n return value && typeof value === 'object' ? (value as Record<string, unknown>) : null;\n}\n\nfunction readToolResultDetails(result: unknown): Record<string, unknown> | null {\n const rec = readRecord(result);\n if (!rec) return null;\n return readRecord(rec.details);\n}\n\nfunction readStringArray(value: unknown): string[] {\n return Array.isArray(value) ? value.filter((item): item is string => typeof item === 'string') : [];\n}\n\nfunction readChangedPathsFromToolEnd(toolName: string, args: unknown, result: unknown): string[] {\n const name = toolName.toLowerCase();\n const details = readToolResultDetails(result);\n\n if (name === 'apply_patch') {\n const files = readStringArray(details?.files);\n if (files.length > 0) return files;\n\n const changes = Array.isArray(details?.changes) ? details.changes : [];\n return changes.flatMap((change) => {\n const rec = readRecord(change);\n if (!rec) return [];\n return readStringArray([rec.moveTo, rec.path]);\n });\n }\n\n if (name === 'write_file') {\n if (typeof details?.size !== 'number') return [];\n const rec = readRecord(args);\n return typeof rec?.path === 'string' ? [rec.path] : [];\n }\n\n if (name.includes('write') || name.includes('edit')) {\n const rec = readRecord(args);\n return typeof rec?.path === 'string' ? [rec.path] : [];\n }\n\n return [];\n}\n\nfunction installToolChainListener(bus: SessionEventBus, toolChainTracker: ToolChainTracker): void {\n bus.on('turn_start', (_event, context) => {\n // One chain per LLM turn (pi-agent emits turn_start each round; turn_end clears the chain).\n toolChainTracker.startChain(context.sessionKey);\n });\n bus.on('tool_execution_start', (event, context) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_start' }>;\n toolChainTracker.recordCall(context.sessionKey, e.toolName, e.args || {}, 0);\n });\n bus.on('tool_execution_end', (event, context) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_end' }>;\n const durationMs = (e as { durationMs?: number }).durationMs || 0;\n const chain = toolChainTracker.getCurrentChain(context.sessionKey);\n if (!chain) return;\n const lastNode = chain.nodes[chain.nodes.length - 1];\n if (lastNode && lastNode.toolName === e.toolName) {\n toolChainTracker.recordResult(\n context.sessionKey,\n lastNode.id,\n e.result,\n e.isError ? 'Tool execution failed' : undefined,\n durationMs,\n );\n }\n });\n bus.on('turn_end', (_event, context) => {\n toolChainTracker.endChain(context.sessionKey);\n });\n}\n\nfunction installToolUsageListener(bus: SessionEventBus, toolUsageAnalyzer: ToolUsageAnalyzer): void {\n bus.on('tool_execution_end', (event) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_end' }>;\n const durationMs = (e as { durationMs?: number }).durationMs || 0;\n toolUsageAnalyzer.recordUsage(e.toolName, !e.isError, durationMs);\n });\n}\n\nfunction extractErrorText(result: unknown): string {\n if (typeof result === 'string') return result;\n if (result && typeof result === 'object') {\n const obj = result as Record<string, unknown>;\n if (typeof obj.error === 'string') return obj.error;\n if (typeof obj.message === 'string') return obj.message;\n return JSON.stringify(result);\n }\n return String(result);\n}\n\nfunction installErrorTrackingListener(\n bus: SessionEventBus,\n deps: { errorTracker: ToolErrorTracker; errorPatternMatcher: ErrorPatternMatcher },\n): void {\n bus.on('tool_execution_end', (event) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_end' }>;\n if (!e.isError) return;\n const errorText = extractErrorText(e.result);\n deps.errorTracker.recordFailure(e.toolName, errorText);\n\n const errorMatch = deps.errorPatternMatcher.matchError(errorText);\n if (errorMatch.matched && errorMatch.pattern) {\n const preview = errorText.length > 120 ? `${errorText.slice(0, 120)}…` : errorText;\n log.warn(\n { tool: e.toolName, pattern: errorMatch.pattern.name, errorPreview: preview },\n `Tool error matched pattern \"${errorMatch.pattern.name}\" (${e.toolName}): ${preview}`,\n );\n }\n });\n bus.on('turn_end', () => {\n deps.errorTracker.reset();\n });\n}\n\nfunction installSelfVerifyListener(bus: SessionEventBus, selfVerifyMiddleware: SelfVerifyMiddleware): void {\n bus.on('tool_execution_end', (event, context) => {\n const e = event as Extract<AgentEvent, { type: 'tool_execution_end' }> & { result: unknown };\n const name = e.toolName?.toLowerCase() ?? '';\n const args = (e as { args?: unknown }).args;\n\n if (name === 'exec_command') {\n selfVerifyMiddleware.recordVerification(e.toolName, args, {\n isError: e.isError,\n result: e.result,\n }, context.sessionKey);\n return;\n }\n\n if (!e.isError) {\n const changedPaths = readChangedPathsFromToolEnd(e.toolName, args, e.result);\n for (const path of changedPaths) {\n selfVerifyMiddleware.recordEdit(\n path,\n name.includes('write') ? 'write' : 'edit',\n context.sessionKey,\n );\n }\n if (changedPaths.length > 0) {\n e.result = appendTextToToolResult(\n e.result,\n selfVerifyMiddleware.consumePostEditReminder(context.sessionKey),\n );\n return;\n }\n selfVerifyMiddleware.recordVerification(e.toolName, args, {\n isError: e.isError,\n result: e.result,\n }, context.sessionKey);\n }\n });\n bus.on('turn_start', (_event, context) => {\n selfVerifyMiddleware.onTurnStart(context.sessionKey);\n });\n}\n\n// ── Public façade ───────────────────────────────────────────────────────────\n\nexport interface AgentEventHandlerConfig {\n progressManager: ProgressFeedbackManager;\n errorTracker: ToolErrorTracker;\n requestLimiter: RequestLimiter;\n lifecycleManager: LifecycleManager;\n toolChainTracker: ToolChainTracker;\n selfVerifyMiddleware: SelfVerifyMiddleware;\n systemReminder: SystemReminder;\n toolUsageAnalyzer: ToolUsageAnalyzer;\n errorPatternMatcher: ErrorPatternMatcher;\n}\n\n/**\n * Thin façade over {@link SessionEventBus} that pre-registers all built-in\n * listeners in their required order. `handle(event, ctx)` is the entry point\n * used by `AgentService.handleSessionEvent`; extensions can hook in via\n * {@link registerListener} without touching this class.\n */\nexport class AgentEventHandler {\n private readonly bus: SessionEventBus;\n\n constructor(config: AgentEventHandlerConfig) {\n this.bus = new SessionEventBus();\n\n // Order matters for `tool_execution_end`: SystemReminder mutates `event.result`\n // and ToolChain + ErrorTracking depend on the mutated value. Keep these in this\n // exact sequence.\n installProgressListener(this.bus, config.progressManager);\n installRequestLimitListener(this.bus, {\n requestLimiter: config.requestLimiter,\n progressManager: config.progressManager,\n lifecycleManager: config.lifecycleManager,\n });\n installLifecycleHookListener(this.bus, config.lifecycleManager);\n installSystemReminderListener(this.bus, config.systemReminder);\n installSelfVerifyListener(this.bus, config.selfVerifyMiddleware);\n installToolUsageListener(this.bus, config.toolUsageAnalyzer);\n installToolChainListener(this.bus, config.toolChainTracker);\n installErrorTrackingListener(this.bus, {\n errorTracker: config.errorTracker,\n errorPatternMatcher: config.errorPatternMatcher,\n });\n }\n\n /** Dispatch a pi-agent event to all registered listeners. */\n handle(event: AgentEvent, context: SessionContext | null): void {\n this.bus.dispatch(event, context);\n }\n\n /**\n * Register an additional listener (e.g. from an extension). Returns an\n * unsubscribe function. Listeners run after the built-ins in registration\n * order; if you need to run before a built-in, you must create your own\n * {@link SessionEventBus} instance.\n */\n registerListener(type: SessionEventTypeFilter, listener: SessionEventListener): () => void {\n return this.bus.on(type, listener);\n }\n}\n"],"mappings":"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"}
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
const CORE_TOOL_ORDER = [
|
|
3
3
|
"read_file",
|
|
4
4
|
"write_file",
|
|
5
|
-
"
|
|
5
|
+
"apply_patch",
|
|
6
6
|
"grep",
|
|
7
7
|
"find",
|
|
8
8
|
"list_dir",
|
|
9
|
-
"
|
|
9
|
+
"exec_command",
|
|
10
|
+
"update_plan",
|
|
10
11
|
"web_search",
|
|
11
12
|
"web_fetch",
|
|
12
13
|
"web_extract",
|
|
@@ -34,13 +35,14 @@ const CORE_TOOL_ORDER = [
|
|
|
34
35
|
"execute_code"
|
|
35
36
|
];
|
|
36
37
|
const CORE_TOOL_SUMMARIES = {
|
|
37
|
-
read_file: "Read targeted file contents
|
|
38
|
-
write_file: "Create new files or intentional complete rewrites",
|
|
39
|
-
|
|
38
|
+
read_file: "Read targeted file contents before editing",
|
|
39
|
+
write_file: "Create new files or intentional complete rewrites; prefer apply_patch for code changes",
|
|
40
|
+
apply_patch: "Apply strict patches for source edits",
|
|
40
41
|
grep: "Search file contents for literals, errors, config values, and docs",
|
|
41
42
|
find: "Find files by glob pattern",
|
|
42
43
|
list_dir: "List directory contents",
|
|
43
|
-
|
|
44
|
+
exec_command: "Run tests, type checks, builds, package scripts, and safe inspection commands",
|
|
45
|
+
update_plan: "Keep the current multi-step coding plan visible and accurate",
|
|
44
46
|
web_search: "Search the web",
|
|
45
47
|
web_fetch: "Fetch and extract readable content from a URL",
|
|
46
48
|
web_extract: "Extract structured content from a URL",
|
|
@@ -94,9 +96,10 @@ function buildToolingSection(params) {
|
|
|
94
96
|
const name = resolveToolName(tool);
|
|
95
97
|
toolLines.push(summary ? `- ${name}: ${summary}` : `- ${name}`);
|
|
96
98
|
}
|
|
97
|
-
const
|
|
99
|
+
const execToolName = resolveToolName("exec_command");
|
|
100
|
+
const planToolName = resolveToolName("update_plan");
|
|
98
101
|
const readToolName = resolveToolName("read_file");
|
|
99
|
-
const
|
|
102
|
+
const patchToolName = resolveToolName("apply_patch");
|
|
100
103
|
const writeToolName = resolveToolName("write_file");
|
|
101
104
|
const grepToolName = resolveToolName("grep");
|
|
102
105
|
const findToolName = resolveToolName("find");
|
|
@@ -104,11 +107,12 @@ function buildToolingSection(params) {
|
|
|
104
107
|
const hasWorkflow = availableTools.has("workflow");
|
|
105
108
|
const hasToolManual = availableTools.has("tool_manual");
|
|
106
109
|
const hasRead = availableTools.has("read_file");
|
|
107
|
-
const
|
|
110
|
+
const hasPatch = availableTools.has("apply_patch");
|
|
108
111
|
const hasWrite = availableTools.has("write_file");
|
|
109
112
|
const hasGrep = availableTools.has("grep");
|
|
110
113
|
const hasFind = availableTools.has("find");
|
|
111
|
-
const
|
|
114
|
+
const hasExec = availableTools.has("exec_command");
|
|
115
|
+
const hasPlan = availableTools.has("update_plan");
|
|
112
116
|
const orchestrationLines = [];
|
|
113
117
|
if (hasDelegate) orchestrationLines.push("- Sub-agent delegation → use `delegate_task(goal, context?, toolset?)` for focused parallel work; results return to you automatically.");
|
|
114
118
|
if (hasWorkflow) orchestrationLines.push("- Multi-phase workflows → use `workflow` to start persisted runs with subagents when a script defines phases.");
|
|
@@ -117,12 +121,13 @@ function buildToolingSection(params) {
|
|
|
117
121
|
"Tool availability (filtered by policy):",
|
|
118
122
|
"Tool names are case-sensitive. Call tools exactly as listed.",
|
|
119
123
|
toolLines.length > 0 ? toolLines.join("\n") : "- No tools are registered for this session.",
|
|
120
|
-
`For long waits, avoid rapid poll loops: use \`${
|
|
124
|
+
`For long waits, avoid rapid poll loops: use \`${execToolName}\` with a sufficient timeout or poll in reasonable intervals.`,
|
|
121
125
|
hasRead ? `Use \`${readToolName}\` for targeted file inspection before editing; do not rely on filenames alone.` : "",
|
|
122
|
-
|
|
126
|
+
hasPatch ? `Use \`${patchToolName}\` for source changes; keep patches focused and inspect failures carefully.` : "",
|
|
123
127
|
hasWrite ? `Use \`${writeToolName}\` only for new files or intentional complete rewrites.` : "",
|
|
124
128
|
hasGrep || hasFind ? `Use ${[hasGrep ? `\`${grepToolName}\`` : "", hasFind ? `\`${findToolName}\`` : ""].filter(Boolean).join(" and ")} for text/file discovery; use code graph or symbol tools when available for definitions and call relationships.` : "",
|
|
125
|
-
|
|
129
|
+
hasExec ? `Use \`${execToolName}\` for verification and safe inspection, not routine file editing.` : "",
|
|
130
|
+
hasPlan ? `Use \`${planToolName}\` for multi-step coding work: keep exactly one active step when work is in progress, and update it after meaningful progress or review.` : "",
|
|
126
131
|
hasToolManual ? "Some complex tools have built-in manuals. Use `tool_manual(tool)` before non-trivial use." : "",
|
|
127
132
|
hasDelegate ? "If a task is complex or long-running, delegate it. Completion is push-based: the sub-agent result returns when done." : "",
|
|
128
133
|
...orchestrationLines,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tooling.js","names":[],"sources":["../../../../../src/agent/prompt/sections/tooling.ts"],"sourcesContent":["const CORE_TOOL_ORDER = [\n 'read_file',\n 'write_file',\n 'edit_file',\n 'grep',\n 'find',\n 'list_dir',\n 'shell',\n 'web_search',\n 'web_fetch',\n 'web_extract',\n 'browser_use',\n 'send_message',\n 'send_media',\n 'memory_search',\n 'memory_get',\n 'session_search',\n 'curated_memory',\n 'session_status',\n 'tool_manual',\n 'delegate_task',\n 'workflow',\n 'automation',\n 'skills_list',\n 'skill_view',\n 'skill_manage',\n 'todo',\n 'clarify',\n 'text_to_speech',\n 'image',\n 'image_generate',\n 'create_share',\n 'execute_code',\n] as const;\n\nconst CORE_TOOL_SUMMARIES: Record<string, string> = {\n read_file: 'Read targeted file contents; prefer this over shell cat/sed for inspection',\n write_file: 'Create new files or intentional complete rewrites',\n edit_file: 'Make precise, minimal edits to existing files',\n grep: 'Search file contents for literals, errors, config values, and docs',\n find: 'Find files by glob pattern',\n list_dir: 'List directory contents',\n shell: 'Run tests, type checks, builds, package scripts, and safe inspection commands',\n web_search: 'Search the web',\n web_fetch: 'Fetch and extract readable content from a URL',\n web_extract: 'Extract structured content from a URL',\n browser_use: 'Control the configured browser',\n send_message: 'Send messages to the current channel',\n send_media: 'Send media attachments to the current channel',\n memory_search: 'Semantic search over indexed memory sources',\n memory_get: 'Read specific lines from memory sources returned by search',\n session_search: 'Search other chat sessions or list recent sessions',\n curated_memory: 'Read/write structured notes under agent home memories/',\n session_status: 'Show session usage/time/model state',\n tool_manual: 'Load built-in usage manuals for complex tools',\n delegate_task: 'Spawn an isolated sub-agent for a delegated task',\n workflow: 'Start a persisted multi-phase workflow run',\n automation: 'Manage product automations',\n skills_list: 'List available skills',\n skill_view: 'Load a skill SKILL.md or sub-documents',\n skill_manage: 'Create or update skills on disk',\n todo: 'Manage in-session todo items',\n clarify: 'Ask the user a blocking clarification question',\n text_to_speech: 'Send a standalone voice message',\n image: 'Analyze an image with the configured vision model',\n image_generate: 'Generate images with the configured image model',\n create_share: 'Create a shareable link for workspace files',\n execute_code: 'Run code in a sandbox with a restricted tool subset',\n};\n\nexport function buildToolingSection(params: {\n toolNames?: string[];\n toolSummaries?: Record<string, string>;\n}): string {\n const rawToolNames = (params.toolNames ?? []).map((tool) => tool.trim()).filter(Boolean);\n const canonicalByNormalized = new Map<string, string>();\n for (const name of rawToolNames) {\n const normalized = name.toLowerCase();\n if (!canonicalByNormalized.has(normalized)) {\n canonicalByNormalized.set(normalized, name);\n }\n }\n const resolveToolName = (normalized: string) =>\n canonicalByNormalized.get(normalized) ?? normalized;\n\n const normalizedTools = rawToolNames.map((tool) => tool.toLowerCase());\n const availableTools = new Set(normalizedTools);\n\n const externalToolSummaries = new Map<string, string>();\n for (const [key, value] of Object.entries(params.toolSummaries ?? {})) {\n const normalized = key.trim().toLowerCase();\n if (!normalized || !value?.trim()) {\n continue;\n }\n externalToolSummaries.set(normalized, value.trim());\n }\n\n const extraTools = Array.from(\n new Set(normalizedTools.filter((tool) => !CORE_TOOL_ORDER.includes(tool as (typeof CORE_TOOL_ORDER)[number]))),\n );\n const enabledTools = CORE_TOOL_ORDER.filter((tool) => availableTools.has(tool));\n const toolLines = enabledTools.map((tool) => {\n const summary = CORE_TOOL_SUMMARIES[tool] ?? externalToolSummaries.get(tool);\n const name = resolveToolName(tool);\n return summary ? `- ${name}: ${summary}` : `- ${name}`;\n });\n for (const tool of extraTools.toSorted()) {\n const summary = CORE_TOOL_SUMMARIES[tool] ?? externalToolSummaries.get(tool);\n const name = resolveToolName(tool);\n toolLines.push(summary ? `- ${name}: ${summary}` : `- ${name}`);\n }\n\n const shellToolName = resolveToolName('shell');\n const readToolName = resolveToolName('read_file');\n const editToolName = resolveToolName('edit_file');\n const writeToolName = resolveToolName('write_file');\n const grepToolName = resolveToolName('grep');\n const findToolName = resolveToolName('find');\n const hasDelegate = availableTools.has('delegate_task');\n const hasWorkflow = availableTools.has('workflow');\n const hasToolManual = availableTools.has('tool_manual');\n const hasRead = availableTools.has('read_file');\n const hasEdit = availableTools.has('edit_file');\n const hasWrite = availableTools.has('write_file');\n const hasGrep = availableTools.has('grep');\n const hasFind = availableTools.has('find');\n const hasShell = availableTools.has('shell');\n\n const orchestrationLines: string[] = [];\n if (hasDelegate) {\n orchestrationLines.push(\n '- Sub-agent delegation → use `delegate_task(goal, context?, toolset?)` for focused parallel work; results return to you automatically.',\n );\n }\n if (hasWorkflow) {\n orchestrationLines.push(\n '- Multi-phase workflows → use `workflow` to start persisted runs with subagents when a script defines phases.',\n );\n }\n\n return [\n '## Tooling',\n 'Tool availability (filtered by policy):',\n 'Tool names are case-sensitive. Call tools exactly as listed.',\n toolLines.length > 0\n ? toolLines.join('\\n')\n : '- No tools are registered for this session.',\n `For long waits, avoid rapid poll loops: use \\`${shellToolName}\\` with enough yieldMs or poll in reasonable intervals.`,\n hasRead\n ? `Use \\`${readToolName}\\` for targeted file inspection before editing; do not rely on filenames alone.`\n : '',\n hasEdit\n ? `Use \\`${editToolName}\\` for small source changes; keep patches focused.`\n : '',\n hasWrite\n ? `Use \\`${writeToolName}\\` only for new files or intentional complete rewrites.`\n : '',\n hasGrep || hasFind\n ? `Use ${[\n hasGrep ? `\\`${grepToolName}\\`` : '',\n hasFind ? `\\`${findToolName}\\`` : '',\n ].filter(Boolean).join(' and ')} for text/file discovery; use code graph or symbol tools when available for definitions and call relationships.`\n : '',\n hasShell\n ? `Use \\`${shellToolName}\\` for verification and safe inspection, not routine file editing.`\n : '',\n hasToolManual\n ? 'Some complex tools have built-in manuals. Use `tool_manual(tool)` before non-trivial use.'\n : '',\n hasDelegate\n ? 'If a task is complex or long-running, delegate it. Completion is push-based: the sub-agent result returns when done.'\n : '',\n ...orchestrationLines,\n 'Do not poll delegate/workflow status in a tight loop; check on-demand or wait for completion.',\n ]\n .filter(Boolean)\n .join('\\n');\n}\n\nexport function hasSkillsTools(toolNames: string[]): boolean {\n const normalized = new Set(toolNames.map((t) => t.toLowerCase()));\n return normalized.has('skills_list') || normalized.has('skill_view');\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tooling.js","names":[],"sources":["../../../../../src/agent/prompt/sections/tooling.ts"],"sourcesContent":["const CORE_TOOL_ORDER = [\n 'read_file',\n 'write_file',\n 'apply_patch',\n 'grep',\n 'find',\n 'list_dir',\n 'exec_command',\n 'update_plan',\n 'web_search',\n 'web_fetch',\n 'web_extract',\n 'browser_use',\n 'send_message',\n 'send_media',\n 'memory_search',\n 'memory_get',\n 'session_search',\n 'curated_memory',\n 'session_status',\n 'tool_manual',\n 'delegate_task',\n 'workflow',\n 'automation',\n 'skills_list',\n 'skill_view',\n 'skill_manage',\n 'todo',\n 'clarify',\n 'text_to_speech',\n 'image',\n 'image_generate',\n 'create_share',\n 'execute_code',\n] as const;\n\nconst CORE_TOOL_SUMMARIES: Record<string, string> = {\n read_file: 'Read targeted file contents before editing',\n write_file: 'Create new files or intentional complete rewrites; prefer apply_patch for code changes',\n apply_patch: 'Apply strict patches for source edits',\n grep: 'Search file contents for literals, errors, config values, and docs',\n find: 'Find files by glob pattern',\n list_dir: 'List directory contents',\n exec_command: 'Run tests, type checks, builds, package scripts, and safe inspection commands',\n update_plan: 'Keep the current multi-step coding plan visible and accurate',\n web_search: 'Search the web',\n web_fetch: 'Fetch and extract readable content from a URL',\n web_extract: 'Extract structured content from a URL',\n browser_use: 'Control the configured browser',\n send_message: 'Send messages to the current channel',\n send_media: 'Send media attachments to the current channel',\n memory_search: 'Semantic search over indexed memory sources',\n memory_get: 'Read specific lines from memory sources returned by search',\n session_search: 'Search other chat sessions or list recent sessions',\n curated_memory: 'Read/write structured notes under agent home memories/',\n session_status: 'Show session usage/time/model state',\n tool_manual: 'Load built-in usage manuals for complex tools',\n delegate_task: 'Spawn an isolated sub-agent for a delegated task',\n workflow: 'Start a persisted multi-phase workflow run',\n automation: 'Manage product automations',\n skills_list: 'List available skills',\n skill_view: 'Load a skill SKILL.md or sub-documents',\n skill_manage: 'Create or update skills on disk',\n todo: 'Manage in-session todo items',\n clarify: 'Ask the user a blocking clarification question',\n text_to_speech: 'Send a standalone voice message',\n image: 'Analyze an image with the configured vision model',\n image_generate: 'Generate images with the configured image model',\n create_share: 'Create a shareable link for workspace files',\n execute_code: 'Run code in a sandbox with a restricted tool subset',\n};\n\nexport function buildToolingSection(params: {\n toolNames?: string[];\n toolSummaries?: Record<string, string>;\n}): string {\n const rawToolNames = (params.toolNames ?? []).map((tool) => tool.trim()).filter(Boolean);\n const canonicalByNormalized = new Map<string, string>();\n for (const name of rawToolNames) {\n const normalized = name.toLowerCase();\n if (!canonicalByNormalized.has(normalized)) {\n canonicalByNormalized.set(normalized, name);\n }\n }\n const resolveToolName = (normalized: string) =>\n canonicalByNormalized.get(normalized) ?? normalized;\n\n const normalizedTools = rawToolNames.map((tool) => tool.toLowerCase());\n const availableTools = new Set(normalizedTools);\n\n const externalToolSummaries = new Map<string, string>();\n for (const [key, value] of Object.entries(params.toolSummaries ?? {})) {\n const normalized = key.trim().toLowerCase();\n if (!normalized || !value?.trim()) {\n continue;\n }\n externalToolSummaries.set(normalized, value.trim());\n }\n\n const extraTools = Array.from(\n new Set(normalizedTools.filter((tool) => !CORE_TOOL_ORDER.includes(tool as (typeof CORE_TOOL_ORDER)[number]))),\n );\n const enabledTools = CORE_TOOL_ORDER.filter((tool) => availableTools.has(tool));\n const toolLines = enabledTools.map((tool) => {\n const summary = CORE_TOOL_SUMMARIES[tool] ?? externalToolSummaries.get(tool);\n const name = resolveToolName(tool);\n return summary ? `- ${name}: ${summary}` : `- ${name}`;\n });\n for (const tool of extraTools.toSorted()) {\n const summary = CORE_TOOL_SUMMARIES[tool] ?? externalToolSummaries.get(tool);\n const name = resolveToolName(tool);\n toolLines.push(summary ? `- ${name}: ${summary}` : `- ${name}`);\n }\n\n const execToolName = resolveToolName('exec_command');\n const planToolName = resolveToolName('update_plan');\n const readToolName = resolveToolName('read_file');\n const patchToolName = resolveToolName('apply_patch');\n const writeToolName = resolveToolName('write_file');\n const grepToolName = resolveToolName('grep');\n const findToolName = resolveToolName('find');\n const hasDelegate = availableTools.has('delegate_task');\n const hasWorkflow = availableTools.has('workflow');\n const hasToolManual = availableTools.has('tool_manual');\n const hasRead = availableTools.has('read_file');\n const hasPatch = availableTools.has('apply_patch');\n const hasWrite = availableTools.has('write_file');\n const hasGrep = availableTools.has('grep');\n const hasFind = availableTools.has('find');\n const hasExec = availableTools.has('exec_command');\n const hasPlan = availableTools.has('update_plan');\n\n const orchestrationLines: string[] = [];\n if (hasDelegate) {\n orchestrationLines.push(\n '- Sub-agent delegation → use `delegate_task(goal, context?, toolset?)` for focused parallel work; results return to you automatically.',\n );\n }\n if (hasWorkflow) {\n orchestrationLines.push(\n '- Multi-phase workflows → use `workflow` to start persisted runs with subagents when a script defines phases.',\n );\n }\n\n return [\n '## Tooling',\n 'Tool availability (filtered by policy):',\n 'Tool names are case-sensitive. Call tools exactly as listed.',\n toolLines.length > 0\n ? toolLines.join('\\n')\n : '- No tools are registered for this session.',\n `For long waits, avoid rapid poll loops: use \\`${execToolName}\\` with a sufficient timeout or poll in reasonable intervals.`,\n hasRead\n ? `Use \\`${readToolName}\\` for targeted file inspection before editing; do not rely on filenames alone.`\n : '',\n hasPatch\n ? `Use \\`${patchToolName}\\` for source changes; keep patches focused and inspect failures carefully.`\n : '',\n hasWrite\n ? `Use \\`${writeToolName}\\` only for new files or intentional complete rewrites.`\n : '',\n hasGrep || hasFind\n ? `Use ${[\n hasGrep ? `\\`${grepToolName}\\`` : '',\n hasFind ? `\\`${findToolName}\\`` : '',\n ].filter(Boolean).join(' and ')} for text/file discovery; use code graph or symbol tools when available for definitions and call relationships.`\n : '',\n hasExec\n ? `Use \\`${execToolName}\\` for verification and safe inspection, not routine file editing.`\n : '',\n hasPlan\n ? `Use \\`${planToolName}\\` for multi-step coding work: keep exactly one active step when work is in progress, and update it after meaningful progress or review.`\n : '',\n hasToolManual\n ? 'Some complex tools have built-in manuals. Use `tool_manual(tool)` before non-trivial use.'\n : '',\n hasDelegate\n ? 'If a task is complex or long-running, delegate it. Completion is push-based: the sub-agent result returns when done.'\n : '',\n ...orchestrationLines,\n 'Do not poll delegate/workflow status in a tight loop; check on-demand or wait for completion.',\n ]\n .filter(Boolean)\n .join('\\n');\n}\n\nexport function hasSkillsTools(toolNames: string[]): boolean {\n const normalized = new Set(toolNames.map((t) => t.toLowerCase()));\n return normalized.has('skills_list') || normalized.has('skill_view');\n}\n"],"mappings":"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"}
|
|
@@ -3,9 +3,9 @@ const DEFAULT_CONFIG = {
|
|
|
3
3
|
enabled: true,
|
|
4
4
|
defaultReminders: [],
|
|
5
5
|
contextualReminders: {
|
|
6
|
-
"file.write": ["
|
|
6
|
+
"file.write": ["Tip: apply_patch > write_file for small changes"],
|
|
7
7
|
"file.delete": ["⚠️ Confirming: file deletion"],
|
|
8
|
-
|
|
8
|
+
exec_command: ["Command: use non-interactive commands with clear timeouts"],
|
|
9
9
|
"web.fetch": ["🌐 Web: check for rate limits on repeated calls"]
|
|
10
10
|
},
|
|
11
11
|
appendToToolResults: true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"system-reminder.js","names":[],"sources":["../../../../src/agent/prompt/system-reminder.ts"],"sourcesContent":["/**\n * System Reminder - Contextual tips after tool execution\n * \n * Based on Agent Harness theory: \"Fixed system reminders appended after \n * tool execution are more effective than instructions in System Prompt alone\"\n * \n * Optimized: Contextual reminders instead of fixed spam\n */\n\nexport interface SystemReminderConfig {\n enabled: boolean;\n defaultReminders: string[];\n contextualReminders: Record<string, string[]>;\n appendToToolResults: boolean;\n maxRemindersPerTurn: number;\n}\n\n// Default minimal reminders (only essentials)\nconst DEFAULT_REMINDERS: string[] = [];\n\n// Contextual reminders based on tool type\nconst CONTEXTUAL_REMINDERS: Record<string, string[]> = {\n 'file.write': ['
|
|
1
|
+
{"version":3,"file":"system-reminder.js","names":[],"sources":["../../../../src/agent/prompt/system-reminder.ts"],"sourcesContent":["/**\n * System Reminder - Contextual tips after tool execution\n * \n * Based on Agent Harness theory: \"Fixed system reminders appended after \n * tool execution are more effective than instructions in System Prompt alone\"\n * \n * Optimized: Contextual reminders instead of fixed spam\n */\n\nexport interface SystemReminderConfig {\n enabled: boolean;\n defaultReminders: string[];\n contextualReminders: Record<string, string[]>;\n appendToToolResults: boolean;\n maxRemindersPerTurn: number;\n}\n\n// Default minimal reminders (only essentials)\nconst DEFAULT_REMINDERS: string[] = [];\n\n// Contextual reminders based on tool type\nconst CONTEXTUAL_REMINDERS: Record<string, string[]> = {\n 'file.write': ['Tip: apply_patch > write_file for small changes'],\n 'file.delete': ['⚠️ Confirming: file deletion'],\n exec_command: ['Command: use non-interactive commands with clear timeouts'],\n 'web.fetch': ['🌐 Web: check for rate limits on repeated calls'],\n};\n\nconst DEFAULT_CONFIG: SystemReminderConfig = {\n enabled: true,\n defaultReminders: DEFAULT_REMINDERS,\n contextualReminders: CONTEXTUAL_REMINDERS,\n appendToToolResults: true,\n maxRemindersPerTurn: 2, // Reduced from 3\n};\n\nexport class SystemReminder {\n private config: SystemReminderConfig;\n private reminderCountThisTurn = 0;\n\n constructor(config: Partial<SystemReminderConfig> = {}) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n }\n\n /**\n * Append default reminders (when configured and under per-turn cap).\n */\n getReminderText(): string {\n if (!this.config.enabled) {\n return '';\n }\n\n if (this.reminderCountThisTurn >= this.config.maxRemindersPerTurn) {\n return ''; // Don't spam too many reminders\n }\n\n if (this.config.defaultReminders.length === 0) {\n return '';\n }\n\n this.reminderCountThisTurn++;\n return this.config.defaultReminders.join('\\n');\n }\n\n /**\n * Get contextual reminder based on tool name\n */\n getContextualReminder(toolName: string): string {\n if (!this.config.enabled) {\n return '';\n }\n\n if (this.reminderCountThisTurn >= this.config.maxRemindersPerTurn) {\n return '';\n }\n\n const reminders = this.config.contextualReminders[toolName];\n if (!reminders || reminders.length === 0) {\n return '';\n }\n\n this.reminderCountThisTurn++;\n // Pick first matching reminder (could randomize if multiple)\n return reminders[0];\n }\n\n /**\n * Append reminder to tool result content\n */\n appendToResult(result: unknown, toolName?: string): unknown {\n if (!this.config.enabled || !this.config.appendToToolResults) {\n return result;\n }\n\n // Try contextual reminder first, fall back to default\n const reminderText = toolName \n ? this.getContextualReminder(toolName) \n : this.getReminderText();\n \n if (!reminderText) {\n return result;\n }\n\n // Handle different result formats\n if (result && typeof result === 'object') {\n const res = result as Record<string, unknown>;\n \n if (res.content && Array.isArray(res.content)) {\n // Append to content array\n res.content = [\n ...res.content,\n { type: 'text', text: `\\n${reminderText}` },\n ];\n } else if (typeof res.content === 'string') {\n // Append to string content\n res.content = `${res.content}\\n${reminderText}`;\n }\n \n return res;\n }\n\n return result;\n }\n\n /**\n * Reset turn counter (called at end of turn)\n */\n resetTurn(): void {\n this.reminderCountThisTurn = 0;\n }\n\n /**\n * Update configuration\n */\n setConfig(config: Partial<SystemReminderConfig>): void {\n this.config = { ...this.config, ...config };\n }\n\n /**\n * Get current config\n */\n getConfig(): SystemReminderConfig {\n return { ...this.config };\n }\n}\n"],"mappings":";AA4BA,MAAM,iBAAuC;CAC3C,SAAS;CACT,kBAAkB,EAAiB;CACnC,qBAAqB;EATrB,cAAc,CAAC,kDAAkD;EACjE,eAAe,CAAC,+BAA+B;EAC/C,cAAc,CAAC,4DAA4D;EAC3E,aAAa,CAAC,kDAAkD;EAMvB;CACzC,qBAAqB;CACrB,qBAAqB;CACtB;AAED,IAAa,iBAAb,MAA4B;CAC1B;CACA,wBAAgC;CAEhC,YAAY,SAAwC,EAAE,EAAE;AACtD,OAAK,SAAS;GAAE,GAAG;GAAgB,GAAG;GAAQ;;;;;CAMhD,kBAA0B;AACxB,MAAI,CAAC,KAAK,OAAO,QACf,QAAO;AAGT,MAAI,KAAK,yBAAyB,KAAK,OAAO,oBAC5C,QAAO;AAGT,MAAI,KAAK,OAAO,iBAAiB,WAAW,EAC1C,QAAO;AAGT,OAAK;AACL,SAAO,KAAK,OAAO,iBAAiB,KAAK,KAAK;;;;;CAMhD,sBAAsB,UAA0B;AAC9C,MAAI,CAAC,KAAK,OAAO,QACf,QAAO;AAGT,MAAI,KAAK,yBAAyB,KAAK,OAAO,oBAC5C,QAAO;EAGT,MAAM,YAAY,KAAK,OAAO,oBAAoB;AAClD,MAAI,CAAC,aAAa,UAAU,WAAW,EACrC,QAAO;AAGT,OAAK;AAEL,SAAO,UAAU;;;;;CAMnB,eAAe,QAAiB,UAA4B;AAC1D,MAAI,CAAC,KAAK,OAAO,WAAW,CAAC,KAAK,OAAO,oBACvC,QAAO;EAIT,MAAM,eAAe,WACjB,KAAK,sBAAsB,SAAS,GACpC,KAAK,iBAAiB;AAE1B,MAAI,CAAC,aACH,QAAO;AAIT,MAAI,UAAU,OAAO,WAAW,UAAU;GACxC,MAAM,MAAM;AAEZ,OAAI,IAAI,WAAW,MAAM,QAAQ,IAAI,QAAQ,CAE3C,KAAI,UAAU,CACZ,GAAG,IAAI,SACP;IAAE,MAAM;IAAQ,MAAM,KAAK;IAAgB,CAC5C;YACQ,OAAO,IAAI,YAAY,SAEhC,KAAI,UAAU,GAAG,IAAI,QAAQ,IAAI;AAGnC,UAAO;;AAGT,SAAO;;;;;CAMT,YAAkB;AAChB,OAAK,wBAAwB;;;;;CAM/B,UAAU,QAA6C;AACrD,OAAK,SAAS;GAAE,GAAG,KAAK;GAAQ,GAAG;GAAQ;;;;;CAM7C,YAAkC;AAChC,SAAO,EAAE,GAAG,KAAK,QAAQ"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Filters process environment before passing it to child processes (e.g.
|
|
2
|
+
* Filters process environment before passing it to child processes (e.g. exec_command)
|
|
3
3
|
* to avoid leaking API keys and other credentials.
|
|
4
4
|
*/
|
|
5
5
|
export interface SanitizedEnvVars {
|
|
@@ -12,7 +12,7 @@ export declare function sanitizeEnvVars(env: Record<string, string | undefined>,
|
|
|
12
12
|
customBlocked?: RegExp[];
|
|
13
13
|
}): SanitizedEnvVars;
|
|
14
14
|
/**
|
|
15
|
-
* Environment for agent
|
|
15
|
+
* Environment for agent command execution: strips secrets, keeps normal tooling vars.
|
|
16
16
|
*/
|
|
17
17
|
export declare function prepareSafeToolEnv(baseEnv: NodeJS.ProcessEnv | Record<string, string | undefined>, options?: {
|
|
18
18
|
allowedVars?: string[];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
//#region src/agent/sandbox/sanitize-env-vars.ts
|
|
2
2
|
/**
|
|
3
|
-
* Filters process environment before passing it to child processes (e.g.
|
|
3
|
+
* Filters process environment before passing it to child processes (e.g. exec_command)
|
|
4
4
|
* to avoid leaking API keys and other credentials.
|
|
5
5
|
*/
|
|
6
6
|
const BLOCKED_ENV_VAR_PATTERNS = [
|
|
@@ -57,7 +57,7 @@ function sanitizeEnvVars(env, options) {
|
|
|
57
57
|
};
|
|
58
58
|
}
|
|
59
59
|
/**
|
|
60
|
-
* Environment for agent
|
|
60
|
+
* Environment for agent command execution: strips secrets, keeps normal tooling vars.
|
|
61
61
|
*/
|
|
62
62
|
function prepareSafeToolEnv(baseEnv, options) {
|
|
63
63
|
const { safe } = sanitizeEnvVars(baseEnv, options);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sanitize-env-vars.js","names":[],"sources":["../../../../src/agent/sandbox/sanitize-env-vars.ts"],"sourcesContent":["/**\n * Filters process environment before passing it to child processes (e.g.
|
|
1
|
+
{"version":3,"file":"sanitize-env-vars.js","names":[],"sources":["../../../../src/agent/sandbox/sanitize-env-vars.ts"],"sourcesContent":["/**\n * Filters process environment before passing it to child processes (e.g. exec_command)\n * to avoid leaking API keys and other credentials.\n */\n\nconst BLOCKED_ENV_VAR_PATTERNS: RegExp[] = [\n /^ANTHROPIC_API_KEY$/i,\n /^OPENAI_API_KEY$/i,\n /^GEMINI_API_KEY$/i,\n /^GOOGLE_API_KEY$/i,\n /^TELEGRAM_BOT_TOKEN$/i,\n /^DISCORD_BOT_TOKEN$/i,\n /^XOPC_GATEWAY_TOKEN$/i,\n /^XOPC_CONFIG$/i,\n /^AWS_(SECRET_ACCESS_KEY|SECRET_KEY|SESSION_TOKEN)$/i,\n /^AZURE_(CLIENT_SECRET|KEY)$/i,\n /_?(API_KEY|TOKEN|PASSWORD|PASSWD|PRIVATE_KEY|SECRET)$/i,\n];\n\nconst MAX_ENV_VAR_LENGTH = 32768;\n\nexport interface SanitizedEnvVars {\n safe: Record<string, string>;\n blocked: string[];\n warnings: string[];\n}\n\nfunction isDangerousEnvVar(key: string): boolean {\n const upperKey = key.toUpperCase();\n return BLOCKED_ENV_VAR_PATTERNS.some((pattern) => pattern.test(upperKey));\n}\n\nfunction validateEnvVarValue(key: string, value: string): string | null {\n if (value.includes('\\0')) {\n return `Environment variable ${key} contains null bytes`;\n }\n if (value.length > MAX_ENV_VAR_LENGTH) {\n return `Environment variable ${key} exceeds maximum length (${MAX_ENV_VAR_LENGTH})`;\n }\n if (/^[A-Za-z0-9+/=]{80,}$/.test(value)) {\n return `Environment variable ${key} looks like base64-encoded credential data`;\n }\n return null;\n}\n\nexport function sanitizeEnvVars(\n env: Record<string, string | undefined>,\n options?: {\n allowedVars?: string[];\n customBlocked?: RegExp[];\n },\n): SanitizedEnvVars {\n const safe: Record<string, string> = {};\n const blocked: string[] = [];\n const warnings: string[] = [];\n\n const allowedVars = new Set(options?.allowedVars ?? []);\n const customBlocked = options?.customBlocked ?? [];\n\n for (const [key, value] of Object.entries(env)) {\n if (value === undefined) continue;\n\n if (allowedVars.has(key)) {\n safe[key] = value;\n continue;\n }\n\n const isBlocked =\n isDangerousEnvVar(key) || customBlocked.some((pattern) => pattern.test(key));\n\n if (isBlocked) {\n blocked.push(key);\n continue;\n }\n\n const warning = validateEnvVarValue(key, value);\n if (warning) {\n warnings.push(warning);\n continue;\n }\n\n safe[key] = value;\n }\n\n return { safe, blocked, warnings };\n}\n\n/**\n * Environment for agent command execution: strips secrets, keeps normal tooling vars.\n */\nexport function prepareSafeToolEnv(\n baseEnv: NodeJS.ProcessEnv | Record<string, string | undefined>,\n options?: { allowedVars?: string[] },\n): Record<string, string> {\n const { safe } = sanitizeEnvVars(baseEnv as Record<string, string | undefined>, options);\n return {\n ...safe,\n HOME: typeof baseEnv.HOME === 'string' && baseEnv.HOME ? baseEnv.HOME : safe.HOME || '/tmp',\n };\n}\n"],"mappings":";;;;;AAKA,MAAM,2BAAqC;CACzC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AAED,MAAM,qBAAqB;AAQ3B,SAAS,kBAAkB,KAAsB;CAC/C,MAAM,WAAW,IAAI,aAAa;AAClC,QAAO,yBAAyB,MAAM,YAAY,QAAQ,KAAK,SAAS,CAAC;;AAG3E,SAAS,oBAAoB,KAAa,OAA8B;AACtE,KAAI,MAAM,SAAS,KAAK,CACtB,QAAO,wBAAwB,IAAI;AAErC,KAAI,MAAM,SAAS,mBACjB,QAAO,wBAAwB,IAAI,2BAA2B,mBAAmB;AAEnF,KAAI,wBAAwB,KAAK,MAAM,CACrC,QAAO,wBAAwB,IAAI;AAErC,QAAO;;AAGT,SAAgB,gBACd,KACA,SAIkB;CAClB,MAAM,OAA+B,EAAE;CACvC,MAAM,UAAoB,EAAE;CAC5B,MAAM,WAAqB,EAAE;CAE7B,MAAM,cAAc,IAAI,IAAI,SAAS,eAAe,EAAE,CAAC;CACvD,MAAM,gBAAgB,SAAS,iBAAiB,EAAE;AAElD,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,IAAI,EAAE;AAC9C,MAAI,UAAU,KAAA,EAAW;AAEzB,MAAI,YAAY,IAAI,IAAI,EAAE;AACxB,QAAK,OAAO;AACZ;;AAMF,MAFE,kBAAkB,IAAI,IAAI,cAAc,MAAM,YAAY,QAAQ,KAAK,IAAI,CAAC,EAE/D;AACb,WAAQ,KAAK,IAAI;AACjB;;EAGF,MAAM,UAAU,oBAAoB,KAAK,MAAM;AAC/C,MAAI,SAAS;AACX,YAAS,KAAK,QAAQ;AACtB;;AAGF,OAAK,OAAO;;AAGd,QAAO;EAAE;EAAM;EAAS;EAAU;;;;;AAMpC,SAAgB,mBACd,SACA,SACwB;CACxB,MAAM,EAAE,SAAS,gBAAgB,SAA+C,QAAQ;AACxF,QAAO;EACL,GAAG;EACH,MAAM,OAAO,QAAQ,SAAS,YAAY,QAAQ,OAAO,QAAQ,OAAO,KAAK,QAAQ;EACtF"}
|
|
@@ -30,9 +30,9 @@ export type SandboxConfig = {
|
|
|
30
30
|
mode: SandboxMode;
|
|
31
31
|
/** Directories where file operations and command execution are allowed. */
|
|
32
32
|
allowedRoots: string[];
|
|
33
|
-
/** Maximum execution time for
|
|
33
|
+
/** Maximum execution time for commands (ms). */
|
|
34
34
|
maxExecutionTimeMs: number;
|
|
35
|
-
/** Maximum output size from
|
|
35
|
+
/** Maximum output size from commands (bytes). */
|
|
36
36
|
maxOutputBytes: number;
|
|
37
37
|
/** Paths that are always blocked regardless of allowedRoots. */
|
|
38
38
|
blockedPaths?: string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","names":[],"sources":["../../../../src/agent/sandbox/types.ts"],"sourcesContent":["/**\n * Sandbox type definitions for Phase 1 process-level isolation.\n */\n\n/** Controls how strictly the sandbox enforces isolation. */\nexport type SandboxMode = 'off' | 'basic' | 'container';\n\n/** Result of path validation against the sandbox policy. */\nexport type PathValidationResult = {\n allowed: boolean;\n /** Canonical (symlink-resolved) path after validation. */\n canonicalPath?: string;\n reason?: string;\n};\n\n/** Result of command validation against injection patterns. */\nexport type CommandValidationResult = {\n allowed: boolean;\n reason?: string;\n /** Risk severity when blocked. */\n severity?: 'critical' | 'high' | 'medium';\n};\n\n/** Aggregated result from the exec policy layer. */\nexport type ExecPolicyResult = {\n allowed: boolean;\n reason?: string;\n sanitizedEnv: Record<string, string>;\n effectiveCwd: string;\n timeoutMs: number;\n};\n\n/** Sandbox configuration for an agent or workspace. */\nexport type SandboxConfig = {\n mode: SandboxMode;\n /** Directories where file operations and command execution are allowed. */\n allowedRoots: string[];\n /** Maximum execution time for
|
|
1
|
+
{"version":3,"file":"types.js","names":[],"sources":["../../../../src/agent/sandbox/types.ts"],"sourcesContent":["/**\n * Sandbox type definitions for Phase 1 process-level isolation.\n */\n\n/** Controls how strictly the sandbox enforces isolation. */\nexport type SandboxMode = 'off' | 'basic' | 'container';\n\n/** Result of path validation against the sandbox policy. */\nexport type PathValidationResult = {\n allowed: boolean;\n /** Canonical (symlink-resolved) path after validation. */\n canonicalPath?: string;\n reason?: string;\n};\n\n/** Result of command validation against injection patterns. */\nexport type CommandValidationResult = {\n allowed: boolean;\n reason?: string;\n /** Risk severity when blocked. */\n severity?: 'critical' | 'high' | 'medium';\n};\n\n/** Aggregated result from the exec policy layer. */\nexport type ExecPolicyResult = {\n allowed: boolean;\n reason?: string;\n sanitizedEnv: Record<string, string>;\n effectiveCwd: string;\n timeoutMs: number;\n};\n\n/** Sandbox configuration for an agent or workspace. */\nexport type SandboxConfig = {\n mode: SandboxMode;\n /** Directories where file operations and command execution are allowed. */\n allowedRoots: string[];\n /** Maximum execution time for commands (ms). */\n maxExecutionTimeMs: number;\n /** Maximum output size from commands (bytes). */\n maxOutputBytes: number;\n /** Paths that are always blocked regardless of allowedRoots. */\n blockedPaths?: string[];\n /** Env var names to pass through even if they match secret heuristics. */\n allowedEnvVars?: string[];\n};\n\n/** Default sandbox configuration. */\nexport const DEFAULT_SANDBOX_CONFIG: SandboxConfig = {\n mode: 'basic',\n allowedRoots: [],\n maxExecutionTimeMs: 300_000,\n maxOutputBytes: 50 * 1024,\n};\n"],"mappings":";;AAgDA,MAAa,yBAAwC;CACnD,MAAM;CACN,cAAc,EAAE;CAChB,oBAAoB;CACpB,gBAAgB,KAAK;CACtB"}
|
|
@@ -51,7 +51,7 @@ async function runBtwQuery(opts) {
|
|
|
51
51
|
"You are answering an ephemeral /btw side question about the current conversation.",
|
|
52
52
|
"Use the conversation only as background context.",
|
|
53
53
|
"Answer only the side question. Do not continue or complete any unfinished task from the conversation.",
|
|
54
|
-
"Do not use tools,
|
|
54
|
+
"Do not use tools, commands, or file writes unless the question explicitly requires a tiny code snippet.",
|
|
55
55
|
"If the question can be answered briefly, answer briefly."
|
|
56
56
|
].join("\n"),
|
|
57
57
|
"",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"btw-query.js","names":[],"sources":["../../../../src/agent/service/btw-query.ts"],"sourcesContent":["import type { AgentMessage } from '@earendil-works/pi-agent-core';\nimport { complete, type UserMessage } from '@earendil-works/pi-ai/compat';\n\nimport { readAgentMessageContent } from '../memory/agent-message-access.js';\nimport type { SessionStore } from '../../session/index.js';\nimport { resolveModel } from '../../providers/index.js';\n\ntype BtwLog = { warn: (obj: Record<string, unknown>, msg: string) => void };\n\nfunction textFromCompleteContent(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(\n (c): c is { type: 'text'; text: string } =>\n c?.type === 'text' && typeof (c as { text?: unknown }).text === 'string',\n )\n .map((c) => c.text || '')\n .join('');\n}\n\nfunction formatMessagesForBtw(messages: AgentMessage[]): string {\n return messages\n .map((m) => {\n const role = m.role;\n let body = '';\n const raw = readAgentMessageContent(m);\n if (typeof raw === 'string') {\n body = raw;\n } else if (Array.isArray(raw)) {\n body = raw\n .filter((c): c is { type: 'text'; text: string } => c.type === 'text')\n .map((c) => c.text || '')\n .join('\\n');\n }\n const line = `[${role}]: ${body}`;\n return line.length > 4000 ? `${line.slice(0, 4000)}…` : line;\n })\n .join('\\n\\n');\n}\n\n/** One-shot LLM answer for /btw: transcript as background only; does not persist. */\nexport async function runBtwQuery(opts: {\n sessionKey: string;\n question: string;\n sessionStore: SessionStore;\n modelForSession: string;\n log: BtwLog;\n}): Promise<{ text: string; error?: string }> {\n const q = opts.question.trim();\n if (!q) {\n return { text: '', error: 'Empty question.' };\n }\n const messages = await opts.sessionStore.load(opts.sessionKey);\n const modelRef = opts.modelForSession;\n let model;\n try {\n model = resolveModel(modelRef);\n } catch (err) {\n const em = err instanceof Error ? err.message : String(err);\n opts.log.warn({ err, modelRef, errorMessage: em }, 'btwQuery: model resolve failed');\n return { text: '', error: `Could not resolve model: ${modelRef}` };\n }\n\n const background = formatMessagesForBtw(messages.slice(-40));\n const systemBlock = [\n 'You are answering an ephemeral /btw side question about the current conversation.',\n 'Use the conversation only as background context.',\n 'Answer only the side question. Do not continue or complete any unfinished task from the conversation.',\n 'Do not use tools,
|
|
1
|
+
{"version":3,"file":"btw-query.js","names":[],"sources":["../../../../src/agent/service/btw-query.ts"],"sourcesContent":["import type { AgentMessage } from '@earendil-works/pi-agent-core';\nimport { complete, type UserMessage } from '@earendil-works/pi-ai/compat';\n\nimport { readAgentMessageContent } from '../memory/agent-message-access.js';\nimport type { SessionStore } from '../../session/index.js';\nimport { resolveModel } from '../../providers/index.js';\n\ntype BtwLog = { warn: (obj: Record<string, unknown>, msg: string) => void };\n\nfunction textFromCompleteContent(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(\n (c): c is { type: 'text'; text: string } =>\n c?.type === 'text' && typeof (c as { text?: unknown }).text === 'string',\n )\n .map((c) => c.text || '')\n .join('');\n}\n\nfunction formatMessagesForBtw(messages: AgentMessage[]): string {\n return messages\n .map((m) => {\n const role = m.role;\n let body = '';\n const raw = readAgentMessageContent(m);\n if (typeof raw === 'string') {\n body = raw;\n } else if (Array.isArray(raw)) {\n body = raw\n .filter((c): c is { type: 'text'; text: string } => c.type === 'text')\n .map((c) => c.text || '')\n .join('\\n');\n }\n const line = `[${role}]: ${body}`;\n return line.length > 4000 ? `${line.slice(0, 4000)}…` : line;\n })\n .join('\\n\\n');\n}\n\n/** One-shot LLM answer for /btw: transcript as background only; does not persist. */\nexport async function runBtwQuery(opts: {\n sessionKey: string;\n question: string;\n sessionStore: SessionStore;\n modelForSession: string;\n log: BtwLog;\n}): Promise<{ text: string; error?: string }> {\n const q = opts.question.trim();\n if (!q) {\n return { text: '', error: 'Empty question.' };\n }\n const messages = await opts.sessionStore.load(opts.sessionKey);\n const modelRef = opts.modelForSession;\n let model;\n try {\n model = resolveModel(modelRef);\n } catch (err) {\n const em = err instanceof Error ? err.message : String(err);\n opts.log.warn({ err, modelRef, errorMessage: em }, 'btwQuery: model resolve failed');\n return { text: '', error: `Could not resolve model: ${modelRef}` };\n }\n\n const background = formatMessagesForBtw(messages.slice(-40));\n const systemBlock = [\n 'You are answering an ephemeral /btw side question about the current conversation.',\n 'Use the conversation only as background context.',\n 'Answer only the side question. Do not continue or complete any unfinished task from the conversation.',\n 'Do not use tools, commands, or file writes unless the question explicitly requires a tiny code snippet.',\n 'If the question can be answered briefly, answer briefly.',\n ].join('\\n');\n\n const userPrompt = [\n systemBlock,\n '',\n '---',\n 'Conversation background (read-only):',\n background || '(empty)',\n '',\n '---',\n 'Side question:',\n q,\n ].join('\\n');\n\n const userMessage: UserMessage = { role: 'user', content: userPrompt, timestamp: Date.now() };\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 90_000);\n try {\n const out = await complete(model, { messages: [userMessage] }, {\n maxTokens: 2048,\n temperature: 0.4,\n signal: controller.signal as AbortSignal,\n });\n const text = textFromCompleteContent((out as { content?: unknown }).content).trim();\n return text ? { text } : { text: '', error: 'No text returned from model.' };\n } catch (err) {\n const em = err instanceof Error ? err.message : String(err);\n opts.log.warn({ err, sessionKey: opts.sessionKey, errorMessage: em }, 'btwQuery failed');\n return { text: '', error: em };\n } finally {\n clearTimeout(timeoutId);\n }\n}\n"],"mappings":";;;;gBAKwD;AAIxD,SAAS,wBAAwB,SAA0B;AACzD,KAAI,OAAO,YAAY,SACrB,QAAO;AAET,KAAI,CAAC,MAAM,QAAQ,QAAQ,CACzB,QAAO;AAET,QAAO,QACJ,QACE,MACC,GAAG,SAAS,UAAU,OAAQ,EAAyB,SAAS,SACnE,CACA,KAAK,MAAM,EAAE,QAAQ,GAAG,CACxB,KAAK,GAAG;;AAGb,SAAS,qBAAqB,UAAkC;AAC9D,QAAO,SACJ,KAAK,MAAM;EACV,MAAM,OAAO,EAAE;EACf,IAAI,OAAO;EACX,MAAM,MAAM,wBAAwB,EAAE;AACtC,MAAI,OAAO,QAAQ,SACjB,QAAO;WACE,MAAM,QAAQ,IAAI,CAC3B,QAAO,IACJ,QAAQ,MAA2C,EAAE,SAAS,OAAO,CACrE,KAAK,MAAM,EAAE,QAAQ,GAAG,CACxB,KAAK,KAAK;EAEf,MAAM,OAAO,IAAI,KAAK,KAAK;AAC3B,SAAO,KAAK,SAAS,MAAO,GAAG,KAAK,MAAM,GAAG,IAAK,CAAC,KAAK;GACxD,CACD,KAAK,OAAO;;;AAIjB,eAAsB,YAAY,MAMY;CAC5C,MAAM,IAAI,KAAK,SAAS,MAAM;AAC9B,KAAI,CAAC,EACH,QAAO;EAAE,MAAM;EAAI,OAAO;EAAmB;CAE/C,MAAM,WAAW,MAAM,KAAK,aAAa,KAAK,KAAK,WAAW;CAC9D,MAAM,WAAW,KAAK;CACtB,IAAI;AACJ,KAAI;AACF,UAAQ,aAAa,SAAS;UACvB,KAAK;EACZ,MAAM,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC3D,OAAK,IAAI,KAAK;GAAE;GAAK;GAAU,cAAc;GAAI,EAAE,iCAAiC;AACpF,SAAO;GAAE,MAAM;GAAI,OAAO,4BAA4B;GAAY;;CAGpE,MAAM,aAAa,qBAAqB,SAAS,MAAM,IAAI,CAAC;CAqB5D,MAAM,cAA2B;EAAE,MAAM;EAAQ,SAZ9B;GARC;IAClB;IACA;IACA;IACA;IACA;IACD,CAAC,KAAK,KAGM;GACX;GACA;GACA;GACA,cAAc;GACd;GACA;GACA;GACA;GACD,CAAC,KAAK,KAE6D;EAAE,WAAW,KAAK,KAAK;EAAE;CAC7F,MAAM,aAAa,IAAI,iBAAiB;CACxC,MAAM,YAAY,iBAAiB,WAAW,OAAO,EAAE,IAAO;AAC9D,KAAI;EAMF,MAAM,OAAO,yBAAyB,MALpB,SAAS,OAAO,EAAE,UAAU,CAAC,YAAY,EAAE,EAAE;GAC7D,WAAW;GACX,aAAa;GACb,QAAQ,WAAW;GACpB,CAAC,EACkE,QAAQ,CAAC,MAAM;AACnF,SAAO,OAAO,EAAE,MAAM,GAAG;GAAE,MAAM;GAAI,OAAO;GAAgC;UACrE,KAAK;EACZ,MAAM,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC3D,OAAK,IAAI,KAAK;GAAE;GAAK,YAAY,KAAK;GAAY,cAAc;GAAI,EAAE,kBAAkB;AACxF,SAAO;GAAE,MAAM;GAAI,OAAO;GAAI;WACtB;AACR,eAAa,UAAU"}
|
|
@@ -4,6 +4,9 @@ import "../../session/index.js";
|
|
|
4
4
|
import { buildDirectUserMessageContent } from "./build-direct-message-content.js";
|
|
5
5
|
import { hydratePerTurnState, runDirectAgentTurn, tryRunSlashCommand } from "./direct-turn-helpers.js";
|
|
6
6
|
//#region src/agent/service/process-direct-one-shot.ts
|
|
7
|
+
function isReviewOutput(value) {
|
|
8
|
+
return !!value && typeof value === "object" && !Array.isArray(value) && value.type === "review";
|
|
9
|
+
}
|
|
7
10
|
async function runProcessDirect(deps, input) {
|
|
8
11
|
const { channel, chatId } = await deps.resolveSessionEndpoint(input.sessionKey);
|
|
9
12
|
deps.initSessionContext(input.sessionKey, channel, chatId);
|
|
@@ -30,11 +33,12 @@ async function runProcessDirect(deps, input) {
|
|
|
30
33
|
const trimmed = slash.aggregatedText.trim();
|
|
31
34
|
if (trimmed) await deps.sessionStore.appendTranscriptMessage(input.sessionKey, {
|
|
32
35
|
role: "assistant",
|
|
33
|
-
content: [{
|
|
36
|
+
content: isReviewOutput(slash.metadata?.review) ? [slash.metadata.review] : [{
|
|
34
37
|
type: "text",
|
|
35
38
|
text: trimmed
|
|
36
39
|
}],
|
|
37
|
-
timestamp: Date.now()
|
|
40
|
+
timestamp: Date.now(),
|
|
41
|
+
...slash.metadata && Object.keys(slash.metadata).length > 0 ? { metadata: slash.metadata } : {}
|
|
38
42
|
});
|
|
39
43
|
return slash.aggregatedText ?? "";
|
|
40
44
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-direct-one-shot.js","names":[],"sources":["../../../../src/agent/service/process-direct-one-shot.ts"],"sourcesContent":["import type { AgentMessage } from '@earendil-works/pi-agent-core';\n\nimport type { CommandHandler } from '../messaging/command-handler.js';\nimport type { AgentInstanceGateway } from '../agent-instance-gateway.js';\nimport type { ModelManager } from '../models/index.js';\nimport type { SessionStore } from '../../session/index.js';\nimport type { Config } from '../../config/schema.js';\nimport { initSessionTurn } from '../../session/index.js';\nimport {\n buildDirectUserMessageContent,\n} from './build-direct-message-content.js';\nimport type { ProcessDirectStreamLog } from './process-direct-streaming.js';\nimport type {\n InboundAttachmentInput,\n MediaRef,\n} from '../../channels/attachments/inbound-persist.js';\nimport {\n hydratePerTurnState,\n runDirectAgentTurn,\n tryRunSlashCommand,\n} from './direct-turn-helpers.js';\nimport {\n clearPendingTranscriptUserMessage,\n setPendingTranscriptUserMessage,\n type TranscriptUserMessage,\n} from '../inbound/attachment-pipeline.js';\n\nexport type RunProcessDirectDeps = {\n log: ProcessDirectStreamLog;\n config: Config;\n resolveSessionEndpoint: (sessionKey: string) => Promise<{ channel: string; chatId: string }>;\n initSessionContext: (sessionKey: string, channel: string, chatId: string) => void;\n hydrateSessionWorkspaceFromStore: (sessionKey: string) => Promise<void>;\n hydrateSessionModelFromStore: (sessionKey: string) => Promise<void>;\n agentManager: AgentInstanceGateway;\n sessionStore: SessionStore;\n modelManager: ModelManager;\n applyResolvedThinkingLevel: (sessionKey: string, thinking?: string | null) => Promise<void>;\n prepareInboundAttachments: (\n sessionKey: string,\n attachments?: InboundAttachmentInput[],\n ) => Promise<MediaRef[] | undefined>;\n commandHandler: Pick<CommandHandler, 'executeCommandAndAggregateReply'>;\n onTurnComplete?: (sessionKey: string, lastAssistantText?: string) => void;\n endDirectRequestContext: () => void;\n resetSession: (sessionKey: string) => Promise<{ sessionId: string; previousSessionId: string } | null>;\n};\n\nexport async function runProcessDirect(\n deps: RunProcessDirectDeps,\n input: {\n content: string;\n sessionKey: string;\n attachments?: InboundAttachmentInput[];\n thinking?: string;\n },\n): Promise<string> {\n const { channel, chatId } = await deps.resolveSessionEndpoint(input.sessionKey);\n deps.initSessionContext(input.sessionKey, channel, chatId);\n\n try {\n let turnBody = input.content;\n let resetTriggeredAtInit = false;\n const turn = await initSessionTurn({\n cfg: deps.config,\n sessionKey: input.sessionKey,\n body: input.content,\n resetSession: deps.resetSession,\n });\n resetTriggeredAtInit = turn.resetTriggered;\n if (turn.bareReset && turn.ackMessage) {\n return turn.ackMessage;\n }\n turnBody = turn.bodyStripped;\n\n await hydratePerTurnState(deps, input.sessionKey, input.thinking);\n const prepared = await deps.prepareInboundAttachments(input.sessionKey, input.attachments);\n\n const slash = await tryRunSlashCommand(\n deps,\n { sessionKey: input.sessionKey, channel, chatId },\n turnBody,\n { skipResetCommands: resetTriggeredAtInit },\n );\n if (slash.matched) {\n const trimmed = slash.aggregatedText.trim();\n if (trimmed) {\n await deps.sessionStore.appendTranscriptMessage(input.sessionKey, {\n role: 'assistant',\n content: [{ type: 'text', text: trimmed }],\n timestamp: Date.now(),\n } as AgentMessage);\n }\n return slash.aggregatedText ?? '';\n }\n\n const textForDirect = turnBody.trimStart().startsWith('/skill:')\n ? deps.agentManager.expandSkillUserText(turnBody)\n : turnBody;\n const userMessage = await buildDirectUserMessageContent({\n content: textForDirect,\n attachments: prepared,\n sessionKey: input.sessionKey,\n config: deps.config,\n agentManager: deps.agentManager,\n modelManager: deps.modelManager,\n });\n\n const pendingUserMessage = userMessage as TranscriptUserMessage;\n setPendingTranscriptUserMessage(input.sessionKey, pendingUserMessage);\n\n const result = await (async () => {\n try {\n return await runDirectAgentTurn(\n { ...deps, config: deps.config },\n {\n sessionKey: input.sessionKey,\n userMessage,\n },\n );\n } finally {\n clearPendingTranscriptUserMessage(input.sessionKey, pendingUserMessage);\n }\n })();\n\n if (result.lastAssistantText) {\n deps.onTurnComplete?.(input.sessionKey, result.lastAssistantText);\n }\n\n return result.lastAssistantText ?? '';\n } finally {\n deps.endDirectRequestContext();\n }\n}\n"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"process-direct-one-shot.js","names":[],"sources":["../../../../src/agent/service/process-direct-one-shot.ts"],"sourcesContent":["import type { AgentMessage } from '@earendil-works/pi-agent-core';\n\nimport type { CommandHandler } from '../messaging/command-handler.js';\nimport type { AgentInstanceGateway } from '../agent-instance-gateway.js';\nimport type { ModelManager } from '../models/index.js';\nimport type { SessionStore } from '../../session/index.js';\nimport type { Config } from '../../config/schema.js';\nimport { initSessionTurn } from '../../session/index.js';\nimport {\n buildDirectUserMessageContent,\n} from './build-direct-message-content.js';\nimport type { ProcessDirectStreamLog } from './process-direct-streaming.js';\nimport type {\n InboundAttachmentInput,\n MediaRef,\n} from '../../channels/attachments/inbound-persist.js';\nimport {\n hydratePerTurnState,\n runDirectAgentTurn,\n tryRunSlashCommand,\n} from './direct-turn-helpers.js';\nimport {\n clearPendingTranscriptUserMessage,\n setPendingTranscriptUserMessage,\n type TranscriptUserMessage,\n} from '../inbound/attachment-pipeline.js';\nimport type { ReviewOutput } from '../../review/review-types.js';\n\nexport type RunProcessDirectDeps = {\n log: ProcessDirectStreamLog;\n config: Config;\n resolveSessionEndpoint: (sessionKey: string) => Promise<{ channel: string; chatId: string }>;\n initSessionContext: (sessionKey: string, channel: string, chatId: string) => void;\n hydrateSessionWorkspaceFromStore: (sessionKey: string) => Promise<void>;\n hydrateSessionModelFromStore: (sessionKey: string) => Promise<void>;\n agentManager: AgentInstanceGateway;\n sessionStore: SessionStore;\n modelManager: ModelManager;\n applyResolvedThinkingLevel: (sessionKey: string, thinking?: string | null) => Promise<void>;\n prepareInboundAttachments: (\n sessionKey: string,\n attachments?: InboundAttachmentInput[],\n ) => Promise<MediaRef[] | undefined>;\n commandHandler: Pick<CommandHandler, 'executeCommandAndAggregateReply'>;\n onTurnComplete?: (sessionKey: string, lastAssistantText?: string) => void;\n endDirectRequestContext: () => void;\n resetSession: (sessionKey: string) => Promise<{ sessionId: string; previousSessionId: string } | null>;\n};\n\nfunction isReviewOutput(value: unknown): value is ReviewOutput {\n return !!value && typeof value === 'object' && !Array.isArray(value)\n && (value as { type?: unknown }).type === 'review';\n}\n\nexport async function runProcessDirect(\n deps: RunProcessDirectDeps,\n input: {\n content: string;\n sessionKey: string;\n attachments?: InboundAttachmentInput[];\n thinking?: string;\n },\n): Promise<string> {\n const { channel, chatId } = await deps.resolveSessionEndpoint(input.sessionKey);\n deps.initSessionContext(input.sessionKey, channel, chatId);\n\n try {\n let turnBody = input.content;\n let resetTriggeredAtInit = false;\n const turn = await initSessionTurn({\n cfg: deps.config,\n sessionKey: input.sessionKey,\n body: input.content,\n resetSession: deps.resetSession,\n });\n resetTriggeredAtInit = turn.resetTriggered;\n if (turn.bareReset && turn.ackMessage) {\n return turn.ackMessage;\n }\n turnBody = turn.bodyStripped;\n\n await hydratePerTurnState(deps, input.sessionKey, input.thinking);\n const prepared = await deps.prepareInboundAttachments(input.sessionKey, input.attachments);\n\n const slash = await tryRunSlashCommand(\n deps,\n { sessionKey: input.sessionKey, channel, chatId },\n turnBody,\n { skipResetCommands: resetTriggeredAtInit },\n );\n if (slash.matched) {\n const trimmed = slash.aggregatedText.trim();\n if (trimmed) {\n await deps.sessionStore.appendTranscriptMessage(input.sessionKey, {\n role: 'assistant',\n content: isReviewOutput(slash.metadata?.review)\n ? [slash.metadata.review]\n : [{ type: 'text', text: trimmed }],\n timestamp: Date.now(),\n ...(slash.metadata && Object.keys(slash.metadata).length > 0\n ? { metadata: slash.metadata }\n : {}),\n } as AgentMessage);\n }\n return slash.aggregatedText ?? '';\n }\n\n const textForDirect = turnBody.trimStart().startsWith('/skill:')\n ? deps.agentManager.expandSkillUserText(turnBody)\n : turnBody;\n const userMessage = await buildDirectUserMessageContent({\n content: textForDirect,\n attachments: prepared,\n sessionKey: input.sessionKey,\n config: deps.config,\n agentManager: deps.agentManager,\n modelManager: deps.modelManager,\n });\n\n const pendingUserMessage = userMessage as TranscriptUserMessage;\n setPendingTranscriptUserMessage(input.sessionKey, pendingUserMessage);\n\n const result = await (async () => {\n try {\n return await runDirectAgentTurn(\n { ...deps, config: deps.config },\n {\n sessionKey: input.sessionKey,\n userMessage,\n },\n );\n } finally {\n clearPendingTranscriptUserMessage(input.sessionKey, pendingUserMessage);\n }\n })();\n\n if (result.lastAssistantText) {\n deps.onTurnComplete?.(input.sessionKey, result.lastAssistantText);\n }\n\n return result.lastAssistantText ?? '';\n } finally {\n deps.endDirectRequestContext();\n }\n}\n"],"mappings":";;;;;;AAiDA,SAAS,eAAe,OAAuC;AAC7D,QAAO,CAAC,CAAC,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,MAAM,IAC9D,MAA6B,SAAS;;AAG9C,eAAsB,iBACpB,MACA,OAMiB;CACjB,MAAM,EAAE,SAAS,WAAW,MAAM,KAAK,uBAAuB,MAAM,WAAW;AAC/E,MAAK,mBAAmB,MAAM,YAAY,SAAS,OAAO;AAE1D,KAAI;EACF,IAAI,WAAW,MAAM;EACrB,IAAI,uBAAuB;EAC3B,MAAM,OAAO,MAAM,gBAAgB;GACjC,KAAK,KAAK;GACV,YAAY,MAAM;GAClB,MAAM,MAAM;GACZ,cAAc,KAAK;GACpB,CAAC;AACF,yBAAuB,KAAK;AAC5B,MAAI,KAAK,aAAa,KAAK,WACzB,QAAO,KAAK;AAEd,aAAW,KAAK;AAEhB,QAAM,oBAAoB,MAAM,MAAM,YAAY,MAAM,SAAS;EACjE,MAAM,WAAW,MAAM,KAAK,0BAA0B,MAAM,YAAY,MAAM,YAAY;EAE1F,MAAM,QAAQ,MAAM,mBAClB,MACA;GAAE,YAAY,MAAM;GAAY;GAAS;GAAQ,EACjD,UACA,EAAE,mBAAmB,sBAAsB,CAC5C;AACD,MAAI,MAAM,SAAS;GACjB,MAAM,UAAU,MAAM,eAAe,MAAM;AAC3C,OAAI,QACF,OAAM,KAAK,aAAa,wBAAwB,MAAM,YAAY;IAChE,MAAM;IACN,SAAS,eAAe,MAAM,UAAU,OAAO,GAC3C,CAAC,MAAM,SAAS,OAAO,GACvB,CAAC;KAAE,MAAM;KAAQ,MAAM;KAAS,CAAC;IACrC,WAAW,KAAK,KAAK;IACrB,GAAI,MAAM,YAAY,OAAO,KAAK,MAAM,SAAS,CAAC,SAAS,IACvD,EAAE,UAAU,MAAM,UAAU,GAC5B,EAAE;IACP,CAAiB;AAEpB,UAAO,MAAM,kBAAkB;;EAMjC,MAAM,cAAc,MAAM,8BAA8B;GACtD,SAJoB,SAAS,WAAW,CAAC,WAAW,UAAU,GAC5D,KAAK,aAAa,oBAAoB,SAAS,GAC/C;GAGF,aAAa;GACb,YAAY,MAAM;GAClB,QAAQ,KAAK;GACb,cAAc,KAAK;GACnB,cAAc,KAAK;GACpB,CAAC;EAEF,MAAM,qBAAqB;AAC3B,kCAAgC,MAAM,YAAY,mBAAmB;EAErE,MAAM,SAAS,OAAO,YAAY;AAChC,OAAI;AACF,WAAO,MAAM,mBACX;KAAE,GAAG;KAAM,QAAQ,KAAK;KAAQ,EAChC;KACE,YAAY,MAAM;KAClB;KACD,CACF;aACO;AACR,sCAAkC,MAAM,YAAY,mBAAmB;;MAEvE;AAEJ,MAAI,OAAO,kBACT,MAAK,iBAAiB,MAAM,YAAY,OAAO,kBAAkB;AAGnE,SAAO,OAAO,qBAAqB;WAC3B;AACR,OAAK,yBAAyB"}
|
|
@@ -12,18 +12,23 @@ import { isSessionSourceBinding } from "../source-context/types.js";
|
|
|
12
12
|
import { AsyncQueue } from "./async-queue.js";
|
|
13
13
|
import { hydratePerTurnState, runDirectAgentTurn, tryRunSlashCommand } from "./direct-turn-helpers.js";
|
|
14
14
|
//#region src/agent/service/process-direct-streaming.ts
|
|
15
|
-
function
|
|
15
|
+
function isReviewOutput(value) {
|
|
16
|
+
return !!value && typeof value === "object" && !Array.isArray(value) && value.type === "review";
|
|
17
|
+
}
|
|
18
|
+
function makeAssistantReceiptMessage(text, metadata) {
|
|
19
|
+
const review = isReviewOutput(metadata?.review) ? metadata.review : void 0;
|
|
16
20
|
return {
|
|
17
21
|
role: "assistant",
|
|
18
|
-
content: [{
|
|
22
|
+
content: review ? [review] : [{
|
|
19
23
|
type: "text",
|
|
20
24
|
text
|
|
21
25
|
}],
|
|
22
|
-
timestamp: Date.now()
|
|
26
|
+
timestamp: Date.now(),
|
|
27
|
+
...metadata && Object.keys(metadata).length > 0 ? { metadata } : {}
|
|
23
28
|
};
|
|
24
29
|
}
|
|
25
|
-
function pushAssistantReceipt(queue, text, runId) {
|
|
26
|
-
const message = makeAssistantReceiptMessage(text);
|
|
30
|
+
function pushAssistantReceipt(queue, text, runId, metadata) {
|
|
31
|
+
const message = makeAssistantReceiptMessage(text, metadata);
|
|
27
32
|
queue.push({
|
|
28
33
|
type: "message_start",
|
|
29
34
|
runId,
|
|
@@ -147,7 +152,7 @@ async function* runProcessDirectStreaming(deps, input) {
|
|
|
147
152
|
const text = slash.aggregatedText.trim();
|
|
148
153
|
if (text) {
|
|
149
154
|
webchatSlashReceipt = text;
|
|
150
|
-
pushAssistantReceipt(queue, text, input.runId);
|
|
155
|
+
pushAssistantReceipt(queue, text, input.runId, slashCommandMetadata);
|
|
151
156
|
} else if (channel === "webchat") {
|
|
152
157
|
webchatSlashReceipt = "Command finished with no assistant text. If you used `/goal`, a follow-up turn may still be scheduled automatically.";
|
|
153
158
|
pushAssistantReceipt(queue, webchatSlashReceipt, input.runId);
|
|
@@ -244,7 +249,7 @@ async function* runProcessDirectStreaming(deps, input) {
|
|
|
244
249
|
if (webchatSlashReceipt?.trim()) {
|
|
245
250
|
const assistantMsg = {
|
|
246
251
|
role: "assistant",
|
|
247
|
-
content: [{
|
|
252
|
+
content: isReviewOutput(slashCommandMetadata?.review) ? [slashCommandMetadata.review] : [{
|
|
248
253
|
type: "text",
|
|
249
254
|
text: webchatSlashReceipt.trim()
|
|
250
255
|
}],
|