@xopcai/xopc 0.0.134 → 0.0.136
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser-ext/manifest.json +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/Combination-HAlzriaz.js +41 -0
- package/dist/gateway/static/root/assets/{agent-avatar-dicebear-mQ4lbZRf.js → agent-avatar-dicebear-CER17NHM.js} +1 -1
- package/dist/gateway/static/root/assets/agents-Bv5R-N8Q.js +2 -0
- package/dist/gateway/static/root/assets/{app-management-settings-panel-Bj2YJiQe.js → app-management-settings-panel-B3Ap_wpX.js} +1 -1
- package/dist/gateway/static/root/assets/{appearance-settings-B93olV5Z.js → appearance-settings-3kBO22Xd.js} +1 -1
- package/dist/gateway/static/root/assets/apps-page-BHYbGQcZ.js +1 -0
- package/dist/gateway/static/root/assets/{archive-plugin-D_yymKFJ.js → archive-plugin-D_vGeNB8.js} +1 -1
- package/dist/gateway/static/root/assets/{attachment-load-kfyH3HV-.js → attachment-load-hWV_8pHL.js} +1 -1
- package/dist/gateway/static/root/assets/{automation-suggestion-card-DyGBB2WH.js → automation-suggestion-card-DVKmVTIo.js} +1 -1
- package/dist/gateway/static/root/assets/automations-page-BCTXpOBp.js +4 -0
- package/dist/gateway/static/root/assets/{binary-plugins-BX1AXf98.js → binary-plugins-BuXIeSPc.js} +1 -1
- package/dist/gateway/static/root/assets/{block-editor-D-WEVNHU.js → block-editor-C1m0u-UU.js} +39 -39
- package/dist/gateway/static/root/assets/browser-settings-page-ChUF08V5.js +8 -0
- package/dist/gateway/static/root/assets/capability-presets-api-B7WWYaEh.js +1 -0
- package/dist/gateway/static/root/assets/{capability-presets-settings-panel-CHExb90z.js → capability-presets-settings-panel-D_Bh1PnA.js} +1 -1
- package/dist/gateway/static/root/assets/{channel-recipient-api-RANYrzfa.js → channel-recipient-api-sV06LCht.js} +1 -1
- package/dist/gateway/static/root/assets/channels-settings-BI2ChQc7.js +1 -0
- package/dist/gateway/static/root/assets/channels-status-swr-MY4Xh4fM.js +1 -0
- package/dist/gateway/static/root/assets/connectors-page-qgvEp09M.js +2 -0
- package/dist/gateway/static/root/assets/desktop-pet-BxMJlvb-.js +1 -0
- package/dist/gateway/static/root/assets/{desktop-pet-settings-C3JDiXSu.js → desktop-pet-settings-DCvpDImn.js} +1 -1
- package/dist/gateway/static/root/assets/{directory-picker-path-field-MT71cjgl.js → directory-picker-path-field-D1nxs9PA.js} +1 -1
- package/dist/gateway/static/root/assets/dist-Mys7eFJ5.js +1 -0
- package/dist/gateway/static/root/assets/{dreaming-settings-Cfx_Qg4R.js → dreaming-settings-EAWkpOgw.js} +3 -3
- package/dist/gateway/static/root/assets/{extension-debug-page-Wp23cLkf.js → extension-debug-page-LUQ8dE8z.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-B5HRNtVY.js → extension-page-B_mnhoRd.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-zPfMpsYi.js → extension-settings-page-BWQsGmvW.js} +1 -1
- package/dist/gateway/static/root/assets/{fetch-6HXXdO77.js → fetch-BWXmBizE.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-config-api-CsS_3Ii6.js → gateway-config-api-M9bzPASf.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-settings-DOxh0hei.js → gateway-settings-CbgtpprN.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-startup-retry-BE1M9fH_.js → gateway-startup-retry-BALL91S7.js} +1 -1
- package/dist/gateway/static/root/assets/goal-create-dialog-CbP4t9aM.js +1 -0
- package/dist/gateway/static/root/assets/goal-detail-page-DlwI-MZZ.js +4 -0
- package/dist/gateway/static/root/assets/{goals-config-api--3Vzceq-.js → goals-config-api-BBhFVzBS.js} +1 -1
- package/dist/gateway/static/root/assets/goals-page-D26r7knp.js +2 -0
- package/dist/gateway/static/root/assets/{goals-settings-Dg_hQNVC.js → goals-settings-DdmVG_TR.js} +1 -1
- package/dist/gateway/static/root/assets/{heartbeat-settings-CecoDTZJ.js → heartbeat-settings-DZW2Xq-U.js} +1 -1
- package/dist/gateway/static/root/assets/index-CPwVzi9x.css +1 -0
- package/dist/gateway/static/root/assets/index-DZOm5_ex.js +262 -0
- package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-BWMLAFxf.js → keyboard-shortcuts-settings-1IWd88-T.js} +1 -1
- package/dist/gateway/static/root/assets/logs-page-DxoKJO7-.js +7 -0
- package/dist/gateway/static/root/assets/{markdown-split-5tvDwUMS.js → markdown-split-BtdBj32h.js} +1 -1
- package/dist/gateway/static/root/assets/{media-plugins-RSjPG4B0.js → media-plugins-CpoPj3s8.js} +1 -1
- package/dist/gateway/static/root/assets/messages-CQ_k8RHj.js +3 -0
- package/dist/gateway/static/root/assets/models-hub-panel-D1Rp__dV.js +2 -0
- package/dist/gateway/static/root/assets/notes-page-CgR1bO2W.js +1 -0
- package/dist/gateway/static/root/assets/notes-workbench-ozsnmDTV.js +2 -0
- package/dist/gateway/static/root/assets/{page-tabs-DYNWfN6S.js → page-tabs-CVLqCfQB.js} +1 -1
- package/dist/gateway/static/root/assets/{preview-open-alternatives-B50PdIc8.js → preview-open-alternatives-CKUXJk2t.js} +1 -1
- package/dist/gateway/static/root/assets/project-detail-page-B4dqFAQF.js +1 -0
- package/dist/gateway/static/root/assets/projects-page-DlRGXv4a.js +1 -0
- package/dist/gateway/static/root/assets/remote-access-hub-rZEYQMZV.js +1 -0
- package/dist/gateway/static/root/assets/{save-bar-controls-DggNb0SF.js → save-bar-controls-BtGni268.js} +1 -1
- package/dist/gateway/static/root/assets/{schedule-field-T4ti2C8S.js → schedule-field-BDGzKVWn.js} +1 -1
- package/dist/gateway/static/root/assets/{schema-form-D6wGEfEa.js → schema-form-DQJsKMCE.js} +1 -1
- package/dist/gateway/static/root/assets/sessions-page-DxGoCGU8.js +1 -0
- package/dist/gateway/static/root/assets/settings-advanced-gate-4e8ZQwxr.js +1 -0
- package/dist/gateway/static/root/assets/{settings-collapsible-section-Cgd_aLBV.js → settings-collapsible-section-c8XGd4-8.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-form-section-DnDrLMWZ.js → settings-form-section-By0YiDWo.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-loading-skeleton-A2OshMK8.js → settings-loading-skeleton-SYCPY-WM.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-CXMK9Vb3.js → settings-page-D9NwvnBt.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-layout-Da_LNPDS.js → settings-page-layout-CrICyGob.js} +1 -1
- package/dist/gateway/static/root/assets/setup-status-panel-IjK-hDxf.js +1 -0
- package/dist/gateway/static/root/assets/{share-preview-page-BGZV-PCJ.js → share-preview-page-Dfnk9M_m.js} +1 -1
- package/dist/gateway/static/root/assets/shares-settings-vQiRBra7.js +1 -0
- package/dist/gateway/static/root/assets/skill-reload-api-BIWv778A.js +1 -0
- package/dist/gateway/static/root/assets/skills-page-CruV215G.js +2 -0
- package/dist/gateway/static/root/assets/{system-settings-panel-D8uzXP0Y.js → system-settings-panel-BBVbSDFD.js} +1 -1
- package/dist/gateway/static/root/assets/{typed-models-lib-DQxv2BHa.js → typed-models-lib-CwFayhxu.js} +1 -1
- package/dist/gateway/static/root/assets/{use-channel-catalog-BCBfSoHx.js → use-channel-catalog-BKtHOM50.js} +1 -1
- package/dist/gateway/static/root/assets/{user-profile-settings-panel-DkZSHsIs.js → user-profile-settings-panel-_KDWzagh.js} +1 -1
- package/dist/gateway/static/root/assets/{utils-ivGpUrZi.js → utils-BiUjIHb4.js} +1 -1
- package/dist/gateway/static/root/assets/voice-api-key-field-agJqlm4o.js +1 -0
- package/dist/gateway/static/root/assets/work-item-detail-page-Bdw4ATCb.js +1 -0
- package/dist/gateway/static/root/assets/workflow-run-setup-panel-D-Dy6mTy.js +3 -0
- package/dist/gateway/static/root/assets/workflow-start-dialog-GammZ2GK.js +1 -0
- package/dist/gateway/static/root/assets/workflows-page-Cw5zrqti.js +18 -0
- package/dist/gateway/static/root/index.html +7 -8
- package/dist/package.js +1 -1
- package/dist/src/agent/image/inbound-image-handling.d.ts +1 -0
- package/dist/src/agent/image/inbound-image-handling.js +4 -1
- package/dist/src/agent/image/inbound-image-handling.js.map +1 -1
- package/dist/src/agent/image/tool-model-config.d.ts +18 -2
- package/dist/src/agent/image/tool-model-config.js +100 -14
- package/dist/src/agent/image/tool-model-config.js.map +1 -1
- package/dist/src/agent/image/vision-detection.js +4 -23
- package/dist/src/agent/image/vision-detection.js.map +1 -1
- package/dist/src/agent/inbound/attachment-pipeline.js +3 -0
- package/dist/src/agent/inbound/attachment-pipeline.js.map +1 -1
- package/dist/src/agent/messaging/command-handler.d.ts +5 -2
- package/dist/src/agent/messaging/command-handler.js +4 -3
- package/dist/src/agent/messaging/command-handler.js.map +1 -1
- package/dist/src/agent/service/btw-query.d.ts +2 -0
- package/dist/src/agent/service/btw-query.js +4 -2
- package/dist/src/agent/service/btw-query.js.map +1 -1
- package/dist/src/agent/service/direct-turn-helpers.d.ts +2 -0
- package/dist/src/agent/service/direct-turn-helpers.js +1 -1
- package/dist/src/agent/service/direct-turn-helpers.js.map +1 -1
- package/dist/src/agent/service/process-direct-streaming.js +58 -1
- package/dist/src/agent/service/process-direct-streaming.js.map +1 -1
- package/dist/src/agent/service.js +1 -1
- package/dist/src/agent/service.js.map +1 -1
- package/dist/src/agent/session/session-inspector.d.ts +2 -1
- package/dist/src/agent/session/session-inspector.js +5 -3
- package/dist/src/agent/session/session-inspector.js.map +1 -1
- package/dist/src/agent/tools/factory.js +2 -1
- package/dist/src/agent/tools/factory.js.map +1 -1
- package/dist/src/agent/tools/image-tool.d.ts +1 -0
- package/dist/src/agent/tools/image-tool.js +4 -1
- package/dist/src/agent/tools/image-tool.js.map +1 -1
- package/dist/src/chat-commands/builtins/review.js +94 -7
- package/dist/src/chat-commands/builtins/review.js.map +1 -1
- package/dist/src/chat-commands/context.d.ts +5 -3
- package/dist/src/chat-commands/context.js +5 -2
- package/dist/src/chat-commands/context.js.map +1 -1
- package/dist/src/chat-commands/index.d.ts +1 -1
- package/dist/src/chat-commands/index.js.map +1 -1
- package/dist/src/chat-commands/types.d.ts +12 -1
- package/dist/src/config/agent-profile.js +1 -1
- package/dist/src/gateway/agents-admin.d.ts +5 -0
- package/dist/src/gateway/agents-admin.js +6 -1
- package/dist/src/gateway/agents-admin.js.map +1 -1
- package/dist/src/gateway/hono/routes/models.js +2 -1
- package/dist/src/gateway/hono/routes/models.js.map +1 -1
- package/dist/src/gateway/hono/routes/projects.js +88 -0
- package/dist/src/gateway/hono/routes/projects.js.map +1 -1
- package/dist/src/gateway/hono/routes/sessions.js +2 -1
- package/dist/src/gateway/hono/routes/sessions.js.map +1 -1
- package/dist/src/gateway/hono/routes/workflows.js +1 -0
- package/dist/src/gateway/hono/routes/workflows.js.map +1 -1
- package/dist/src/gateway/image-capabilities.d.ts +7 -1
- package/dist/src/gateway/image-capabilities.js +16 -3
- package/dist/src/gateway/image-capabilities.js.map +1 -1
- package/dist/src/gateway/service/sessions-api.d.ts +1 -0
- package/dist/src/gateway/service/sessions-api.js.map +1 -1
- package/dist/src/gateway/service.js +3 -3
- package/dist/src/projects/project-loop.js +2 -2
- package/dist/src/projects/project-loop.js.map +1 -1
- package/dist/src/review/review-types.js +2 -1
- package/dist/src/review/review-types.js.map +1 -1
- package/dist/src/session/client-history.d.ts +1 -0
- package/dist/src/session/client-history.js +56 -0
- package/dist/src/session/client-history.js.map +1 -1
- package/dist/src/session/index.js +1 -1
- package/dist/src/session/manager.d.ts +1 -0
- package/dist/src/session/manager.js.map +1 -1
- package/dist/src/session/store.d.ts +1 -0
- package/dist/src/session/store.js +30 -1
- package/dist/src/session/store.js.map +1 -1
- package/dist/src/tui/backends/embedded-backend.js +1 -1
- package/dist/src/tui/components/assistant-message.js +10 -3
- package/dist/src/tui/components/assistant-message.js.map +1 -1
- package/dist/src/tui/components/bash-execution.d.ts +4 -0
- package/dist/src/tui/components/bash-execution.js +15 -7
- package/dist/src/tui/components/bash-execution.js.map +1 -1
- package/dist/src/tui/tool-summary.js +9 -3
- package/dist/src/tui/tool-summary.js.map +1 -1
- package/dist/src/tui/tui-submit.d.ts +3 -0
- package/dist/src/tui/tui-submit.js +24 -1
- package/dist/src/tui/tui-submit.js.map +1 -1
- package/dist/src/tui/tui.js +29 -2
- package/dist/src/tui/tui.js.map +1 -1
- package/dist/src/work-items/index.d.ts +1 -0
- package/dist/src/work-items/index.js +2 -1
- package/dist/src/work-items/work-item-workflow-judge.d.ts +8 -0
- package/dist/src/work-items/work-item-workflow-judge.js +50 -0
- package/dist/src/work-items/work-item-workflow-judge.js.map +1 -0
- package/dist/src/workflows/domain/run.d.ts +17 -0
- package/dist/src/workflows/domain/run.js.map +1 -1
- package/dist/src/workflows/service/workflow-run-service.d.ts +3 -0
- package/dist/src/workflows/service/workflow-run-service.js +67 -0
- package/dist/src/workflows/service/workflow-run-service.js.map +1 -1
- package/dist/src/workflows/service/workflow-run-service.types.d.ts +4 -1
- package/dist/src/workflows/service/workflow-session-bridge.d.ts +1 -0
- package/dist/src/workflows/service/workflow-session-bridge.js +45 -0
- package/dist/src/workflows/service/workflow-session-bridge.js.map +1 -1
- package/package.json +1 -1
- package/dist/gateway/static/root/assets/Combination-BzaXOLd9.js +0 -41
- package/dist/gateway/static/root/assets/agents-BP0iRtiu.js +0 -2
- package/dist/gateway/static/root/assets/apps-page-12_2SDFq.js +0 -1
- package/dist/gateway/static/root/assets/automations-page-V-tw6y1o.js +0 -4
- package/dist/gateway/static/root/assets/browser-settings-page-aMPnxWXi.js +0 -8
- package/dist/gateway/static/root/assets/capability-presets-api-CsaY2Z8B.js +0 -1
- package/dist/gateway/static/root/assets/channels-settings-N8NujD--.js +0 -1
- package/dist/gateway/static/root/assets/channels-status-swr-BG_j4Ci-.js +0 -1
- package/dist/gateway/static/root/assets/connectors-page-zBJbFgnK.js +0 -2
- package/dist/gateway/static/root/assets/desktop-pet-DfA-hVlt.js +0 -1
- package/dist/gateway/static/root/assets/dist-CkobR7mk.js +0 -1
- package/dist/gateway/static/root/assets/dist-DokQNVMQ.js +0 -1
- package/dist/gateway/static/root/assets/goal-create-dialog-BOWSTGB-.js +0 -1
- package/dist/gateway/static/root/assets/goal-detail-page-CCLgkqj1.js +0 -4
- package/dist/gateway/static/root/assets/goals-page-CsfIp8Vl.js +0 -2
- package/dist/gateway/static/root/assets/index-BOme955B.js +0 -262
- package/dist/gateway/static/root/assets/index-DXUkWmeK.css +0 -1
- package/dist/gateway/static/root/assets/logs-page-CW7AMYhN.js +0 -7
- package/dist/gateway/static/root/assets/messages-DR9ODkb8.js +0 -3
- package/dist/gateway/static/root/assets/models-hub-panel-mKfeyGVq.js +0 -2
- package/dist/gateway/static/root/assets/notes-page-DYw5zPeJ.js +0 -1
- package/dist/gateway/static/root/assets/notes-workbench-SPdKpvSN.js +0 -2
- package/dist/gateway/static/root/assets/project-detail-page-D3yP4K3k.js +0 -1
- package/dist/gateway/static/root/assets/projects-page-B6EV45NR.js +0 -1
- package/dist/gateway/static/root/assets/remote-access-hub-9foX60FQ.js +0 -1
- package/dist/gateway/static/root/assets/sessions-page-BReBhxWd.js +0 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-DNDBN4nT.js +0 -1
- package/dist/gateway/static/root/assets/setup-status-panel-C8UtBMJ_.js +0 -1
- package/dist/gateway/static/root/assets/shares-settings-xXAMKlc7.js +0 -1
- package/dist/gateway/static/root/assets/skill-reload-api-CurjHfEM.js +0 -1
- package/dist/gateway/static/root/assets/skills-page-BRR7NQkL.js +0 -2
- package/dist/gateway/static/root/assets/voice-api-key-field-DCiECZN1.js +0 -1
- package/dist/gateway/static/root/assets/work-item-detail-page-DAel5CHH.js +0 -1
- package/dist/gateway/static/root/assets/workflow-run-setup-panel-2w0zj9eJ.js +0 -3
- package/dist/gateway/static/root/assets/workflow-start-dialog-B5xdDFV-.js +0 -1
- package/dist/gateway/static/root/assets/workflows-page-DRhiSwuU.js +0 -18
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image-tool.js","names":[],"sources":["../../../../src/agent/tools/image-tool.ts"],"sourcesContent":["import { Type } from '@sinclair/typebox';\nimport type { AgentTool, AgentToolResult } from '@earendil-works/pi-agent-core';\nimport type { Config } from '../../config/schema.js';\nimport { describeImages } from '../image/understanding/runtime.js';\nimport { buildImageToolTextResult, resolvePromptAndModelOverride } from '../image/image-helpers.js';\nimport { runWithImageModelFallback } from '../image/image-model-fallback.js';\nimport { loadImageForToolInput } from '../image/load-image-media.js';\nimport { resolveImageModelConfigForTool } from '../image/tool-model-config.js';\n\nconst DEFAULT_PROMPT = 'Describe the image.';\nconst DEFAULT_MAX_IMAGES = 20;\n\nexport { resolveImageModelConfigForTool } from '../image/tool-model-config.js';\n\nfunction pickMaxBytes(cfg?: Config, maxBytesMb?: number): number {\n if (typeof maxBytesMb === 'number' && Number.isFinite(maxBytesMb) && maxBytesMb > 0) {\n return Math.floor(maxBytesMb * 1024 * 1024);\n }\n return 20 * 1024 * 1024;\n}\n\nexport function createImageTool(options: {\n config?: Config;\n workspace: string;\n /** When true, session model already receives images in the user message. */\n modelHasVision?: boolean;\n}): AgentTool<any, Record<string, unknown>> | null {\n const imageModelConfig = resolveImageModelConfigForTool({ cfg: options.config });\n if (!imageModelConfig) {\n return null;\n }\n\n const description = options.modelHasVision\n ? 'Analyze one or more images with a vision model. Use `image` for a single path/URL, or `images` for multiple (up to 20). Only use when images were NOT already in the user message.'\n : 'Analyze one or more images using the available image model. Use `image` or `images` for paths/URLs; optional `prompt` for what to extract.';\n\n const localRoots = [options.workspace];\n\n return {\n name: 'image',\n label: 'Image',\n description,\n parameters: Type.Object({\n prompt: Type.Optional(Type.String()),\n image: Type.Optional(Type.String({ description: 'Single image path or URL.' })),\n images: Type.Optional(\n Type.Array(Type.String(), {\n description: 'Multiple image paths or URLs (up to maxImages, default 20).',\n }),\n ),\n model: Type.Optional(Type.String({ description: 'Optional provider/model override.' })),\n maxBytesMb: Type.Optional(Type.Number()),\n maxImages: Type.Optional(Type.Number()),\n }),\n async execute(\n _toolCallId: string,\n args: Record<string, unknown>,\n ): Promise<AgentToolResult<Record<string, unknown>>> {\n const record = args && typeof args === 'object' ? args : {};\n\n const imageCandidates: string[] = [];\n if (typeof record.image === 'string') {\n imageCandidates.push(record.image);\n }\n if (Array.isArray(record.images)) {\n imageCandidates.push(...record.images.filter((v): v is string => typeof v === 'string'));\n }\n\n const seenImages = new Set<string>();\n const imageInputs: string[] = [];\n for (const candidate of imageCandidates) {\n const trimmedCandidate = candidate.trim();\n const normalizedForDedupe = trimmedCandidate.startsWith('@')\n ? trimmedCandidate.slice(1).trim()\n : trimmedCandidate;\n if (!normalizedForDedupe || seenImages.has(normalizedForDedupe)) {\n continue;\n }\n seenImages.add(normalizedForDedupe);\n imageInputs.push(trimmedCandidate);\n }\n\n if (imageInputs.length === 0) {\n return {\n content: [{ type: 'text', text: 'Error: provide `image` or `images`.' }],\n details: { error: 'missing_image' },\n };\n }\n\n const maxImagesRaw = typeof record.maxImages === 'number' ? record.maxImages : undefined;\n const maxImages =\n typeof maxImagesRaw === 'number' && Number.isFinite(maxImagesRaw) && maxImagesRaw > 0\n ? Math.floor(maxImagesRaw)\n : DEFAULT_MAX_IMAGES;\n if (imageInputs.length > maxImages) {\n return {\n content: [\n {\n type: 'text',\n text: `Too many images: ${imageInputs.length} (max ${maxImages}).`,\n },\n ],\n details: { error: 'too_many_images', count: imageInputs.length, max: maxImages },\n };\n }\n\n const { prompt: promptRaw, modelOverride } = resolvePromptAndModelOverride(\n record,\n DEFAULT_PROMPT,\n );\n const maxBytesMb = typeof record.maxBytesMb === 'number' ? record.maxBytesMb : undefined;\n const maxBytes = pickMaxBytes(options.config, maxBytesMb);\n\n const loadedImages: Array<{ buffer: Buffer; mimeType: string; resolvedImage: string }> = [];\n\n for (const imageRawInput of imageInputs) {\n const trimmed = imageRawInput.trim();\n const imageRaw = trimmed.startsWith('@') ? trimmed.slice(1).trim() : trimmed;\n if (!imageRaw) {\n return {\n content: [{ type: 'text', text: 'Error: empty image entry.' }],\n details: { error: 'empty_image' },\n };\n }\n\n const looksLikeWindowsDrivePath = /^[a-zA-Z]:[\\\\/]/.test(imageRaw);\n const hasScheme = /^[a-z][a-z0-9+.-]*:/i.test(imageRaw);\n const isFileUrl = /^file:/i.test(imageRaw);\n const isHttpUrl = /^https?:\\/\\//i.test(imageRaw);\n const isDataUrl = /^data:/i.test(imageRaw);\n if (hasScheme && !looksLikeWindowsDrivePath && !isFileUrl && !isHttpUrl && !isDataUrl) {\n return {\n content: [\n {\n type: 'text',\n text: `Unsupported image reference: ${imageRawInput}. Use a path, file://, data:, or http(s) URL.`,\n },\n ],\n details: { error: 'unsupported_image_reference', image: imageRawInput },\n };\n }\n\n try {\n const media = await loadImageForToolInput(imageRaw, {\n maxBytes,\n workspace: options.workspace,\n localRoots,\n });\n loadedImages.push({\n buffer: media.buffer,\n mimeType: media.mimeType,\n resolvedImage: imageRaw,\n });\n } catch (e) {\n return {\n content: [\n {\n type: 'text',\n text: `Failed to load image (${imageRawInput}): ${e instanceof Error ? e.message : String(e)}`,\n },\n ],\n details: { error: 'load_failed', image: imageRawInput },\n };\n }\n }\n\n const runResult = await runWithImageModelFallback({\n toolConfig: imageModelConfig,\n modelOverride,\n run: async (modelRef) => {\n const { text, provider, model } = await describeImages({\n modelRef,\n prompt: promptRaw,\n images: loadedImages.map((img) => ({ buffer: img.buffer, mimeType: img.mimeType })),\n timeoutMs: 60_000,\n });\n return { text, provider, model };\n },\n });\n\n const { result: inner, attempts } = runResult;\n const result = {\n text: inner.text,\n provider: inner.provider,\n model: inner.model,\n attempts,\n };\n\n const imageDetails =\n loadedImages.length === 1\n ? { image: loadedImages[0].resolvedImage }\n : {\n images: loadedImages.map((img) => ({ image: img.resolvedImage })),\n };\n\n return buildImageToolTextResult(result, imageDetails);\n },\n } as any;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"image-tool.js","names":[],"sources":["../../../../src/agent/tools/image-tool.ts"],"sourcesContent":["import { Type } from '@sinclair/typebox';\nimport type { AgentTool, AgentToolResult } from '@earendil-works/pi-agent-core';\nimport type { Config } from '../../config/schema.js';\nimport { describeImages } from '../image/understanding/runtime.js';\nimport { buildImageToolTextResult, resolvePromptAndModelOverride } from '../image/image-helpers.js';\nimport { runWithImageModelFallback } from '../image/image-model-fallback.js';\nimport { loadImageForToolInput } from '../image/load-image-media.js';\nimport { resolveImageModelConfigForTool } from '../image/tool-model-config.js';\n\nconst DEFAULT_PROMPT = 'Describe the image.';\nconst DEFAULT_MAX_IMAGES = 20;\n\nexport { resolveImageModelConfigForTool } from '../image/tool-model-config.js';\n\nfunction pickMaxBytes(cfg?: Config, maxBytesMb?: number): number {\n if (typeof maxBytesMb === 'number' && Number.isFinite(maxBytesMb) && maxBytesMb > 0) {\n return Math.floor(maxBytesMb * 1024 * 1024);\n }\n return 20 * 1024 * 1024;\n}\n\nexport function createImageTool(options: {\n config?: Config;\n workspace: string;\n /** When true, session model already receives images in the user message. */\n modelHasVision?: boolean;\n agentId?: string;\n}): AgentTool<any, Record<string, unknown>> | null {\n const imageModelConfig = resolveImageModelConfigForTool({\n cfg: options.config,\n agentId: options.agentId,\n });\n if (!imageModelConfig) {\n return null;\n }\n\n const description = options.modelHasVision\n ? 'Analyze one or more images with a vision model. Use `image` for a single path/URL, or `images` for multiple (up to 20). Only use when images were NOT already in the user message.'\n : 'Analyze one or more images using the available image model. Use `image` or `images` for paths/URLs; optional `prompt` for what to extract.';\n\n const localRoots = [options.workspace];\n\n return {\n name: 'image',\n label: 'Image',\n description,\n parameters: Type.Object({\n prompt: Type.Optional(Type.String()),\n image: Type.Optional(Type.String({ description: 'Single image path or URL.' })),\n images: Type.Optional(\n Type.Array(Type.String(), {\n description: 'Multiple image paths or URLs (up to maxImages, default 20).',\n }),\n ),\n model: Type.Optional(Type.String({ description: 'Optional provider/model override.' })),\n maxBytesMb: Type.Optional(Type.Number()),\n maxImages: Type.Optional(Type.Number()),\n }),\n async execute(\n _toolCallId: string,\n args: Record<string, unknown>,\n ): Promise<AgentToolResult<Record<string, unknown>>> {\n const record = args && typeof args === 'object' ? args : {};\n\n const imageCandidates: string[] = [];\n if (typeof record.image === 'string') {\n imageCandidates.push(record.image);\n }\n if (Array.isArray(record.images)) {\n imageCandidates.push(...record.images.filter((v): v is string => typeof v === 'string'));\n }\n\n const seenImages = new Set<string>();\n const imageInputs: string[] = [];\n for (const candidate of imageCandidates) {\n const trimmedCandidate = candidate.trim();\n const normalizedForDedupe = trimmedCandidate.startsWith('@')\n ? trimmedCandidate.slice(1).trim()\n : trimmedCandidate;\n if (!normalizedForDedupe || seenImages.has(normalizedForDedupe)) {\n continue;\n }\n seenImages.add(normalizedForDedupe);\n imageInputs.push(trimmedCandidate);\n }\n\n if (imageInputs.length === 0) {\n return {\n content: [{ type: 'text', text: 'Error: provide `image` or `images`.' }],\n details: { error: 'missing_image' },\n };\n }\n\n const maxImagesRaw = typeof record.maxImages === 'number' ? record.maxImages : undefined;\n const maxImages =\n typeof maxImagesRaw === 'number' && Number.isFinite(maxImagesRaw) && maxImagesRaw > 0\n ? Math.floor(maxImagesRaw)\n : DEFAULT_MAX_IMAGES;\n if (imageInputs.length > maxImages) {\n return {\n content: [\n {\n type: 'text',\n text: `Too many images: ${imageInputs.length} (max ${maxImages}).`,\n },\n ],\n details: { error: 'too_many_images', count: imageInputs.length, max: maxImages },\n };\n }\n\n const { prompt: promptRaw, modelOverride } = resolvePromptAndModelOverride(\n record,\n DEFAULT_PROMPT,\n );\n const maxBytesMb = typeof record.maxBytesMb === 'number' ? record.maxBytesMb : undefined;\n const maxBytes = pickMaxBytes(options.config, maxBytesMb);\n\n const loadedImages: Array<{ buffer: Buffer; mimeType: string; resolvedImage: string }> = [];\n\n for (const imageRawInput of imageInputs) {\n const trimmed = imageRawInput.trim();\n const imageRaw = trimmed.startsWith('@') ? trimmed.slice(1).trim() : trimmed;\n if (!imageRaw) {\n return {\n content: [{ type: 'text', text: 'Error: empty image entry.' }],\n details: { error: 'empty_image' },\n };\n }\n\n const looksLikeWindowsDrivePath = /^[a-zA-Z]:[\\\\/]/.test(imageRaw);\n const hasScheme = /^[a-z][a-z0-9+.-]*:/i.test(imageRaw);\n const isFileUrl = /^file:/i.test(imageRaw);\n const isHttpUrl = /^https?:\\/\\//i.test(imageRaw);\n const isDataUrl = /^data:/i.test(imageRaw);\n if (hasScheme && !looksLikeWindowsDrivePath && !isFileUrl && !isHttpUrl && !isDataUrl) {\n return {\n content: [\n {\n type: 'text',\n text: `Unsupported image reference: ${imageRawInput}. Use a path, file://, data:, or http(s) URL.`,\n },\n ],\n details: { error: 'unsupported_image_reference', image: imageRawInput },\n };\n }\n\n try {\n const media = await loadImageForToolInput(imageRaw, {\n maxBytes,\n workspace: options.workspace,\n localRoots,\n });\n loadedImages.push({\n buffer: media.buffer,\n mimeType: media.mimeType,\n resolvedImage: imageRaw,\n });\n } catch (e) {\n return {\n content: [\n {\n type: 'text',\n text: `Failed to load image (${imageRawInput}): ${e instanceof Error ? e.message : String(e)}`,\n },\n ],\n details: { error: 'load_failed', image: imageRawInput },\n };\n }\n }\n\n const runResult = await runWithImageModelFallback({\n toolConfig: imageModelConfig,\n modelOverride,\n run: async (modelRef) => {\n const { text, provider, model } = await describeImages({\n modelRef,\n prompt: promptRaw,\n images: loadedImages.map((img) => ({ buffer: img.buffer, mimeType: img.mimeType })),\n timeoutMs: 60_000,\n });\n return { text, provider, model };\n },\n });\n\n const { result: inner, attempts } = runResult;\n const result = {\n text: inner.text,\n provider: inner.provider,\n model: inner.model,\n attempts,\n };\n\n const imageDetails =\n loadedImages.length === 1\n ? { image: loadedImages[0].resolvedImage }\n : {\n images: loadedImages.map((img) => ({ image: img.resolvedImage })),\n };\n\n return buildImageToolTextResult(result, imageDetails);\n },\n } as any;\n}\n"],"mappings":"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"}
|
|
@@ -1,10 +1,36 @@
|
|
|
1
|
+
import { resolveEffectiveAgentProfileForSession } from "../../config/agent-profile.js";
|
|
1
2
|
import { effectiveWorkspacePathForSession } from "../../session/session-workspace.js";
|
|
2
3
|
import { getProjectForSession } from "../../projects/workspace.js";
|
|
3
4
|
import { commandRegistry } from "../registry.js";
|
|
4
5
|
import { buildReviewDiffBundle, parseReviewTargetArgs, resolveGitRoot } from "../../review/review-git.js";
|
|
5
6
|
import { formatReviewMarkdown } from "../../review/review-types.js";
|
|
7
|
+
import { randomUUID } from "node:crypto";
|
|
6
8
|
//#region src/chat-commands/builtins/review.ts
|
|
7
9
|
const MAX_DIFF_CHARS = 6e4;
|
|
10
|
+
const REVIEW_JUDGE_MAX_TOKENS = 8192;
|
|
11
|
+
async function emitToolStart(ctx, toolCallId, toolName, args) {
|
|
12
|
+
await ctx.emitEvent?.({
|
|
13
|
+
type: "tool_execution_start",
|
|
14
|
+
toolCallId,
|
|
15
|
+
toolName,
|
|
16
|
+
args
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
async function emitToolEnd(ctx, toolCallId, toolName, resultText, details, isError = false) {
|
|
20
|
+
await ctx.emitEvent?.({
|
|
21
|
+
type: "tool_execution_end",
|
|
22
|
+
toolCallId,
|
|
23
|
+
toolName,
|
|
24
|
+
isError,
|
|
25
|
+
result: {
|
|
26
|
+
content: [{
|
|
27
|
+
type: "text",
|
|
28
|
+
text: resultText
|
|
29
|
+
}],
|
|
30
|
+
...details ? { details } : {}
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
8
34
|
function truncateForPrompt(text) {
|
|
9
35
|
if (text.length <= MAX_DIFF_CHARS) return {
|
|
10
36
|
text,
|
|
@@ -72,6 +98,17 @@ function parseReviewJson(raw, target) {
|
|
|
72
98
|
source: "model"
|
|
73
99
|
};
|
|
74
100
|
}
|
|
101
|
+
function previewText(text, max = 500) {
|
|
102
|
+
const trimmed = text.trim();
|
|
103
|
+
return trimmed.length > max ? `${trimmed.slice(0, max)}…` : trimmed;
|
|
104
|
+
}
|
|
105
|
+
function reviewDefaultModelRef(ctx) {
|
|
106
|
+
try {
|
|
107
|
+
return resolveEffectiveAgentProfileForSession(ctx.config, ctx.sessionKey).primaryModelRef;
|
|
108
|
+
} catch {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
75
112
|
function buildReviewPrompt(params) {
|
|
76
113
|
return [
|
|
77
114
|
"You are a senior code reviewer. Review the provided git diff for correctness bugs only.",
|
|
@@ -99,7 +136,23 @@ async function resolveWorkspace(ctx) {
|
|
|
99
136
|
async function buildReview(ctx, args) {
|
|
100
137
|
const cwd = await resolveGitRoot(await resolveWorkspace(ctx));
|
|
101
138
|
const reviewTarget = parseReviewTargetArgs(args);
|
|
102
|
-
const
|
|
139
|
+
const prepareToolCallId = `review_prepare_${randomUUID()}`;
|
|
140
|
+
await emitToolStart(ctx, prepareToolCallId, "review.prepare_diff", {
|
|
141
|
+
target: args.trim() || "uncommitted",
|
|
142
|
+
cwd
|
|
143
|
+
});
|
|
144
|
+
let bundle;
|
|
145
|
+
try {
|
|
146
|
+
bundle = await buildReviewDiffBundle(cwd, reviewTarget);
|
|
147
|
+
await emitToolEnd(ctx, prepareToolCallId, "review.prepare_diff", [`Target: ${bundle.targetLabel}`, bundle.stat.trim() ? `Changed files:\n${bundle.stat.trim()}` : "No diff stat."].join("\n\n"), {
|
|
148
|
+
target: bundle.targetLabel,
|
|
149
|
+
status: bundle.status,
|
|
150
|
+
stat: bundle.stat
|
|
151
|
+
});
|
|
152
|
+
} catch (err) {
|
|
153
|
+
await emitToolEnd(ctx, prepareToolCallId, "review.prepare_diff", err instanceof Error ? err.message : String(err), { cwd }, true);
|
|
154
|
+
throw err;
|
|
155
|
+
}
|
|
103
156
|
const { text: diff, truncated } = truncateForPrompt(bundle.diff);
|
|
104
157
|
const target = bundle.targetLabel;
|
|
105
158
|
const instructions = reviewTarget.instructions ?? "";
|
|
@@ -121,20 +174,54 @@ async function buildReview(ctx, args) {
|
|
|
121
174
|
truncated,
|
|
122
175
|
instructions
|
|
123
176
|
});
|
|
124
|
-
const
|
|
177
|
+
const reviewerModelRef = reviewDefaultModelRef(ctx);
|
|
178
|
+
const judgeToolCallId = `review_judge_${randomUUID()}`;
|
|
179
|
+
await emitToolStart(ctx, judgeToolCallId, "review.model_judge", {
|
|
180
|
+
target,
|
|
181
|
+
diffChars: diff.length,
|
|
182
|
+
truncated,
|
|
183
|
+
...reviewerModelRef ? { modelRef: reviewerModelRef } : {}
|
|
184
|
+
});
|
|
185
|
+
const answer = ctx.btwQuery ? await ctx.btwQuery(prompt, {
|
|
186
|
+
maxTokens: REVIEW_JUDGE_MAX_TOKENS,
|
|
187
|
+
temperature: .1,
|
|
188
|
+
...reviewerModelRef ? { modelRef: reviewerModelRef } : {}
|
|
189
|
+
}) : {
|
|
190
|
+
text: "",
|
|
191
|
+
error: "Review model query is not available in this command context."
|
|
192
|
+
};
|
|
193
|
+
let judgeFailure = answer.error ? { reason: `Reviewer model error: ${answer.error}` } : answer.text ? void 0 : { reason: "Reviewer model returned no text." };
|
|
125
194
|
if (answer?.text && !answer.error) try {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
195
|
+
const parsed = parseReviewJson(answer.text, target);
|
|
196
|
+
await emitToolEnd(ctx, judgeToolCallId, "review.model_judge", parsed.summary || `${parsed.findings.length} findings`, {
|
|
197
|
+
target,
|
|
198
|
+
findings: parsed.findings.length,
|
|
199
|
+
overallCorrectness: parsed.overallCorrectness
|
|
200
|
+
});
|
|
201
|
+
return parsed;
|
|
202
|
+
} catch (err) {
|
|
203
|
+
judgeFailure = { reason: `Reviewer model output could not be parsed: ${err instanceof Error ? err.message : String(err)}` };
|
|
204
|
+
}
|
|
205
|
+
const reason = judgeFailure?.reason ?? "Reviewer model output could not be parsed.";
|
|
206
|
+
const fallback = {
|
|
129
207
|
type: "review",
|
|
130
208
|
target,
|
|
131
|
-
summary: "Review model
|
|
209
|
+
summary: "Review model did not complete; returned a local diff summary.",
|
|
132
210
|
findings: [],
|
|
133
211
|
overallCorrectness: "unknown",
|
|
134
|
-
overallExplanation: [
|
|
212
|
+
overallExplanation: [reason, bundle.stat.trim() ? `Changed files:\n${bundle.stat.trim()}` : "No textual diff stat was available."].join("\n\n"),
|
|
135
213
|
generatedAt: Date.now(),
|
|
136
214
|
source: "local"
|
|
137
215
|
};
|
|
216
|
+
await emitToolEnd(ctx, judgeToolCallId, "review.model_judge", fallback.summary, {
|
|
217
|
+
target,
|
|
218
|
+
findings: 0,
|
|
219
|
+
overallCorrectness: fallback.overallCorrectness,
|
|
220
|
+
fallback: true,
|
|
221
|
+
reason,
|
|
222
|
+
...answer.text ? { responsePreview: previewText(answer.text) } : {}
|
|
223
|
+
}, true);
|
|
224
|
+
return fallback;
|
|
138
225
|
}
|
|
139
226
|
const reviewCommand = {
|
|
140
227
|
id: "coding.review",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"review.js","names":[],"sources":["../../../../src/chat-commands/builtins/review.ts"],"sourcesContent":["import type { CommandDefinition, CommandContext } from '../types.js';\nimport { commandRegistry } from '../registry.js';\nimport { effectiveWorkspacePathForSession } from '../../session/session-workspace.js';\nimport { getProjectForSession } from '../../projects/workspace.js';\nimport {\n buildReviewDiffBundle,\n parseReviewTargetArgs,\n resolveGitRoot,\n} from '../../review/review-git.js';\nimport {\n formatReviewMarkdown,\n type ReviewFinding,\n type ReviewOutput,\n type ReviewPriority,\n} from '../../review/review-types.js';\n\nconst MAX_DIFF_CHARS = 60_000;\n\nfunction truncateForPrompt(text: string): { text: string; truncated: boolean } {\n if (text.length <= MAX_DIFF_CHARS) return { text, truncated: false };\n return {\n text: text.slice(0, MAX_DIFF_CHARS) + '\\n\\n[diff truncated]',\n truncated: true,\n };\n}\n\nfunction extractJsonObject(raw: string): unknown {\n const fenced = raw.match(/```(?:json)?\\s*([\\s\\S]*?)```/i);\n const text = (fenced?.[1] ?? raw).trim();\n const start = text.indexOf('{');\n const end = text.lastIndexOf('}');\n if (start < 0 || end <= start) throw new Error('review response did not contain JSON');\n return JSON.parse(text.slice(start, end + 1));\n}\n\nfunction priorityFrom(raw: unknown): ReviewPriority {\n const n = typeof raw === 'number' ? raw : Number.parseInt(String(raw ?? ''), 10);\n if (n === 0 || n === 1 || n === 2 || n === 3) return n;\n return 2;\n}\n\nfunction numberFrom(raw: unknown): number | undefined {\n return typeof raw === 'number' && Number.isFinite(raw) ? raw : undefined;\n}\n\nfunction stringFrom(raw: unknown): string {\n return typeof raw === 'string' ? raw.trim() : '';\n}\n\nfunction parseReviewJson(raw: string, target: string): ReviewOutput {\n const parsed = extractJsonObject(raw);\n const rec = parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? parsed as Record<string, unknown>\n : {};\n const rawFindings = Array.isArray(rec.findings) ? rec.findings : [];\n const findings: ReviewFinding[] = rawFindings\n .map((item) => {\n const f = item && typeof item === 'object' && !Array.isArray(item)\n ? item as Record<string, unknown>\n : {};\n const loc = f.code_location && typeof f.code_location === 'object'\n ? f.code_location as Record<string, unknown>\n : {};\n const range = loc.line_range && typeof loc.line_range === 'object'\n ? loc.line_range as Record<string, unknown>\n : {};\n const title = stringFrom(f.title);\n const body = stringFrom(f.body);\n if (!title && !body) return undefined;\n const finding: ReviewFinding = {\n title: title || body.slice(0, 80),\n body,\n priority: priorityFrom(f.priority),\n };\n const confidenceScore = numberFrom(f.confidence_score ?? f.confidenceScore);\n const filePath = stringFrom(loc.file_path ?? f.filePath);\n const lineStart = numberFrom(range.start ?? f.lineStart);\n const lineEnd = numberFrom(range.end ?? f.lineEnd);\n if (confidenceScore !== undefined) finding.confidenceScore = confidenceScore;\n if (filePath) finding.filePath = filePath;\n if (lineStart !== undefined) finding.lineStart = lineStart;\n if (lineEnd !== undefined) finding.lineEnd = lineEnd;\n return finding;\n })\n .filter((item): item is ReviewFinding => Boolean(item));\n\n const correctness = stringFrom(rec.overall_correctness ?? rec.overallCorrectness);\n const overallCorrectness =\n correctness === 'patch is correct' || correctness === 'patch is incorrect'\n ? correctness\n : 'unknown';\n return {\n type: 'review',\n target,\n summary: stringFrom(rec.summary) || `${findings.length} finding${findings.length === 1 ? '' : 's'}`,\n findings,\n overallCorrectness,\n overallExplanation: stringFrom(rec.overall_explanation ?? rec.overallExplanation),\n overallConfidenceScore: numberFrom(rec.overall_confidence_score ?? rec.overallConfidenceScore),\n generatedAt: Date.now(),\n source: 'model',\n };\n}\n\nfunction buildReviewPrompt(params: {\n target: string;\n status: string;\n stat: string;\n diff: string;\n truncated: boolean;\n instructions: string;\n}): string {\n return [\n 'You are a senior code reviewer. Review the provided git diff for correctness bugs only.',\n 'Prioritize concrete regressions, broken behavior, missing required tests for changed behavior, data loss, security issues, and runtime/type errors.',\n 'Do not mention style, broad refactors, praise, or speculative improvements.',\n 'Return strict JSON only with this shape:',\n '{\"findings\":[{\"title\":\"...\",\"body\":\"...\",\"priority\":1,\"confidence_score\":0.9,\"code_location\":{\"file_path\":\"path\",\"line_range\":{\"start\":12,\"end\":12}}}],\"overall_correctness\":\"patch is correct\",\"overall_explanation\":\"...\",\"overall_confidence_score\":0.8,\"summary\":\"...\"}',\n 'Use priority 0 for release-blocking, 1 for high, 2 for medium, 3 for low.',\n params.instructions ? `Extra reviewer instructions: ${params.instructions}` : '',\n `Target: ${params.target}`,\n params.truncated ? 'The diff was truncated; only report findings supported by visible lines.' : '',\n 'Git status:',\n params.status || '(clean)',\n 'Diff stat:',\n params.stat || '(none)',\n 'Diff:',\n params.diff || '(none)',\n ].filter(Boolean).join('\\n\\n');\n}\n\nasync function resolveWorkspace(ctx: CommandContext): Promise<string> {\n const sessionConfigStore = ctx.getSessionConfigStore?.();\n const sessionConfig = sessionConfigStore\n ? await sessionConfigStore.get(ctx.sessionKey).catch(() => null)\n : null;\n return effectiveWorkspacePathForSession(\n ctx.config,\n ctx.sessionKey,\n sessionConfig,\n getProjectForSession(ctx.sessionKey),\n );\n}\n\nasync function buildReview(ctx: CommandContext, args: string): Promise<ReviewOutput> {\n const workspace = await resolveWorkspace(ctx);\n const cwd = await resolveGitRoot(workspace);\n const reviewTarget = parseReviewTargetArgs(args);\n const bundle = await buildReviewDiffBundle(cwd, reviewTarget);\n const { text: diff, truncated } = truncateForPrompt(bundle.diff);\n const target = bundle.targetLabel;\n const instructions = reviewTarget.instructions ?? '';\n\n if (!bundle.status.trim() && !bundle.diff.trim()) {\n return {\n type: 'review',\n target,\n summary: 'Working tree is clean.',\n findings: [],\n overallCorrectness: 'patch is correct',\n overallExplanation: 'No uncommitted changes were found to review.',\n generatedAt: Date.now(),\n source: 'local',\n };\n }\n\n const prompt = buildReviewPrompt({\n target,\n status: bundle.status,\n stat: bundle.stat,\n diff,\n truncated,\n instructions,\n });\n const answer = await ctx.btwQuery?.(prompt);\n if (answer?.text && !answer.error) {\n try {\n return parseReviewJson(answer.text, target);\n } catch {\n // Fall through to a deterministic local summary when model output is not parseable.\n }\n }\n\n return {\n type: 'review',\n target,\n summary: 'Review model was unavailable; returned a local diff summary.',\n findings: [],\n overallCorrectness: 'unknown',\n overallExplanation: [\n answer?.error ? `Reviewer model error: ${answer.error}` : 'Reviewer model output could not be parsed.',\n bundle.stat.trim() ? `Changed files:\\n${bundle.stat.trim()}` : 'No textual diff stat was available.',\n ].join('\\n\\n'),\n generatedAt: Date.now(),\n source: 'local',\n };\n}\n\nconst reviewCommand: CommandDefinition = {\n id: 'coding.review',\n name: 'review',\n description: 'Review current workspace changes for correctness issues',\n category: 'tool',\n scope: ['global', 'private', 'group'],\n acceptsArgs: true,\n examples: ['/review', '/review focus on auth and persistence'],\n handler: async (ctx: CommandContext, args: string) => {\n await ctx.setTyping(true);\n const review = await buildReview(ctx, args);\n return {\n content: formatReviewMarkdown(review),\n success: true,\n metadata: { review },\n };\n },\n};\n\nexport function registerReviewCommand(): void {\n commandRegistry.register(reviewCommand);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"review.js","names":[],"sources":["../../../../src/chat-commands/builtins/review.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\nimport type { CommandDefinition, CommandContext } from '../types.js';\nimport { commandRegistry } from '../registry.js';\nimport { effectiveWorkspacePathForSession } from '../../session/session-workspace.js';\nimport { resolveEffectiveAgentProfileForSession } from '../../config/agent-profile.js';\nimport { getProjectForSession } from '../../projects/workspace.js';\nimport {\n buildReviewDiffBundle,\n parseReviewTargetArgs,\n resolveGitRoot,\n} from '../../review/review-git.js';\nimport {\n formatReviewMarkdown,\n type ReviewFinding,\n type ReviewOutput,\n type ReviewPriority,\n} from '../../review/review-types.js';\n\nconst MAX_DIFF_CHARS = 60_000;\nconst REVIEW_JUDGE_MAX_TOKENS = 8_192;\n\nasync function emitToolStart(\n ctx: CommandContext,\n toolCallId: string,\n toolName: string,\n args: Record<string, unknown>,\n): Promise<void> {\n await ctx.emitEvent?.({\n type: 'tool_execution_start',\n toolCallId,\n toolName,\n args,\n });\n}\n\nasync function emitToolEnd(\n ctx: CommandContext,\n toolCallId: string,\n toolName: string,\n resultText: string,\n details?: Record<string, unknown>,\n isError = false,\n): Promise<void> {\n await ctx.emitEvent?.({\n type: 'tool_execution_end',\n toolCallId,\n toolName,\n isError,\n result: {\n content: [{ type: 'text', text: resultText }],\n ...(details ? { details } : {}),\n },\n });\n}\n\nfunction truncateForPrompt(text: string): { text: string; truncated: boolean } {\n if (text.length <= MAX_DIFF_CHARS) return { text, truncated: false };\n return {\n text: text.slice(0, MAX_DIFF_CHARS) + '\\n\\n[diff truncated]',\n truncated: true,\n };\n}\n\nfunction extractJsonObject(raw: string): unknown {\n const fenced = raw.match(/```(?:json)?\\s*([\\s\\S]*?)```/i);\n const text = (fenced?.[1] ?? raw).trim();\n const start = text.indexOf('{');\n const end = text.lastIndexOf('}');\n if (start < 0 || end <= start) throw new Error('review response did not contain JSON');\n return JSON.parse(text.slice(start, end + 1));\n}\n\nfunction priorityFrom(raw: unknown): ReviewPriority {\n const n = typeof raw === 'number' ? raw : Number.parseInt(String(raw ?? ''), 10);\n if (n === 0 || n === 1 || n === 2 || n === 3) return n;\n return 2;\n}\n\nfunction numberFrom(raw: unknown): number | undefined {\n return typeof raw === 'number' && Number.isFinite(raw) ? raw : undefined;\n}\n\nfunction stringFrom(raw: unknown): string {\n return typeof raw === 'string' ? raw.trim() : '';\n}\n\nfunction parseReviewJson(raw: string, target: string): ReviewOutput {\n const parsed = extractJsonObject(raw);\n const rec = parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? parsed as Record<string, unknown>\n : {};\n const rawFindings = Array.isArray(rec.findings) ? rec.findings : [];\n const findings: ReviewFinding[] = rawFindings\n .map((item) => {\n const f = item && typeof item === 'object' && !Array.isArray(item)\n ? item as Record<string, unknown>\n : {};\n const loc = f.code_location && typeof f.code_location === 'object'\n ? f.code_location as Record<string, unknown>\n : {};\n const range = loc.line_range && typeof loc.line_range === 'object'\n ? loc.line_range as Record<string, unknown>\n : {};\n const title = stringFrom(f.title);\n const body = stringFrom(f.body);\n if (!title && !body) return undefined;\n const finding: ReviewFinding = {\n title: title || body.slice(0, 80),\n body,\n priority: priorityFrom(f.priority),\n };\n const confidenceScore = numberFrom(f.confidence_score ?? f.confidenceScore);\n const filePath = stringFrom(loc.file_path ?? f.filePath);\n const lineStart = numberFrom(range.start ?? f.lineStart);\n const lineEnd = numberFrom(range.end ?? f.lineEnd);\n if (confidenceScore !== undefined) finding.confidenceScore = confidenceScore;\n if (filePath) finding.filePath = filePath;\n if (lineStart !== undefined) finding.lineStart = lineStart;\n if (lineEnd !== undefined) finding.lineEnd = lineEnd;\n return finding;\n })\n .filter((item): item is ReviewFinding => Boolean(item));\n\n const correctness = stringFrom(rec.overall_correctness ?? rec.overallCorrectness);\n const overallCorrectness =\n correctness === 'patch is correct' || correctness === 'patch is incorrect'\n ? correctness\n : 'unknown';\n return {\n type: 'review',\n target,\n summary: stringFrom(rec.summary) || `${findings.length} finding${findings.length === 1 ? '' : 's'}`,\n findings,\n overallCorrectness,\n overallExplanation: stringFrom(rec.overall_explanation ?? rec.overallExplanation),\n overallConfidenceScore: numberFrom(rec.overall_confidence_score ?? rec.overallConfidenceScore),\n generatedAt: Date.now(),\n source: 'model',\n };\n}\n\nfunction previewText(text: string, max = 500): string {\n const trimmed = text.trim();\n return trimmed.length > max ? `${trimmed.slice(0, max)}…` : trimmed;\n}\n\nfunction reviewDefaultModelRef(ctx: CommandContext): string | undefined {\n try {\n return resolveEffectiveAgentProfileForSession(ctx.config, ctx.sessionKey).primaryModelRef;\n } catch {\n return undefined;\n }\n}\n\nfunction buildReviewPrompt(params: {\n target: string;\n status: string;\n stat: string;\n diff: string;\n truncated: boolean;\n instructions: string;\n}): string {\n return [\n 'You are a senior code reviewer. Review the provided git diff for correctness bugs only.',\n 'Prioritize concrete regressions, broken behavior, missing required tests for changed behavior, data loss, security issues, and runtime/type errors.',\n 'Do not mention style, broad refactors, praise, or speculative improvements.',\n 'Return strict JSON only with this shape:',\n '{\"findings\":[{\"title\":\"...\",\"body\":\"...\",\"priority\":1,\"confidence_score\":0.9,\"code_location\":{\"file_path\":\"path\",\"line_range\":{\"start\":12,\"end\":12}}}],\"overall_correctness\":\"patch is correct\",\"overall_explanation\":\"...\",\"overall_confidence_score\":0.8,\"summary\":\"...\"}',\n 'Use priority 0 for release-blocking, 1 for high, 2 for medium, 3 for low.',\n params.instructions ? `Extra reviewer instructions: ${params.instructions}` : '',\n `Target: ${params.target}`,\n params.truncated ? 'The diff was truncated; only report findings supported by visible lines.' : '',\n 'Git status:',\n params.status || '(clean)',\n 'Diff stat:',\n params.stat || '(none)',\n 'Diff:',\n params.diff || '(none)',\n ].filter(Boolean).join('\\n\\n');\n}\n\nasync function resolveWorkspace(ctx: CommandContext): Promise<string> {\n const sessionConfigStore = ctx.getSessionConfigStore?.();\n const sessionConfig = sessionConfigStore\n ? await sessionConfigStore.get(ctx.sessionKey).catch(() => null)\n : null;\n return effectiveWorkspacePathForSession(\n ctx.config,\n ctx.sessionKey,\n sessionConfig,\n getProjectForSession(ctx.sessionKey),\n );\n}\n\nasync function buildReview(ctx: CommandContext, args: string): Promise<ReviewOutput> {\n const workspace = await resolveWorkspace(ctx);\n const cwd = await resolveGitRoot(workspace);\n const reviewTarget = parseReviewTargetArgs(args);\n const prepareToolCallId = `review_prepare_${randomUUID()}`;\n await emitToolStart(ctx, prepareToolCallId, 'review.prepare_diff', {\n target: args.trim() || 'uncommitted',\n cwd,\n });\n let bundle: Awaited<ReturnType<typeof buildReviewDiffBundle>>;\n try {\n bundle = await buildReviewDiffBundle(cwd, reviewTarget);\n await emitToolEnd(\n ctx,\n prepareToolCallId,\n 'review.prepare_diff',\n [\n `Target: ${bundle.targetLabel}`,\n bundle.stat.trim() ? `Changed files:\\n${bundle.stat.trim()}` : 'No diff stat.',\n ].join('\\n\\n'),\n {\n target: bundle.targetLabel,\n status: bundle.status,\n stat: bundle.stat,\n },\n );\n } catch (err) {\n const em = err instanceof Error ? err.message : String(err);\n await emitToolEnd(ctx, prepareToolCallId, 'review.prepare_diff', em, { cwd }, true);\n throw err;\n }\n const { text: diff, truncated } = truncateForPrompt(bundle.diff);\n const target = bundle.targetLabel;\n const instructions = reviewTarget.instructions ?? '';\n\n if (!bundle.status.trim() && !bundle.diff.trim()) {\n return {\n type: 'review',\n target,\n summary: 'Working tree is clean.',\n findings: [],\n overallCorrectness: 'patch is correct',\n overallExplanation: 'No uncommitted changes were found to review.',\n generatedAt: Date.now(),\n source: 'local',\n };\n }\n\n const prompt = buildReviewPrompt({\n target,\n status: bundle.status,\n stat: bundle.stat,\n diff,\n truncated,\n instructions,\n });\n const reviewerModelRef = reviewDefaultModelRef(ctx);\n const judgeToolCallId = `review_judge_${randomUUID()}`;\n await emitToolStart(ctx, judgeToolCallId, 'review.model_judge', {\n target,\n diffChars: diff.length,\n truncated,\n ...(reviewerModelRef ? { modelRef: reviewerModelRef } : {}),\n });\n const answer = ctx.btwQuery\n ? await ctx.btwQuery(prompt, {\n maxTokens: REVIEW_JUDGE_MAX_TOKENS,\n temperature: 0.1,\n ...(reviewerModelRef ? { modelRef: reviewerModelRef } : {}),\n })\n : { text: '', error: 'Review model query is not available in this command context.' };\n let judgeFailure = answer.error\n ? { reason: `Reviewer model error: ${answer.error}` }\n : answer.text\n ? undefined\n : { reason: 'Reviewer model returned no text.' };\n if (answer?.text && !answer.error) {\n try {\n const parsed = parseReviewJson(answer.text, target);\n await emitToolEnd(\n ctx,\n judgeToolCallId,\n 'review.model_judge',\n parsed.summary || `${parsed.findings.length} findings`,\n {\n target,\n findings: parsed.findings.length,\n overallCorrectness: parsed.overallCorrectness,\n },\n );\n return parsed;\n } catch (err) {\n const em = err instanceof Error ? err.message : String(err);\n judgeFailure = {\n reason: `Reviewer model output could not be parsed: ${em}`,\n };\n }\n }\n\n const reason = judgeFailure?.reason ?? 'Reviewer model output could not be parsed.';\n const fallback: ReviewOutput = {\n type: 'review',\n target,\n summary: 'Review model did not complete; returned a local diff summary.',\n findings: [],\n overallCorrectness: 'unknown',\n overallExplanation: [\n reason,\n bundle.stat.trim() ? `Changed files:\\n${bundle.stat.trim()}` : 'No textual diff stat was available.',\n ].join('\\n\\n'),\n generatedAt: Date.now(),\n source: 'local',\n };\n await emitToolEnd(\n ctx,\n judgeToolCallId,\n 'review.model_judge',\n fallback.summary,\n {\n target,\n findings: 0,\n overallCorrectness: fallback.overallCorrectness,\n fallback: true,\n reason,\n ...(answer.text ? { responsePreview: previewText(answer.text) } : {}),\n },\n true,\n );\n return fallback;\n}\n\nconst reviewCommand: CommandDefinition = {\n id: 'coding.review',\n name: 'review',\n description: 'Review current workspace changes for correctness issues',\n category: 'tool',\n scope: ['global', 'private', 'group'],\n acceptsArgs: true,\n examples: ['/review', '/review focus on auth and persistence'],\n handler: async (ctx: CommandContext, args: string) => {\n await ctx.setTyping(true);\n const review = await buildReview(ctx, args);\n return {\n content: formatReviewMarkdown(review),\n success: true,\n metadata: { review },\n };\n },\n};\n\nexport function registerReviewCommand(): void {\n commandRegistry.register(reviewCommand);\n}\n"],"mappings":";;;;;;;;AAmBA,MAAM,iBAAiB;AACvB,MAAM,0BAA0B;AAEhC,eAAe,cACb,KACA,YACA,UACA,MACe;AACf,OAAM,IAAI,YAAY;EACpB,MAAM;EACN;EACA;EACA;EACD,CAAC;;AAGJ,eAAe,YACb,KACA,YACA,UACA,YACA,SACA,UAAU,OACK;AACf,OAAM,IAAI,YAAY;EACpB,MAAM;EACN;EACA;EACA;EACA,QAAQ;GACN,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM;IAAY,CAAC;GAC7C,GAAI,UAAU,EAAE,SAAS,GAAG,EAAE;GAC/B;EACF,CAAC;;AAGJ,SAAS,kBAAkB,MAAoD;AAC7E,KAAI,KAAK,UAAU,eAAgB,QAAO;EAAE;EAAM,WAAW;EAAO;AACpE,QAAO;EACL,MAAM,KAAK,MAAM,GAAG,eAAe,GAAG;EACtC,WAAW;EACZ;;AAGH,SAAS,kBAAkB,KAAsB;CAE/C,MAAM,QADS,IAAI,MAAM,gCACL,GAAG,MAAM,KAAK,MAAM;CACxC,MAAM,QAAQ,KAAK,QAAQ,IAAI;CAC/B,MAAM,MAAM,KAAK,YAAY,IAAI;AACjC,KAAI,QAAQ,KAAK,OAAO,MAAO,OAAM,IAAI,MAAM,uCAAuC;AACtF,QAAO,KAAK,MAAM,KAAK,MAAM,OAAO,MAAM,EAAE,CAAC;;AAG/C,SAAS,aAAa,KAA8B;CAClD,MAAM,IAAI,OAAO,QAAQ,WAAW,MAAM,OAAO,SAAS,OAAO,OAAO,GAAG,EAAE,GAAG;AAChF,KAAI,MAAM,KAAK,MAAM,KAAK,MAAM,KAAK,MAAM,EAAG,QAAO;AACrD,QAAO;;AAGT,SAAS,WAAW,KAAkC;AACpD,QAAO,OAAO,QAAQ,YAAY,OAAO,SAAS,IAAI,GAAG,MAAM,KAAA;;AAGjE,SAAS,WAAW,KAAsB;AACxC,QAAO,OAAO,QAAQ,WAAW,IAAI,MAAM,GAAG;;AAGhD,SAAS,gBAAgB,KAAa,QAA8B;CAClE,MAAM,SAAS,kBAAkB,IAAI;CACrC,MAAM,MAAM,UAAU,OAAO,WAAW,YAAY,CAAC,MAAM,QAAQ,OAAO,GACtE,SACA,EAAE;CAEN,MAAM,YADc,MAAM,QAAQ,IAAI,SAAS,GAAG,IAAI,WAAW,EAAE,EAEhE,KAAK,SAAS;EACb,MAAM,IAAI,QAAQ,OAAO,SAAS,YAAY,CAAC,MAAM,QAAQ,KAAK,GAC9D,OACA,EAAE;EACN,MAAM,MAAM,EAAE,iBAAiB,OAAO,EAAE,kBAAkB,WACtD,EAAE,gBACF,EAAE;EACN,MAAM,QAAQ,IAAI,cAAc,OAAO,IAAI,eAAe,WACtD,IAAI,aACJ,EAAE;EACN,MAAM,QAAQ,WAAW,EAAE,MAAM;EACjC,MAAM,OAAO,WAAW,EAAE,KAAK;AAC/B,MAAI,CAAC,SAAS,CAAC,KAAM,QAAO,KAAA;EAC5B,MAAM,UAAyB;GAC7B,OAAO,SAAS,KAAK,MAAM,GAAG,GAAG;GACjC;GACA,UAAU,aAAa,EAAE,SAAS;GACnC;EACD,MAAM,kBAAkB,WAAW,EAAE,oBAAoB,EAAE,gBAAgB;EAC3E,MAAM,WAAW,WAAW,IAAI,aAAa,EAAE,SAAS;EACxD,MAAM,YAAY,WAAW,MAAM,SAAS,EAAE,UAAU;EACxD,MAAM,UAAU,WAAW,MAAM,OAAO,EAAE,QAAQ;AAClD,MAAI,oBAAoB,KAAA,EAAW,SAAQ,kBAAkB;AAC7D,MAAI,SAAU,SAAQ,WAAW;AACjC,MAAI,cAAc,KAAA,EAAW,SAAQ,YAAY;AACjD,MAAI,YAAY,KAAA,EAAW,SAAQ,UAAU;AAC7C,SAAO;GACP,CACD,QAAQ,SAAgC,QAAQ,KAAK,CAAC;CAEzD,MAAM,cAAc,WAAW,IAAI,uBAAuB,IAAI,mBAAmB;CACjF,MAAM,qBACJ,gBAAgB,sBAAsB,gBAAgB,uBAClD,cACA;AACN,QAAO;EACL,MAAM;EACN;EACA,SAAS,WAAW,IAAI,QAAQ,IAAI,GAAG,SAAS,OAAO,UAAU,SAAS,WAAW,IAAI,KAAK;EAC9F;EACA;EACA,oBAAoB,WAAW,IAAI,uBAAuB,IAAI,mBAAmB;EACjF,wBAAwB,WAAW,IAAI,4BAA4B,IAAI,uBAAuB;EAC9F,aAAa,KAAK,KAAK;EACvB,QAAQ;EACT;;AAGH,SAAS,YAAY,MAAc,MAAM,KAAa;CACpD,MAAM,UAAU,KAAK,MAAM;AAC3B,QAAO,QAAQ,SAAS,MAAM,GAAG,QAAQ,MAAM,GAAG,IAAI,CAAC,KAAK;;AAG9D,SAAS,sBAAsB,KAAyC;AACtE,KAAI;AACF,SAAO,uCAAuC,IAAI,QAAQ,IAAI,WAAW,CAAC;SACpE;AACN;;;AAIJ,SAAS,kBAAkB,QAOhB;AACT,QAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA,OAAO,eAAe,gCAAgC,OAAO,iBAAiB;EAC9E,WAAW,OAAO;EAClB,OAAO,YAAY,6EAA6E;EAChG;EACA,OAAO,UAAU;EACjB;EACA,OAAO,QAAQ;EACf;EACA,OAAO,QAAQ;EAChB,CAAC,OAAO,QAAQ,CAAC,KAAK,OAAO;;AAGhC,eAAe,iBAAiB,KAAsC;CACpE,MAAM,qBAAqB,IAAI,yBAAyB;CACxD,MAAM,gBAAgB,qBAClB,MAAM,mBAAmB,IAAI,IAAI,WAAW,CAAC,YAAY,KAAK,GAC9D;AACJ,QAAO,iCACL,IAAI,QACJ,IAAI,YACJ,eACA,qBAAqB,IAAI,WAAW,CACrC;;AAGH,eAAe,YAAY,KAAqB,MAAqC;CAEnF,MAAM,MAAM,MAAM,eAAe,MADT,iBAAiB,IAAI,CACF;CAC3C,MAAM,eAAe,sBAAsB,KAAK;CAChD,MAAM,oBAAoB,kBAAkB,YAAY;AACxD,OAAM,cAAc,KAAK,mBAAmB,uBAAuB;EACjE,QAAQ,KAAK,MAAM,IAAI;EACvB;EACD,CAAC;CACF,IAAI;AACJ,KAAI;AACF,WAAS,MAAM,sBAAsB,KAAK,aAAa;AACvD,QAAM,YACJ,KACA,mBACA,uBACA,CACE,WAAW,OAAO,eAClB,OAAO,KAAK,MAAM,GAAG,mBAAmB,OAAO,KAAK,MAAM,KAAK,gBAChE,CAAC,KAAK,OAAO,EACd;GACE,QAAQ,OAAO;GACf,QAAQ,OAAO;GACf,MAAM,OAAO;GACd,CACF;UACM,KAAK;AAEZ,QAAM,YAAY,KAAK,mBAAmB,uBAD/B,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,EACU,EAAE,KAAK,EAAE,KAAK;AACnF,QAAM;;CAER,MAAM,EAAE,MAAM,MAAM,cAAc,kBAAkB,OAAO,KAAK;CAChE,MAAM,SAAS,OAAO;CACtB,MAAM,eAAe,aAAa,gBAAgB;AAElD,KAAI,CAAC,OAAO,OAAO,MAAM,IAAI,CAAC,OAAO,KAAK,MAAM,CAC9C,QAAO;EACL,MAAM;EACN;EACA,SAAS;EACT,UAAU,EAAE;EACZ,oBAAoB;EACpB,oBAAoB;EACpB,aAAa,KAAK,KAAK;EACvB,QAAQ;EACT;CAGH,MAAM,SAAS,kBAAkB;EAC/B;EACA,QAAQ,OAAO;EACf,MAAM,OAAO;EACb;EACA;EACA;EACD,CAAC;CACF,MAAM,mBAAmB,sBAAsB,IAAI;CACnD,MAAM,kBAAkB,gBAAgB,YAAY;AACpD,OAAM,cAAc,KAAK,iBAAiB,sBAAsB;EAC9D;EACA,WAAW,KAAK;EAChB;EACA,GAAI,mBAAmB,EAAE,UAAU,kBAAkB,GAAG,EAAE;EAC3D,CAAC;CACF,MAAM,SAAS,IAAI,WACf,MAAM,IAAI,SAAS,QAAQ;EACzB,WAAW;EACX,aAAa;EACb,GAAI,mBAAmB,EAAE,UAAU,kBAAkB,GAAG,EAAE;EAC3D,CAAC,GACF;EAAE,MAAM;EAAI,OAAO;EAAgE;CACvF,IAAI,eAAe,OAAO,QACtB,EAAE,QAAQ,yBAAyB,OAAO,SAAS,GACnD,OAAO,OACL,KAAA,IACA,EAAE,QAAQ,oCAAoC;AACpD,KAAI,QAAQ,QAAQ,CAAC,OAAO,MAC1B,KAAI;EACF,MAAM,SAAS,gBAAgB,OAAO,MAAM,OAAO;AACnD,QAAM,YACJ,KACA,iBACA,sBACA,OAAO,WAAW,GAAG,OAAO,SAAS,OAAO,YAC5C;GACE;GACA,UAAU,OAAO,SAAS;GAC1B,oBAAoB,OAAO;GAC5B,CACF;AACD,SAAO;UACA,KAAK;AAEZ,iBAAe,EACb,QAAQ,8CAFC,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,IAG1D;;CAIL,MAAM,SAAS,cAAc,UAAU;CACvC,MAAM,WAAyB;EAC7B,MAAM;EACN;EACA,SAAS;EACT,UAAU,EAAE;EACZ,oBAAoB;EACpB,oBAAoB,CAClB,QACA,OAAO,KAAK,MAAM,GAAG,mBAAmB,OAAO,KAAK,MAAM,KAAK,sCAChE,CAAC,KAAK,OAAO;EACd,aAAa,KAAK,KAAK;EACvB,QAAQ;EACT;AACD,OAAM,YACJ,KACA,iBACA,sBACA,SAAS,SACT;EACE;EACA,UAAU;EACV,oBAAoB,SAAS;EAC7B,UAAU;EACV;EACA,GAAI,OAAO,OAAO,EAAE,iBAAiB,YAAY,OAAO,KAAK,EAAE,GAAG,EAAE;EACrE,EACD,KACD;AACD,QAAO;;AAGT,MAAM,gBAAmC;CACvC,IAAI;CACJ,MAAM;CACN,aAAa;CACb,UAAU;CACV,OAAO;EAAC;EAAU;EAAW;EAAQ;CACrC,aAAa;CACb,UAAU,CAAC,WAAW,wCAAwC;CAC9D,SAAS,OAAO,KAAqB,SAAiB;AACpD,QAAM,IAAI,UAAU,KAAK;EACzB,MAAM,SAAS,MAAM,YAAY,KAAK,KAAK;AAC3C,SAAO;GACL,SAAS,qBAAqB,OAAO;GACrC,SAAS;GACT,UAAU,EAAE,QAAQ;GACrB;;CAEJ;AAED,SAAgB,wBAA8B;AAC5C,iBAAgB,SAAS,cAAc"}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Provides the concrete implementation of CommandContext interface,
|
|
5
5
|
* bridging commands to core services (AgentService, SessionStore, etc.)
|
|
6
6
|
*/
|
|
7
|
-
import type { CommandContext, ReplyOptions, UIComponent, SessionInfo, ModelInfo, UsageStats, PlatformFeature, MessageSource, CompactSessionResult } from './types.js';
|
|
7
|
+
import type { CommandContext, BtwQueryOptions, CommandStreamEvent, ReplyOptions, UIComponent, SessionInfo, ModelInfo, UsageStats, PlatformFeature, MessageSource, CompactSessionResult } from './types.js';
|
|
8
8
|
import type { Config } from '../config/schema.js';
|
|
9
9
|
import type { AgentMessage } from '@earendil-works/pi-agent-core';
|
|
10
10
|
import type { MessageBus } from '../infra/bus/index.js';
|
|
@@ -50,10 +50,11 @@ export interface CommandContextDeps {
|
|
|
50
50
|
instructions?: string;
|
|
51
51
|
force?: boolean;
|
|
52
52
|
}) => Promise<CompactionResult>;
|
|
53
|
-
btwQuery?: (sessionKey: string, question: string) => Promise<{
|
|
53
|
+
btwQuery?: (sessionKey: string, question: string, options?: BtwQueryOptions) => Promise<{
|
|
54
54
|
text: string;
|
|
55
55
|
error?: string;
|
|
56
56
|
}>;
|
|
57
|
+
emitEvent?: (event: CommandStreamEvent) => void | Promise<void>;
|
|
57
58
|
getSessionContextReport?: (sessionKey: string, mode: 'list' | 'detail' | 'json') => Promise<string>;
|
|
58
59
|
persistentGoalApis?: PersistentGoalApis;
|
|
59
60
|
workflowRunService?: WorkflowRunServiceLike;
|
|
@@ -109,10 +110,11 @@ export declare class CommandContextImpl implements CommandContext {
|
|
|
109
110
|
instructions?: string;
|
|
110
111
|
force?: boolean;
|
|
111
112
|
}): Promise<CompactSessionResult | null>;
|
|
112
|
-
btwQuery(question: string): Promise<{
|
|
113
|
+
btwQuery(question: string, options?: BtwQueryOptions): Promise<{
|
|
113
114
|
text: string;
|
|
114
115
|
error?: string;
|
|
115
116
|
}>;
|
|
117
|
+
emitEvent(event: CommandStreamEvent): void | Promise<void>;
|
|
116
118
|
exportSessionToWorkspace(format: 'markdown' | 'html' | 'json'): Promise<{
|
|
117
119
|
path: string;
|
|
118
120
|
}>;
|
|
@@ -235,12 +235,15 @@ var CommandContextImpl = class {
|
|
|
235
235
|
summary: r.summary
|
|
236
236
|
};
|
|
237
237
|
}
|
|
238
|
-
async btwQuery(question) {
|
|
238
|
+
async btwQuery(question, options) {
|
|
239
239
|
if (!this.deps.btwQuery) return {
|
|
240
240
|
text: "",
|
|
241
241
|
error: "Side questions are not available in this environment."
|
|
242
242
|
};
|
|
243
|
-
return this.deps.btwQuery(this.sessionKey, question);
|
|
243
|
+
return this.deps.btwQuery(this.sessionKey, question, options);
|
|
244
|
+
}
|
|
245
|
+
emitEvent(event) {
|
|
246
|
+
return this.deps.emitEvent?.(event);
|
|
244
247
|
}
|
|
245
248
|
async exportSessionToWorkspace(format) {
|
|
246
249
|
const exportFmt = format === "json" ? "json" : "markdown";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","names":[],"sources":["../../../src/chat-commands/context.ts"],"sourcesContent":["/**\n * Command Context Implementation\n * \n * Provides the concrete implementation of CommandContext interface,\n * bridging commands to core services (AgentService, SessionStore, etc.)\n */\n\nimport type {\n CommandContext,\n ReplyOptions,\n UIComponent,\n SessionInfo,\n ModelInfo,\n UsageStats,\n PlatformFeature,\n MessageSource,\n CompactSessionResult,\n} from './types.js';\nimport { getSessionDisplayName } from './session-key.js';\nimport type { Config } from '../config/schema.js';\nimport { getAgentDefaultModelRef } from '../config/schema.js';\nimport type { AgentMessage } from '@earendil-works/pi-agent-core';\nimport type { MessageBus } from '../infra/bus/index.js';\nimport type { SessionStore, SessionConfigStore } from '../session/index.js';\nimport { createLogger } from '../utils/logger.js';\nimport { saveConfig } from '../config/loader.js';\nimport type { ThinkLevel, ReasoningLevel, VerboseLevel } from '../agent/transcript/thinking-types.js';\nimport { mkdir, writeFile } from 'fs/promises';\nimport { join } from 'path';\nimport { effectiveWorkspacePathForSession } from '../session/session-workspace.js';\nimport { wrapMarkdownExportAsHtml } from '../session/chat-export.js';\nimport type { CompactionResult } from '../agent/memory/compaction.js';\nimport type { PersistentGoalApis } from '../agent/goals/persistent-goal-apis.js';\nimport type { WorkflowRunServiceLike } from '../workflows/service/workflow-run-service.types.js';\nimport { getProjectForSession } from '../projects/workspace.js';\n\nconst log = createLogger('CommandContext');\n\nexport interface CommandContextDeps {\n sessionKey: string;\n source: MessageSource;\n channelId: string;\n chatId: string;\n senderId: string;\n isGroup: boolean;\n accountId?: string;\n threadId?: string;\n config: Config;\n bus: MessageBus;\n sessionStore: SessionStore;\n sessionConfigStore?: SessionConfigStore;\n /** After persisting session thinking, sync pi-agent in-memory state */\n applySessionThinkingLevel?: (sessionKey: string, level: ThinkLevel) => void;\n // Callbacks for platform-specific operations\n replyHandler: (text: string, options?: ReplyOptions) => Promise<void>;\n componentHandler?: (component: UIComponent) => Promise<void>;\n typingHandler?: (typing: boolean) => Promise<void>;\n supportedFeatures: PlatformFeature[];\n /** Called after session files are removed so in-memory agents match disk */\n invalidateAgentSession?: (sessionKey: string) => void;\n /** Reset session in place (archive + new session id); optional — falls back to clearSession */\n resetSession?: (sessionKey: string) => Promise<void>;\n // Model management (optional, will be injected)\n getCurrentModel?: () => string;\n switchModel?: (modelId: string) => Promise<boolean>;\n listModels?: () => Promise<ModelInfo[]>;\n getUsage?: () => Promise<UsageStats>;\n /** Stop current LLM turn and clear channel preview stream (Telegram draft, etc.) */\n abortCurrentTurn?: () => Promise<void>;\n /** Reload skills from disk and refresh active agent prompts. */\n reloadSkills?: () => Promise<void>;\n /** Install a managed skill from an explicit source and refresh active agent prompts. */\n installSkillFromSource?: CommandContext['installSkillFromSource'];\n\n compactSession?: (\n sessionKey: string,\n options?: { instructions?: string; force?: boolean },\n ) => Promise<CompactionResult>;\n\n btwQuery?: (sessionKey: string, question: string) => Promise<{ text: string; error?: string }>;\n\n getSessionContextReport?: (\n sessionKey: string,\n mode: 'list' | 'detail' | 'json',\n ) => Promise<string>;\n\n persistentGoalApis?: PersistentGoalApis;\n workflowRunService?: WorkflowRunServiceLike;\n}\n\nexport class CommandContextImpl implements CommandContext {\n readonly sessionKey: string;\n readonly source: MessageSource;\n readonly channelId: string;\n readonly chatId: string;\n readonly senderId: string;\n readonly isGroup: boolean;\n readonly accountId?: string;\n readonly threadId?: string;\n readonly config: Config;\n readonly abortCurrentTurn?: () => Promise<void>;\n readonly reloadSkills?: () => Promise<void>;\n readonly installSkillFromSource?: CommandContext['installSkillFromSource'];\n readonly persistentGoalApis?: PersistentGoalApis;\n readonly workflowRunApis?: CommandContext['workflowRunApis'];\n\n private deps: CommandContextDeps;\n\n constructor(deps: CommandContextDeps) {\n this.sessionKey = deps.sessionKey;\n this.source = deps.source;\n this.channelId = deps.channelId;\n this.chatId = deps.chatId;\n this.senderId = deps.senderId;\n this.isGroup = deps.isGroup;\n this.accountId = deps.accountId;\n this.threadId = deps.threadId;\n this.config = deps.config;\n this.deps = deps;\n\n if (deps.abortCurrentTurn) {\n const run = deps.abortCurrentTurn;\n this.abortCurrentTurn = async () => {\n await run();\n };\n }\n\n if (deps.reloadSkills) {\n const run = deps.reloadSkills;\n this.reloadSkills = async () => {\n await run();\n };\n }\n\n if (deps.installSkillFromSource) {\n this.installSkillFromSource = deps.installSkillFromSource;\n }\n\n this.persistentGoalApis = deps.persistentGoalApis;\n if (deps.workflowRunService) {\n this.workflowRunApis = {\n service: deps.workflowRunService,\n startWorkflowRun: (params) => deps.workflowRunService!.startWorkflowRun(params),\n };\n }\n }\n\n private outboundMetadata(extra?: Record<string, unknown>): Record<string, unknown> {\n return {\n accountId: this.deps.accountId,\n threadId: this.deps.threadId,\n ...extra,\n };\n }\n\n // === Reply API ===\n\n async reply(text: string, options?: ReplyOptions): Promise<void> {\n await this.deps.replyHandler(text, options);\n }\n\n async replyComponent(component: UIComponent): Promise<void> {\n if (this.deps.componentHandler) {\n await this.deps.componentHandler(component);\n } else {\n // Fallback to text representation\n await this.reply(this.renderComponentAsText(component));\n }\n }\n\n async setTyping(typing: boolean): Promise<void> {\n if (this.deps.typingHandler) {\n await this.deps.typingHandler(typing);\n }\n }\n\n // === Session Management ===\n\n async getSession(): Promise<AgentMessage[]> {\n return this.deps.sessionStore.load(this.sessionKey);\n }\n\n async resetSession(): Promise<void> {\n if (this.deps.resetSession) {\n await this.deps.resetSession(this.sessionKey);\n } else if (typeof this.deps.sessionStore.reset === 'function') {\n const outcome = await this.deps.sessionStore.reset(this.sessionKey);\n if (!outcome) {\n throw new Error('Session not found');\n }\n this.deps.invalidateAgentSession?.(this.sessionKey);\n } else {\n await this.clearSession();\n return;\n }\n\n await this.deps.bus.publishOutbound({\n channel: this.source,\n chat_id: this.chatId,\n content: '✅ New session started. Previous transcript archived; model and session overrides kept.',\n type: 'message',\n metadata: this.outboundMetadata(),\n });\n\n log.info({ sessionKey: this.sessionKey }, 'Session reset');\n }\n\n async clearSession(): Promise<void> {\n // Archive first if has messages\n const messages = await this.getSession();\n if (messages.length > 0) {\n await this.deps.sessionStore.archive(this.sessionKey);\n log.info({ sessionKey: this.sessionKey, messageCount: messages.length }, 'Session archived');\n }\n\n // Delete session\n await this.deps.sessionStore.deleteSession(this.sessionKey);\n this.deps.invalidateAgentSession?.(this.sessionKey);\n\n // Publish outbound message to confirm\n await this.deps.bus.publishOutbound({\n channel: this.source,\n chat_id: this.chatId,\n content: '✅ Session cleared.',\n type: 'message',\n metadata: this.outboundMetadata(),\n });\n\n log.info({ sessionKey: this.sessionKey }, 'Session cleared');\n }\n\n async archiveSession(): Promise<void> {\n await this.deps.sessionStore.archive(this.sessionKey);\n log.info({ sessionKey: this.sessionKey }, 'Session archived');\n }\n\n async listSessions(): Promise<SessionInfo[]> {\n // TODO: Implement listSessions in SessionStore\n // For now, return current session only\n const messages = await this.getSession();\n return [{\n key: this.sessionKey,\n name: getSessionDisplayName(this.sessionKey),\n messageCount: messages.length,\n createdAt: new Date(),\n updatedAt: new Date(),\n isActive: true,\n }];\n }\n\n async switchSession(sessionKey: string): Promise<void> {\n // This is mainly for CLI/Web UI where you can switch between sessions\n // For Telegram, each chat has its own session\n log.info({ from: this.sessionKey, to: sessionKey }, 'Session switch requested');\n \n // Note: In the current architecture, switching session means\n // the next message will use a different sessionKey\n // The actual switch happens at the adapter level\n }\n\n // === Model Management ===\n\n getCurrentModel(): string {\n if (this.deps.getCurrentModel) {\n return this.deps.getCurrentModel();\n }\n \n return getAgentDefaultModelRef(this.config);\n }\n\n async listModels(): Promise<ModelInfo[]> {\n if (this.deps.listModels) {\n return this.deps.listModels();\n }\n \n // Fallback to empty list\n return [];\n }\n\n async switchModel(modelId: string): Promise<boolean> {\n if (this.deps.switchModel) {\n return this.deps.switchModel(modelId);\n }\n \n // No model manager available\n await this.reply('❌ Model switching not available in this context.');\n return false;\n }\n\n async getUsage(): Promise<UsageStats> {\n if (this.deps.getUsage) {\n return this.deps.getUsage();\n }\n \n // Fallback: calculate from session\n const messages = await this.getSession();\n let promptTokens = 0;\n let completionTokens = 0;\n \n for (const msg of messages) {\n if ('usage' in msg && msg.usage) {\n promptTokens += msg.usage.input || 0;\n completionTokens += msg.usage.output || 0;\n }\n }\n \n return {\n promptTokens,\n completionTokens,\n totalTokens: promptTokens + completionTokens,\n messageCount: messages.length,\n };\n }\n\n // === Platform Features ===\n\n supports(feature: PlatformFeature): boolean {\n return this.deps.supportedFeatures.includes(feature);\n }\n\n // === Configuration ===\n\n getConfig(): Config {\n return this.config;\n }\n\n async updateConfig(path: string, value: unknown): Promise<boolean> {\n try {\n // Update config object using path\n const keys = path.split('.');\n let target: Record<string, unknown> = this.config as Record<string, unknown>;\n \n for (let i = 0; i < keys.length - 1; i++) {\n const key = keys[i];\n if (!(key in target) || typeof target[key] !== 'object' || target[key] === null) {\n target[key] = {};\n }\n target = target[key] as Record<string, unknown>;\n }\n \n target[keys[keys.length - 1]] = value;\n \n // Save to disk\n await saveConfig(this.config);\n \n log.info({ path, value }, 'Config updated via command');\n return true;\n } catch (error) {\n log.error({ err: error, path, value }, 'Failed to update config');\n return false;\n }\n }\n\n // === Private Helpers ===\n\n // === Thinking Configuration ===\n\n /**\n * Get the session config store (if available)\n */\n getSessionConfigStore(): SessionConfigStore | undefined {\n return this.deps.sessionConfigStore;\n }\n\n /**\n * Get current thinking level (session override or default)\n */\n async getThinkingLevel(): Promise<ThinkLevel | undefined> {\n const configStore = this.deps.sessionConfigStore;\n if (configStore) {\n const sessionConfig = await configStore.get(this.sessionKey);\n if (sessionConfig?.thinkingLevel) {\n return sessionConfig.thinkingLevel;\n }\n }\n return undefined;\n }\n\n /**\n * Set thinking level for this session\n */\n async setThinkingLevel(level: ThinkLevel): Promise<void> {\n const configStore = this.deps.sessionConfigStore;\n if (configStore) {\n await configStore.update(this.sessionKey, { thinkingLevel: level });\n }\n this.deps.applySessionThinkingLevel?.(this.sessionKey, level);\n }\n\n syncAgentThinkingLevel(level: ThinkLevel): void {\n this.deps.applySessionThinkingLevel?.(this.sessionKey, level);\n }\n\n async compactSession(options?: { instructions?: string; force?: boolean }): Promise<CompactSessionResult | null> {\n if (!this.deps.compactSession) {\n return null;\n }\n const r = await this.deps.compactSession(this.sessionKey, options);\n return {\n compacted: r.compacted,\n tokensBefore: r.tokensBefore,\n tokensAfter: r.tokensAfter,\n summary: r.summary,\n };\n }\n\n async btwQuery(question: string): Promise<{ text: string; error?: string }> {\n if (!this.deps.btwQuery) {\n return { text: '', error: 'Side questions are not available in this environment.' };\n }\n return this.deps.btwQuery(this.sessionKey, question);\n }\n\n async exportSessionToWorkspace(format: 'markdown' | 'html' | 'json'): Promise<{ path: string }> {\n const exportFmt = format === 'json' ? 'json' : 'markdown';\n let body = await this.deps.sessionStore.exportSession(this.sessionKey, exportFmt);\n if (format === 'html') {\n body = wrapMarkdownExportAsHtml(`Session ${this.sessionKey}`, body);\n }\n const sc = this.deps.sessionConfigStore\n ? await this.deps.sessionConfigStore.get(this.sessionKey)\n : null;\n const root = effectiveWorkspacePathForSession(\n this.config,\n this.sessionKey,\n sc,\n getProjectForSession(this.sessionKey),\n );\n const dir = join(root, 'exports');\n await mkdir(dir, { recursive: true });\n const safe = this.sessionKey.replace(/[^a-zA-Z0-9._-]+/g, '_').slice(0, 96);\n const ext = format === 'json' ? 'json' : format === 'html' ? 'html' : 'md';\n const name = `session-${safe}-${Date.now()}.${ext}`;\n const outPath = join(dir, name);\n await writeFile(outPath, body, 'utf-8');\n return { path: outPath };\n }\n\n async agentContextReport(mode: 'list' | 'detail' | 'json' = 'list'): Promise<string> {\n if (!this.deps.getSessionContextReport) {\n return 'Context report is not available in this environment.';\n }\n return this.deps.getSessionContextReport(this.sessionKey, mode);\n }\n\n /**\n * Get current reasoning level (session override or default)\n */\n async getReasoningLevel(): Promise<ReasoningLevel | undefined> {\n const configStore = this.deps.sessionConfigStore;\n if (configStore) {\n const sessionConfig = await configStore.get(this.sessionKey);\n if (sessionConfig?.reasoningLevel) {\n return sessionConfig.reasoningLevel;\n }\n }\n return undefined;\n }\n\n /**\n * Set reasoning level for this session\n */\n async setReasoningLevel(level: ReasoningLevel): Promise<void> {\n const configStore = this.deps.sessionConfigStore;\n if (configStore) {\n await configStore.update(this.sessionKey, { reasoningLevel: level });\n }\n }\n\n /**\n * Get current verbose level (session override or default)\n */\n async getVerboseLevel(): Promise<VerboseLevel | undefined> {\n const configStore = this.deps.sessionConfigStore;\n if (configStore) {\n const sessionConfig = await configStore.get(this.sessionKey);\n if (sessionConfig?.verboseLevel) {\n return sessionConfig.verboseLevel;\n }\n }\n return undefined;\n }\n\n /**\n * Set verbose level for this session\n */\n async setVerboseLevel(level: VerboseLevel): Promise<void> {\n const configStore = this.deps.sessionConfigStore;\n if (configStore) {\n await configStore.update(this.sessionKey, { verboseLevel: level });\n }\n }\n\n private renderComponentAsText(component: UIComponent): string {\n switch (component.type) {\n case 'buttons':\n return component.buttons.map(b => `[${b.text}]`).join(' ');\n \n case 'select':\n return component.options.map(o => `- ${o.label}`).join('\\n');\n \n case 'model-picker':\n return component.providers.map(p => \n `**${p.name}**\\n${p.models.map(m => ` - ${m.name}`).join('\\n')}`\n ).join('\\n\\n');\n \n case 'usage-display':\n return `📊 Usage Stats:\\n` +\n `📥 Prompt: ${component.stats.promptTokens.toLocaleString()} tokens\\n` +\n `📤 Completion: ${component.stats.completionTokens.toLocaleString()} tokens\\n` +\n `📊 Total: ${component.stats.totalTokens.toLocaleString()} tokens`;\n \n case 'session-list':\n return component.sessions.map(s => \n `${s.isActive ? '▶️' : ' '} ${s.key} (${s.messageCount} messages)`\n ).join('\\n');\n \n case 'text-input':\n return component.placeholder || 'Enter text...';\n \n default:\n return '[UI Component]';\n }\n }\n}\n\n/**\n * Create a command context from dependencies\n */\nexport function createCommandContext(deps: CommandContextDeps): CommandContext {\n return new CommandContextImpl(deps);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"context.js","names":[],"sources":["../../../src/chat-commands/context.ts"],"sourcesContent":["/**\n * Command Context Implementation\n * \n * Provides the concrete implementation of CommandContext interface,\n * bridging commands to core services (AgentService, SessionStore, etc.)\n */\n\nimport type {\n CommandContext,\n BtwQueryOptions,\n CommandStreamEvent,\n ReplyOptions,\n UIComponent,\n SessionInfo,\n ModelInfo,\n UsageStats,\n PlatformFeature,\n MessageSource,\n CompactSessionResult,\n} from './types.js';\nimport { getSessionDisplayName } from './session-key.js';\nimport type { Config } from '../config/schema.js';\nimport { getAgentDefaultModelRef } from '../config/schema.js';\nimport type { AgentMessage } from '@earendil-works/pi-agent-core';\nimport type { MessageBus } from '../infra/bus/index.js';\nimport type { SessionStore, SessionConfigStore } from '../session/index.js';\nimport { createLogger } from '../utils/logger.js';\nimport { saveConfig } from '../config/loader.js';\nimport type { ThinkLevel, ReasoningLevel, VerboseLevel } from '../agent/transcript/thinking-types.js';\nimport { mkdir, writeFile } from 'fs/promises';\nimport { join } from 'path';\nimport { effectiveWorkspacePathForSession } from '../session/session-workspace.js';\nimport { wrapMarkdownExportAsHtml } from '../session/chat-export.js';\nimport type { CompactionResult } from '../agent/memory/compaction.js';\nimport type { PersistentGoalApis } from '../agent/goals/persistent-goal-apis.js';\nimport type { WorkflowRunServiceLike } from '../workflows/service/workflow-run-service.types.js';\nimport { getProjectForSession } from '../projects/workspace.js';\n\nconst log = createLogger('CommandContext');\n\nexport interface CommandContextDeps {\n sessionKey: string;\n source: MessageSource;\n channelId: string;\n chatId: string;\n senderId: string;\n isGroup: boolean;\n accountId?: string;\n threadId?: string;\n config: Config;\n bus: MessageBus;\n sessionStore: SessionStore;\n sessionConfigStore?: SessionConfigStore;\n /** After persisting session thinking, sync pi-agent in-memory state */\n applySessionThinkingLevel?: (sessionKey: string, level: ThinkLevel) => void;\n // Callbacks for platform-specific operations\n replyHandler: (text: string, options?: ReplyOptions) => Promise<void>;\n componentHandler?: (component: UIComponent) => Promise<void>;\n typingHandler?: (typing: boolean) => Promise<void>;\n supportedFeatures: PlatformFeature[];\n /** Called after session files are removed so in-memory agents match disk */\n invalidateAgentSession?: (sessionKey: string) => void;\n /** Reset session in place (archive + new session id); optional — falls back to clearSession */\n resetSession?: (sessionKey: string) => Promise<void>;\n // Model management (optional, will be injected)\n getCurrentModel?: () => string;\n switchModel?: (modelId: string) => Promise<boolean>;\n listModels?: () => Promise<ModelInfo[]>;\n getUsage?: () => Promise<UsageStats>;\n /** Stop current LLM turn and clear channel preview stream (Telegram draft, etc.) */\n abortCurrentTurn?: () => Promise<void>;\n /** Reload skills from disk and refresh active agent prompts. */\n reloadSkills?: () => Promise<void>;\n /** Install a managed skill from an explicit source and refresh active agent prompts. */\n installSkillFromSource?: CommandContext['installSkillFromSource'];\n\n compactSession?: (\n sessionKey: string,\n options?: { instructions?: string; force?: boolean },\n ) => Promise<CompactionResult>;\n\n btwQuery?: (\n sessionKey: string,\n question: string,\n options?: BtwQueryOptions,\n ) => Promise<{ text: string; error?: string }>;\n\n emitEvent?: (event: CommandStreamEvent) => void | Promise<void>;\n\n getSessionContextReport?: (\n sessionKey: string,\n mode: 'list' | 'detail' | 'json',\n ) => Promise<string>;\n\n persistentGoalApis?: PersistentGoalApis;\n workflowRunService?: WorkflowRunServiceLike;\n}\n\nexport class CommandContextImpl implements CommandContext {\n readonly sessionKey: string;\n readonly source: MessageSource;\n readonly channelId: string;\n readonly chatId: string;\n readonly senderId: string;\n readonly isGroup: boolean;\n readonly accountId?: string;\n readonly threadId?: string;\n readonly config: Config;\n readonly abortCurrentTurn?: () => Promise<void>;\n readonly reloadSkills?: () => Promise<void>;\n readonly installSkillFromSource?: CommandContext['installSkillFromSource'];\n readonly persistentGoalApis?: PersistentGoalApis;\n readonly workflowRunApis?: CommandContext['workflowRunApis'];\n\n private deps: CommandContextDeps;\n\n constructor(deps: CommandContextDeps) {\n this.sessionKey = deps.sessionKey;\n this.source = deps.source;\n this.channelId = deps.channelId;\n this.chatId = deps.chatId;\n this.senderId = deps.senderId;\n this.isGroup = deps.isGroup;\n this.accountId = deps.accountId;\n this.threadId = deps.threadId;\n this.config = deps.config;\n this.deps = deps;\n\n if (deps.abortCurrentTurn) {\n const run = deps.abortCurrentTurn;\n this.abortCurrentTurn = async () => {\n await run();\n };\n }\n\n if (deps.reloadSkills) {\n const run = deps.reloadSkills;\n this.reloadSkills = async () => {\n await run();\n };\n }\n\n if (deps.installSkillFromSource) {\n this.installSkillFromSource = deps.installSkillFromSource;\n }\n\n this.persistentGoalApis = deps.persistentGoalApis;\n if (deps.workflowRunService) {\n this.workflowRunApis = {\n service: deps.workflowRunService,\n startWorkflowRun: (params) => deps.workflowRunService!.startWorkflowRun(params),\n };\n }\n }\n\n private outboundMetadata(extra?: Record<string, unknown>): Record<string, unknown> {\n return {\n accountId: this.deps.accountId,\n threadId: this.deps.threadId,\n ...extra,\n };\n }\n\n // === Reply API ===\n\n async reply(text: string, options?: ReplyOptions): Promise<void> {\n await this.deps.replyHandler(text, options);\n }\n\n async replyComponent(component: UIComponent): Promise<void> {\n if (this.deps.componentHandler) {\n await this.deps.componentHandler(component);\n } else {\n // Fallback to text representation\n await this.reply(this.renderComponentAsText(component));\n }\n }\n\n async setTyping(typing: boolean): Promise<void> {\n if (this.deps.typingHandler) {\n await this.deps.typingHandler(typing);\n }\n }\n\n // === Session Management ===\n\n async getSession(): Promise<AgentMessage[]> {\n return this.deps.sessionStore.load(this.sessionKey);\n }\n\n async resetSession(): Promise<void> {\n if (this.deps.resetSession) {\n await this.deps.resetSession(this.sessionKey);\n } else if (typeof this.deps.sessionStore.reset === 'function') {\n const outcome = await this.deps.sessionStore.reset(this.sessionKey);\n if (!outcome) {\n throw new Error('Session not found');\n }\n this.deps.invalidateAgentSession?.(this.sessionKey);\n } else {\n await this.clearSession();\n return;\n }\n\n await this.deps.bus.publishOutbound({\n channel: this.source,\n chat_id: this.chatId,\n content: '✅ New session started. Previous transcript archived; model and session overrides kept.',\n type: 'message',\n metadata: this.outboundMetadata(),\n });\n\n log.info({ sessionKey: this.sessionKey }, 'Session reset');\n }\n\n async clearSession(): Promise<void> {\n // Archive first if has messages\n const messages = await this.getSession();\n if (messages.length > 0) {\n await this.deps.sessionStore.archive(this.sessionKey);\n log.info({ sessionKey: this.sessionKey, messageCount: messages.length }, 'Session archived');\n }\n\n // Delete session\n await this.deps.sessionStore.deleteSession(this.sessionKey);\n this.deps.invalidateAgentSession?.(this.sessionKey);\n\n // Publish outbound message to confirm\n await this.deps.bus.publishOutbound({\n channel: this.source,\n chat_id: this.chatId,\n content: '✅ Session cleared.',\n type: 'message',\n metadata: this.outboundMetadata(),\n });\n\n log.info({ sessionKey: this.sessionKey }, 'Session cleared');\n }\n\n async archiveSession(): Promise<void> {\n await this.deps.sessionStore.archive(this.sessionKey);\n log.info({ sessionKey: this.sessionKey }, 'Session archived');\n }\n\n async listSessions(): Promise<SessionInfo[]> {\n // TODO: Implement listSessions in SessionStore\n // For now, return current session only\n const messages = await this.getSession();\n return [{\n key: this.sessionKey,\n name: getSessionDisplayName(this.sessionKey),\n messageCount: messages.length,\n createdAt: new Date(),\n updatedAt: new Date(),\n isActive: true,\n }];\n }\n\n async switchSession(sessionKey: string): Promise<void> {\n // This is mainly for CLI/Web UI where you can switch between sessions\n // For Telegram, each chat has its own session\n log.info({ from: this.sessionKey, to: sessionKey }, 'Session switch requested');\n \n // Note: In the current architecture, switching session means\n // the next message will use a different sessionKey\n // The actual switch happens at the adapter level\n }\n\n // === Model Management ===\n\n getCurrentModel(): string {\n if (this.deps.getCurrentModel) {\n return this.deps.getCurrentModel();\n }\n \n return getAgentDefaultModelRef(this.config);\n }\n\n async listModels(): Promise<ModelInfo[]> {\n if (this.deps.listModels) {\n return this.deps.listModels();\n }\n \n // Fallback to empty list\n return [];\n }\n\n async switchModel(modelId: string): Promise<boolean> {\n if (this.deps.switchModel) {\n return this.deps.switchModel(modelId);\n }\n \n // No model manager available\n await this.reply('❌ Model switching not available in this context.');\n return false;\n }\n\n async getUsage(): Promise<UsageStats> {\n if (this.deps.getUsage) {\n return this.deps.getUsage();\n }\n \n // Fallback: calculate from session\n const messages = await this.getSession();\n let promptTokens = 0;\n let completionTokens = 0;\n \n for (const msg of messages) {\n if ('usage' in msg && msg.usage) {\n promptTokens += msg.usage.input || 0;\n completionTokens += msg.usage.output || 0;\n }\n }\n \n return {\n promptTokens,\n completionTokens,\n totalTokens: promptTokens + completionTokens,\n messageCount: messages.length,\n };\n }\n\n // === Platform Features ===\n\n supports(feature: PlatformFeature): boolean {\n return this.deps.supportedFeatures.includes(feature);\n }\n\n // === Configuration ===\n\n getConfig(): Config {\n return this.config;\n }\n\n async updateConfig(path: string, value: unknown): Promise<boolean> {\n try {\n // Update config object using path\n const keys = path.split('.');\n let target: Record<string, unknown> = this.config as Record<string, unknown>;\n \n for (let i = 0; i < keys.length - 1; i++) {\n const key = keys[i];\n if (!(key in target) || typeof target[key] !== 'object' || target[key] === null) {\n target[key] = {};\n }\n target = target[key] as Record<string, unknown>;\n }\n \n target[keys[keys.length - 1]] = value;\n \n // Save to disk\n await saveConfig(this.config);\n \n log.info({ path, value }, 'Config updated via command');\n return true;\n } catch (error) {\n log.error({ err: error, path, value }, 'Failed to update config');\n return false;\n }\n }\n\n // === Private Helpers ===\n\n // === Thinking Configuration ===\n\n /**\n * Get the session config store (if available)\n */\n getSessionConfigStore(): SessionConfigStore | undefined {\n return this.deps.sessionConfigStore;\n }\n\n /**\n * Get current thinking level (session override or default)\n */\n async getThinkingLevel(): Promise<ThinkLevel | undefined> {\n const configStore = this.deps.sessionConfigStore;\n if (configStore) {\n const sessionConfig = await configStore.get(this.sessionKey);\n if (sessionConfig?.thinkingLevel) {\n return sessionConfig.thinkingLevel;\n }\n }\n return undefined;\n }\n\n /**\n * Set thinking level for this session\n */\n async setThinkingLevel(level: ThinkLevel): Promise<void> {\n const configStore = this.deps.sessionConfigStore;\n if (configStore) {\n await configStore.update(this.sessionKey, { thinkingLevel: level });\n }\n this.deps.applySessionThinkingLevel?.(this.sessionKey, level);\n }\n\n syncAgentThinkingLevel(level: ThinkLevel): void {\n this.deps.applySessionThinkingLevel?.(this.sessionKey, level);\n }\n\n async compactSession(options?: { instructions?: string; force?: boolean }): Promise<CompactSessionResult | null> {\n if (!this.deps.compactSession) {\n return null;\n }\n const r = await this.deps.compactSession(this.sessionKey, options);\n return {\n compacted: r.compacted,\n tokensBefore: r.tokensBefore,\n tokensAfter: r.tokensAfter,\n summary: r.summary,\n };\n }\n\n async btwQuery(question: string, options?: BtwQueryOptions): Promise<{ text: string; error?: string }> {\n if (!this.deps.btwQuery) {\n return { text: '', error: 'Side questions are not available in this environment.' };\n }\n return this.deps.btwQuery(this.sessionKey, question, options);\n }\n\n emitEvent(event: CommandStreamEvent): void | Promise<void> {\n return this.deps.emitEvent?.(event);\n }\n\n async exportSessionToWorkspace(format: 'markdown' | 'html' | 'json'): Promise<{ path: string }> {\n const exportFmt = format === 'json' ? 'json' : 'markdown';\n let body = await this.deps.sessionStore.exportSession(this.sessionKey, exportFmt);\n if (format === 'html') {\n body = wrapMarkdownExportAsHtml(`Session ${this.sessionKey}`, body);\n }\n const sc = this.deps.sessionConfigStore\n ? await this.deps.sessionConfigStore.get(this.sessionKey)\n : null;\n const root = effectiveWorkspacePathForSession(\n this.config,\n this.sessionKey,\n sc,\n getProjectForSession(this.sessionKey),\n );\n const dir = join(root, 'exports');\n await mkdir(dir, { recursive: true });\n const safe = this.sessionKey.replace(/[^a-zA-Z0-9._-]+/g, '_').slice(0, 96);\n const ext = format === 'json' ? 'json' : format === 'html' ? 'html' : 'md';\n const name = `session-${safe}-${Date.now()}.${ext}`;\n const outPath = join(dir, name);\n await writeFile(outPath, body, 'utf-8');\n return { path: outPath };\n }\n\n async agentContextReport(mode: 'list' | 'detail' | 'json' = 'list'): Promise<string> {\n if (!this.deps.getSessionContextReport) {\n return 'Context report is not available in this environment.';\n }\n return this.deps.getSessionContextReport(this.sessionKey, mode);\n }\n\n /**\n * Get current reasoning level (session override or default)\n */\n async getReasoningLevel(): Promise<ReasoningLevel | undefined> {\n const configStore = this.deps.sessionConfigStore;\n if (configStore) {\n const sessionConfig = await configStore.get(this.sessionKey);\n if (sessionConfig?.reasoningLevel) {\n return sessionConfig.reasoningLevel;\n }\n }\n return undefined;\n }\n\n /**\n * Set reasoning level for this session\n */\n async setReasoningLevel(level: ReasoningLevel): Promise<void> {\n const configStore = this.deps.sessionConfigStore;\n if (configStore) {\n await configStore.update(this.sessionKey, { reasoningLevel: level });\n }\n }\n\n /**\n * Get current verbose level (session override or default)\n */\n async getVerboseLevel(): Promise<VerboseLevel | undefined> {\n const configStore = this.deps.sessionConfigStore;\n if (configStore) {\n const sessionConfig = await configStore.get(this.sessionKey);\n if (sessionConfig?.verboseLevel) {\n return sessionConfig.verboseLevel;\n }\n }\n return undefined;\n }\n\n /**\n * Set verbose level for this session\n */\n async setVerboseLevel(level: VerboseLevel): Promise<void> {\n const configStore = this.deps.sessionConfigStore;\n if (configStore) {\n await configStore.update(this.sessionKey, { verboseLevel: level });\n }\n }\n\n private renderComponentAsText(component: UIComponent): string {\n switch (component.type) {\n case 'buttons':\n return component.buttons.map(b => `[${b.text}]`).join(' ');\n \n case 'select':\n return component.options.map(o => `- ${o.label}`).join('\\n');\n \n case 'model-picker':\n return component.providers.map(p => \n `**${p.name}**\\n${p.models.map(m => ` - ${m.name}`).join('\\n')}`\n ).join('\\n\\n');\n \n case 'usage-display':\n return `📊 Usage Stats:\\n` +\n `📥 Prompt: ${component.stats.promptTokens.toLocaleString()} tokens\\n` +\n `📤 Completion: ${component.stats.completionTokens.toLocaleString()} tokens\\n` +\n `📊 Total: ${component.stats.totalTokens.toLocaleString()} tokens`;\n \n case 'session-list':\n return component.sessions.map(s => \n `${s.isActive ? '▶️' : ' '} ${s.key} (${s.messageCount} messages)`\n ).join('\\n');\n \n case 'text-input':\n return component.placeholder || 'Enter text...';\n \n default:\n return '[UI Component]';\n }\n }\n}\n\n/**\n * Create a command context from dependencies\n */\nexport function createCommandContext(deps: CommandContextDeps): CommandContext {\n return new CommandContextImpl(deps);\n}\n"],"mappings":"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"}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Provides a platform-agnostic command system that works across
|
|
5
5
|
* Telegram, Feishu, Web UI, CLI, and other channel surfaces.
|
|
6
6
|
*/
|
|
7
|
-
export type { MessageSource, UnifiedMessage, PlatformMetadata, MessageAttachment, CommandDefinition, CommandCategory, CommandScope, CommandHandler, CommandResult, CommandContext, ReplyOptions, UIComponent, ButtonGroup, SelectMenu, ModelPicker, UsageDisplay, SessionList, TextInput, ProviderInfo, ModelInfo, UsageStats, SessionInfo, PlatformFeature, ChannelAdapter, ReplyPayload, } from './types.js';
|
|
7
|
+
export type { MessageSource, UnifiedMessage, PlatformMetadata, MessageAttachment, CommandDefinition, CommandCategory, CommandScope, CommandHandler, CommandResult, CommandStreamEvent, BtwQueryOptions, CommandContext, ReplyOptions, UIComponent, ButtonGroup, SelectMenu, ModelPicker, UsageDisplay, SessionList, TextInput, ProviderInfo, ModelInfo, UsageStats, SessionInfo, PlatformFeature, ChannelAdapter, ReplyPayload, } from './types.js';
|
|
8
8
|
export { generateSessionKey, getSessionDisplayName, type SessionKeyContext } from './session-key.js';
|
|
9
9
|
export { normalizeTelegramCommandName, parseSlashCommand } from './command-parse.js';
|
|
10
10
|
export { CommandRegistry, commandRegistry } from './registry.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/chat-commands/index.ts"],"sourcesContent":["/**\n * Unified Command System\n *\n * Provides a platform-agnostic command system that works across\n * Telegram, Feishu, Web UI, CLI, and other channel surfaces.\n */\n\n// Built-in Commands (import first to avoid circular deps)\nimport { registerSessionCommands } from './builtins/session.js';\nimport { registerModelCommands } from './builtins/model.js';\nimport { registerSystemCommands } from './builtins/system.js';\nimport { registerTTSCommands } from './builtins/tts.js';\nimport { registerThinkingCommands } from './builtins/thinking.js';\nimport { registerConfigCommand } from './builtins/config.js';\nimport { registerContextCommands } from './builtins/context.js';\nimport { registerGoalCommand } from './builtins/goal.js';\nimport { registerSubgoalCommand } from './builtins/subgoal.js';\nimport { registerWorkflowCommands } from './builtins/workflow.js';\nimport { registerReviewCommand } from './builtins/review.js';\nimport { registerSkillCommand } from './builtins/skill.js';\nimport { registerAgentEditCommand } from './agent-edit.js';\n\n// Types\nexport type {\n MessageSource,\n UnifiedMessage,\n PlatformMetadata,\n MessageAttachment,\n CommandDefinition,\n CommandCategory,\n CommandScope,\n CommandHandler,\n CommandResult,\n CommandContext,\n ReplyOptions,\n UIComponent,\n ButtonGroup,\n SelectMenu,\n ModelPicker,\n UsageDisplay,\n SessionList,\n TextInput,\n ProviderInfo,\n ModelInfo,\n UsageStats,\n SessionInfo,\n PlatformFeature,\n ChannelAdapter,\n ReplyPayload,\n} from './types.js';\n\n// Session display helpers\nexport { generateSessionKey, getSessionDisplayName, type SessionKeyContext } from './session-key.js';\n\n// Command parsing helpers\nexport { normalizeTelegramCommandName, parseSlashCommand } from './command-parse.js';\n\n// Registry\nexport { CommandRegistry, commandRegistry } from './registry.js';\nexport type { CommandRegistry as CommandRegistryType } from './types.js';\n\n// Context\nexport { CommandContextImpl, createCommandContext } from './context.js';\n\n// Built-in Commands\nexport { registerSessionCommands } from './builtins/session.js';\nexport { registerModelCommands } from './builtins/model.js';\nexport { registerSystemCommands } from './builtins/system.js';\nexport { registerTTSCommands } from './builtins/tts.js';\nexport { registerThinkingCommands } from './builtins/thinking.js';\nexport { registerConfigCommand } from './builtins/config.js';\nexport { registerContextCommands } from './builtins/context.js';\nexport { registerGoalCommand } from './builtins/goal.js';\nexport { registerSubgoalCommand } from './builtins/subgoal.js';\nexport { registerWorkflowCommands } from './builtins/workflow.js';\nexport { registerReviewCommand } from './builtins/review.js';\nexport { registerSkillCommand } from './builtins/skill.js';\nexport { registerAgentEditCommand } from './agent-edit.js';\n\n/**\n * Initialize the command system with all built-in commands\n */\nexport function initializeCommands(): void {\n registerSessionCommands();\n registerModelCommands();\n registerSystemCommands();\n registerConfigCommand();\n registerContextCommands();\n registerTTSCommands();\n registerThinkingCommands();\n registerGoalCommand();\n registerSubgoalCommand();\n registerWorkflowCommands();\n registerReviewCommand();\n registerSkillCommand();\n registerAgentEditCommand();\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/chat-commands/index.ts"],"sourcesContent":["/**\n * Unified Command System\n *\n * Provides a platform-agnostic command system that works across\n * Telegram, Feishu, Web UI, CLI, and other channel surfaces.\n */\n\n// Built-in Commands (import first to avoid circular deps)\nimport { registerSessionCommands } from './builtins/session.js';\nimport { registerModelCommands } from './builtins/model.js';\nimport { registerSystemCommands } from './builtins/system.js';\nimport { registerTTSCommands } from './builtins/tts.js';\nimport { registerThinkingCommands } from './builtins/thinking.js';\nimport { registerConfigCommand } from './builtins/config.js';\nimport { registerContextCommands } from './builtins/context.js';\nimport { registerGoalCommand } from './builtins/goal.js';\nimport { registerSubgoalCommand } from './builtins/subgoal.js';\nimport { registerWorkflowCommands } from './builtins/workflow.js';\nimport { registerReviewCommand } from './builtins/review.js';\nimport { registerSkillCommand } from './builtins/skill.js';\nimport { registerAgentEditCommand } from './agent-edit.js';\n\n// Types\nexport type {\n MessageSource,\n UnifiedMessage,\n PlatformMetadata,\n MessageAttachment,\n CommandDefinition,\n CommandCategory,\n CommandScope,\n CommandHandler,\n CommandResult,\n CommandStreamEvent,\n BtwQueryOptions,\n CommandContext,\n ReplyOptions,\n UIComponent,\n ButtonGroup,\n SelectMenu,\n ModelPicker,\n UsageDisplay,\n SessionList,\n TextInput,\n ProviderInfo,\n ModelInfo,\n UsageStats,\n SessionInfo,\n PlatformFeature,\n ChannelAdapter,\n ReplyPayload,\n} from './types.js';\n\n// Session display helpers\nexport { generateSessionKey, getSessionDisplayName, type SessionKeyContext } from './session-key.js';\n\n// Command parsing helpers\nexport { normalizeTelegramCommandName, parseSlashCommand } from './command-parse.js';\n\n// Registry\nexport { CommandRegistry, commandRegistry } from './registry.js';\nexport type { CommandRegistry as CommandRegistryType } from './types.js';\n\n// Context\nexport { CommandContextImpl, createCommandContext } from './context.js';\n\n// Built-in Commands\nexport { registerSessionCommands } from './builtins/session.js';\nexport { registerModelCommands } from './builtins/model.js';\nexport { registerSystemCommands } from './builtins/system.js';\nexport { registerTTSCommands } from './builtins/tts.js';\nexport { registerThinkingCommands } from './builtins/thinking.js';\nexport { registerConfigCommand } from './builtins/config.js';\nexport { registerContextCommands } from './builtins/context.js';\nexport { registerGoalCommand } from './builtins/goal.js';\nexport { registerSubgoalCommand } from './builtins/subgoal.js';\nexport { registerWorkflowCommands } from './builtins/workflow.js';\nexport { registerReviewCommand } from './builtins/review.js';\nexport { registerSkillCommand } from './builtins/skill.js';\nexport { registerAgentEditCommand } from './agent-edit.js';\n\n/**\n * Initialize the command system with all built-in commands\n */\nexport function initializeCommands(): void {\n registerSessionCommands();\n registerModelCommands();\n registerSystemCommands();\n registerConfigCommand();\n registerContextCommands();\n registerTTSCommands();\n registerThinkingCommands();\n registerGoalCommand();\n registerSubgoalCommand();\n registerWorkflowCommands();\n registerReviewCommand();\n registerSkillCommand();\n registerAgentEditCommand();\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAoFA,SAAgB,qBAA2B;AACzC,0BAAyB;AACzB,wBAAuB;AACvB,yBAAwB;AACxB,wBAAuB;AACvB,0BAAyB;AACzB,sBAAqB;AACrB,2BAA0B;AAC1B,sBAAqB;AACrB,yBAAwB;AACxB,2BAA0B;AAC1B,wBAAuB;AACvB,uBAAsB;AACtB,2BAA0B"}
|
|
@@ -104,6 +104,15 @@ export interface CommandResult {
|
|
|
104
104
|
/** Additional metadata */
|
|
105
105
|
metadata?: Record<string, unknown>;
|
|
106
106
|
}
|
|
107
|
+
export type CommandStreamEvent = {
|
|
108
|
+
type: string;
|
|
109
|
+
[key: string]: unknown;
|
|
110
|
+
};
|
|
111
|
+
export type BtwQueryOptions = {
|
|
112
|
+
maxTokens?: number;
|
|
113
|
+
temperature?: number;
|
|
114
|
+
modelRef?: string;
|
|
115
|
+
};
|
|
107
116
|
export interface CommandContext {
|
|
108
117
|
/** Unified session key */
|
|
109
118
|
sessionKey: string;
|
|
@@ -191,10 +200,12 @@ export interface CommandContext {
|
|
|
191
200
|
force?: boolean;
|
|
192
201
|
}): Promise<CompactSessionResult | null>;
|
|
193
202
|
/** Side question: LLM answer without appending to the session transcript. */
|
|
194
|
-
btwQuery?(question: string): Promise<{
|
|
203
|
+
btwQuery?(question: string, options?: BtwQueryOptions): Promise<{
|
|
195
204
|
text: string;
|
|
196
205
|
error?: string;
|
|
197
206
|
}>;
|
|
207
|
+
/** Optional stream bridge for rich surfaces such as webchat/TUI. */
|
|
208
|
+
emitEvent?(event: CommandStreamEvent): void | Promise<void>;
|
|
198
209
|
/** Write session transcript to workspace `exports/` (markdown, html, or json). */
|
|
199
210
|
exportSessionToWorkspace?(format: 'markdown' | 'html' | 'json'): Promise<{
|
|
200
211
|
path: string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { init_agent_scope, normalizeAgentId, resolveAgentWorkspaceDir } from "../agent/agent-scope.js";
|
|
2
1
|
import { resolveEffectiveAgentManifest } from "../agent-manifest/resolver.js";
|
|
3
2
|
import "../agent-manifest/index.js";
|
|
3
|
+
import { init_agent_scope, normalizeAgentId, resolveAgentWorkspaceDir } from "../agent/agent-scope.js";
|
|
4
4
|
import { init_session_key, parseSessionKey } from "../routing/session-key.js";
|
|
5
5
|
import { agentExists, getDefaultAgentId, init_resolve_route } from "../routing/resolve-route.js";
|
|
6
6
|
//#region src/config/agent-profile.ts
|
|
@@ -15,6 +15,11 @@ export type GatewayAgentRow = {
|
|
|
15
15
|
name?: string;
|
|
16
16
|
description?: string;
|
|
17
17
|
language?: string;
|
|
18
|
+
role?: string;
|
|
19
|
+
responsibilities?: {
|
|
20
|
+
primary: string[];
|
|
21
|
+
secondary?: string[];
|
|
22
|
+
};
|
|
18
23
|
/** Value from `IDENTITY.md` **Avatar:** line when present (may be URL, `xopc:…`, etc.). */
|
|
19
24
|
avatar?: string;
|
|
20
25
|
workspace: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { init_agent_scope, listAgentEntries, normalizeAgentId, resolveAgentDir, resolveAgentProfileDir, resolveAgentWorkspaceDir, resolveDefaultAgentId, resolveUserPath, validateAgentIdForNewAgent } from "../agent/agent-scope.js";
|
|
2
1
|
import { resolveEffectiveAgentManifest } from "../agent-manifest/resolver.js";
|
|
2
|
+
import { init_agent_scope, listAgentEntries, normalizeAgentId, resolveAgentDir, resolveAgentProfileDir, resolveAgentWorkspaceDir, resolveDefaultAgentId, resolveUserPath, validateAgentIdForNewAgent } from "../agent/agent-scope.js";
|
|
3
3
|
import { resolveEffectiveAgentProfile } from "../config/agent-profile.js";
|
|
4
4
|
import { WORKSPACE_FILES, init_paths, resolveUserProfilePath } from "../config/paths.js";
|
|
5
5
|
import { AGENT_PROFILE_MARKDOWN_SYSTEM_FILES, REQUIRED_AGENT_PROFILE_MARKDOWN_FILE_SET } from "../agent/context/workspace.js";
|
|
@@ -77,6 +77,11 @@ async function listGatewayAgents(cfg, _options = {}) {
|
|
|
77
77
|
...identity.name ? { name: identity.name } : {},
|
|
78
78
|
...identity.description ? { description: identity.description } : {},
|
|
79
79
|
...identity.language ? { language: identity.language } : {},
|
|
80
|
+
...profile.manifest.identity.role ? { role: profile.manifest.identity.role } : {},
|
|
81
|
+
responsibilities: {
|
|
82
|
+
primary: [...profile.manifest.responsibilities.primary],
|
|
83
|
+
...profile.manifest.responsibilities.secondary ? { secondary: [...profile.manifest.responsibilities.secondary] } : {}
|
|
84
|
+
},
|
|
80
85
|
...identity.avatar ? { avatar: identity.avatar } : {},
|
|
81
86
|
workspace: profile.resolvedWorkspacePath,
|
|
82
87
|
profileDir: resolveAgentProfileDir(cfg, id),
|