wind-agent-cli 1.37.2 → 1.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +718 -718
- package/README.md +17 -2
- package/dist/api-docs.html +901 -901
- package/dist/assets/{CodeEditor-B2Wf7Fg0.js → CodeEditor-FvgsnJ7J.js} +6 -6
- package/dist/assets/FileTree-NLULrBDi.js +1 -0
- package/dist/assets/GitPanel-51KUBbEW.js +4 -0
- package/dist/assets/{MarkdownPreview-C0tqYnVu.js → MarkdownPreview-PEEWVWeS.js} +17 -27
- package/dist/assets/Shell-BEx66cW_.js +76 -0
- package/dist/assets/Shell-DrlLKa8f.css +1 -0
- package/dist/assets/StandaloneShell-BkuUCPZP.js +1 -0
- package/dist/assets/TaskMasterPanel-DfQG80SF.js +2 -0
- package/dist/assets/abnfDiagram-N423BO3Z-Cc0ul87U.js +1 -0
- package/dist/assets/arc-BAuugHw_.js +1 -0
- package/dist/assets/architectureDiagram-T3A2C74G-DA3G7QZD.js +36 -0
- package/dist/assets/{blockDiagram-VBNYF7ZC-DpaEf3mv.js → blockDiagram-VBNYF7ZC-tJ12IJs3.js} +4 -4
- package/dist/assets/c4Diagram-5PPSVZJV-5BTMCXL4.js +10 -0
- package/dist/assets/channel-BGDgNNQz.js +1 -0
- package/dist/assets/{chunk-2GRJ4B5K-DoSdNo8u.js → chunk-2GRJ4B5K-BPaI06Ou.js} +1 -1
- package/dist/assets/{chunk-2Q5K7J3B-CE7I3uMn.js → chunk-2Q5K7J3B-HV4zmsEe.js} +1 -1
- package/dist/assets/chunk-5RXB4S5H-BAwutJoU.js +231 -0
- package/dist/assets/{chunk-5VM5RSS4-B55XgqHZ.js → chunk-5VM5RSS4-B9QAJPrv.js} +1 -1
- package/dist/assets/{chunk-6Q2QTUOP-BS1KpefW.js → chunk-6Q2QTUOP-BOIW8bzI.js} +1 -1
- package/dist/assets/chunk-GF5L2VYU-DD13o00-.js +206 -0
- package/dist/assets/{chunk-JWPE2WC7-DOtkNhMm.js → chunk-JWPE2WC7-CFBm23VK.js} +1 -1
- package/dist/assets/{chunk-KBJHAD2P-BXLjviqM.js → chunk-KBJHAD2P-C0i0v8Op.js} +1 -1
- package/dist/assets/{chunk-RYQCIY6F-C50fjNNz.js → chunk-RYQCIY6F-DfeK0taV.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-QBYqCkb7.js → chunk-XXDRQBXY-BKWvf-QE.js} +1 -1
- package/dist/assets/classDiagram-JCYQIIEL-C2SNM4oy.js +1 -0
- package/dist/assets/classDiagram-v2-OCEON4UE-C2SNM4oy.js +1 -0
- package/dist/assets/cose-bilkent-JH36ORCC-CBE8oM7L.js +1 -0
- package/dist/assets/{cynefin-VYW2F7L2-DnJa_OlQ.js → cynefin-VYW2F7L2-DziGp35A.js} +11 -23
- package/dist/assets/cynefinDiagram-MW4NZA55-CNvtHl39.js +62 -0
- package/dist/assets/cytoscape.esm-yzknjiTM.js +321 -0
- package/dist/assets/dagre-VZM6K2ZE-Bz9lsU7m.js +4 -0
- package/dist/assets/{diagram-7IWD3JNH-pFzs9HEu.js → diagram-7IWD3JNH-CV7D3VSd.js} +5 -5
- package/dist/assets/{diagram-B4RE2ZJO-CcehQC7x.js → diagram-B4RE2ZJO-Dj4EjDNC.js} +2 -2
- package/dist/assets/diagram-LBJQPF4R-wJ6FHvf-.js +24 -0
- package/dist/assets/diagram-Q27KOJAE-DMFYMhOB.js +24 -0
- package/dist/assets/diagram-UB23O5K3-DeBSbWAI.js +41 -0
- package/dist/assets/ebnfDiagram-BXEA7PRR-2NnJSGhT.js +1 -0
- package/dist/assets/erDiagram-JOGREHBK-DrSlg8hr.js +85 -0
- package/dist/assets/feature-chat-CvOCyaLe.js +88 -0
- package/dist/assets/feature-workspace-CAB6bgPy.js +39 -0
- package/dist/assets/{flowDiagram-UKHOOZJN-CJV8fQPR.js → flowDiagram-UKHOOZJN-C0pejU6W.js} +4 -4
- package/dist/assets/ganttDiagram-PKOTCBZU-BM0VCUUz.js +292 -0
- package/dist/assets/gitGraphDiagram-DS77QQ5N-SbLG0c5_.js +106 -0
- package/dist/assets/graph-DOmOIIwC.js +1 -0
- package/dist/assets/{index-GFhXZyl2.js → index-BflGfU1C.js} +3 -3
- package/dist/assets/index-Btkf0_rU.js +1 -0
- package/dist/assets/index-Cr5MbHuj.css +1 -0
- package/dist/assets/index-Gqazqfs3.js +1 -0
- package/dist/assets/index-nyrkoQK6.js +35 -0
- package/dist/assets/infoDiagram-6WML65LV-Cf5DSa66.js +2 -0
- package/dist/assets/ishikawaDiagram-WSZJBQD7-DwyFutKy.js +70 -0
- package/dist/assets/journeyDiagram-NVQOT4AX-Df0FRhLo.js +139 -0
- package/dist/assets/{kanban-definition-27J2QSJJ-B3iL0ztJ.js → kanban-definition-27J2QSJJ-HmpbjrZz.js} +6 -6
- package/dist/assets/{layout-D6BstSzF.js → layout-D-LzfAck.js} +1 -1
- package/dist/assets/{linear-Cwe9dEKA.js → linear-DOM01LBe.js} +1 -1
- package/dist/assets/map-DxJ2ADlA.js +1 -0
- package/dist/assets/mermaid.core-C2Ma-_S6.js +306 -0
- package/dist/assets/{mindmap-definition-FAOFIHXS-BgfBYMzr.js → mindmap-definition-FAOFIHXS-B4GOIn76.js} +4 -4
- package/dist/assets/pegDiagram-VL7TDLO6-BHur7aH2.js +1 -0
- package/dist/assets/{pieDiagram-7S7Q4E2Y-DeSrxc4I.js → pieDiagram-7S7Q4E2Y-Cw0UELLV.js} +2 -2
- package/dist/assets/quadrantDiagram-CIZ2JOQS-CB2BrX3g.js +7 -0
- package/dist/assets/railroadDiagram-AXF67PYL-DPYZYmLX.js +1 -0
- package/dist/assets/requirementDiagram-LRYGKXZP-DSX4IJ1n.js +84 -0
- package/dist/assets/sankeyDiagram-W5VNT64P-TenC3ZQo.js +40 -0
- package/dist/assets/sequenceDiagram-SI44F4Z6-DltGsf3F.js +162 -0
- package/dist/assets/sizeCapture-X5ZJPWSS-D7llwbJZ.js +1 -0
- package/dist/assets/stateDiagram-OKZ733FA-C-hAvbnY.js +1 -0
- package/dist/assets/stateDiagram-v2-UEYNNEHI-DaYSFcRQ.js +1 -0
- package/dist/assets/swimlanes-SLNWSIFB-DWD30fv6.js +2 -0
- package/dist/assets/swimlanesDiagram-ULZ7WXOC-LTkF2T6t.js +8 -0
- package/dist/assets/timeline-definition-Z64GVDOM-quWmimSE.js +120 -0
- package/dist/assets/vendor-data-DsEZeNPr.js +4 -0
- package/dist/assets/vendor-i18n-B6HjLCnN.js +1 -0
- package/dist/assets/vendor-markdown-DkITEES2.js +298 -0
- package/dist/assets/vendor-react-C2GTiUCy.js +10 -0
- package/dist/assets/vendor-ui-CAUHSABu.js +49 -0
- package/dist/assets/vennDiagram-T6HMQDX7-aUQcm8h5.js +34 -0
- package/dist/assets/{wardleyDiagram-T6FBY63Y-BNCpsFZV.js → wardleyDiagram-T6FBY63Y-BZ0bwsFQ.js} +2 -2
- package/dist/assets/workbox-window.prod.es5-BBnX5xw4.js +2 -0
- package/dist/assets/xychartDiagram-ELKLHX3M-DOVzAq63.js +7 -0
- package/dist/clear-cache.html +85 -85
- package/dist/convert-icons.md +52 -52
- package/dist/favicon.png +0 -0
- package/dist/favicon.svg +8 -8
- package/dist/generate-icons.js +48 -48
- package/dist/icons/codex-white.svg +3 -3
- package/dist/icons/codex.svg +3 -3
- package/dist/icons/cursor-white.svg +11 -11
- package/dist/icons/icon-128x128.png +0 -0
- package/dist/icons/icon-128x128.svg +11 -11
- package/dist/icons/icon-144x144.png +0 -0
- package/dist/icons/icon-144x144.svg +11 -11
- package/dist/icons/icon-152x152.png +0 -0
- package/dist/icons/icon-152x152.svg +11 -11
- package/dist/icons/icon-180x180.png +0 -0
- package/dist/icons/icon-192x192.png +0 -0
- package/dist/icons/icon-192x192.svg +11 -11
- package/dist/icons/icon-384x384.png +0 -0
- package/dist/icons/icon-384x384.svg +11 -11
- package/dist/icons/icon-512x512.png +0 -0
- package/dist/icons/icon-512x512.svg +11 -11
- package/dist/icons/icon-72x72.png +0 -0
- package/dist/icons/icon-72x72.svg +11 -11
- package/dist/icons/icon-96x96.png +0 -0
- package/dist/icons/icon-96x96.svg +11 -11
- package/dist/icons/icon-maskable-192x192.png +0 -0
- package/dist/icons/icon-maskable-512x512.png +0 -0
- package/dist/icons/icon-template.svg +11 -11
- package/dist/icons/new-icon.png +0 -0
- package/dist/index.html +48 -58
- package/dist/logo-128.png +0 -0
- package/dist/logo-256.png +0 -0
- package/dist/logo.svg +17 -17
- package/dist/manifest.webmanifest +1 -0
- package/dist/push-sw.js +48 -0
- package/dist/screenshots/setup-screen-mobile.png +0 -0
- package/dist/screenshots/setup-screen.png +0 -0
- package/dist/sw.js +1 -124
- package/dist/workbox-ae5c0bb2.js +1 -0
- package/dist-server/server/index.js +1 -1
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/load-env.js +4 -2
- package/dist-server/server/load-env.js.map +1 -1
- package/dist-server/server/modules/agent/agent.routes.js +1 -1
- package/dist-server/server/modules/agent/agent.routes.js.map +1 -1
- package/dist-server/server/modules/browser-use/browser-use.service.js +1 -1
- package/dist-server/server/modules/browser-use/browser-use.service.js.map +1 -1
- package/dist-server/server/modules/cli/cli.service.js +37 -37
- package/dist-server/server/modules/cli/cli.service.js.map +1 -1
- package/dist-server/server/modules/cli/sandbox.service.js +43 -43
- package/dist-server/server/modules/cli/sandbox.service.js.map +1 -1
- package/dist-server/server/modules/cli/tests/cli-environment-bootstrap.test.js +74 -74
- package/dist-server/server/modules/commands/commands.routes.js +25 -25
- package/dist-server/server/modules/commands/commands.routes.js.map +1 -1
- package/dist-server/server/modules/commands/tests/commands.test.js +1 -1
- package/dist-server/server/modules/commands/tests/commands.test.js.map +1 -1
- package/dist-server/server/modules/database/connection.js +1 -1
- package/dist-server/server/modules/database/connection.js.map +1 -1
- package/dist-server/server/modules/database/migrations.js +190 -190
- package/dist-server/server/modules/database/repositories/api-keys.js +3 -3
- package/dist-server/server/modules/database/repositories/github-tokens.js +2 -2
- package/dist-server/server/modules/database/repositories/notification-channel-endpoints.js +28 -28
- package/dist-server/server/modules/database/repositories/notification-preferences.js +4 -4
- package/dist-server/server/modules/database/repositories/projects.db.js +61 -61
- package/dist-server/server/modules/database/repositories/provider-models.js +35 -35
- package/dist-server/server/modules/database/repositories/push-subscriptions.js +5 -5
- package/dist-server/server/modules/database/repositories/scan-state.db.js +5 -5
- package/dist-server/server/modules/database/repositories/sessions.db.js +93 -93
- package/dist-server/server/modules/database/schema.js +177 -177
- package/dist-server/server/modules/database/tests/projects.db.integration.test.js +3 -2
- package/dist-server/server/modules/database/tests/projects.db.integration.test.js.map +1 -1
- package/dist-server/server/modules/database/tests/provider-models.db.integration.test.js +6 -6
- package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js +4 -3
- package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js.map +1 -1
- package/dist-server/server/modules/database/tests/sessions.db.integration.test.js +2 -1
- package/dist-server/server/modules/database/tests/sessions.db.integration.test.js.map +1 -1
- package/dist-server/server/modules/git/git.routes.js +17 -17
- package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js +1 -1
- package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js.map +1 -1
- package/dist-server/server/modules/notifications/vapid-keys.service.js +1 -1
- package/dist-server/server/modules/notifications/vapid-keys.service.js.map +1 -1
- package/dist-server/server/modules/projects/tests/project-management.service.test.js +2 -1
- package/dist-server/server/modules/projects/tests/project-management.service.test.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-app-server.client.js +2 -2
- package/dist-server/server/modules/providers/list/codex/codex-app-server.client.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-models.provider.js +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-models.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor-models.provider.js +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor-models.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js +13 -13
- package/dist-server/server/modules/providers/list/opencode/opencode-runtime.provider.js +9 -9
- package/dist-server/server/modules/providers/list/opencode/opencode-runtime.provider.test.js +17 -17
- package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js +25 -25
- package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js +27 -27
- package/dist-server/server/modules/providers/services/provider-token-usage.service.js +9 -9
- package/dist-server/server/modules/providers/services/sessions.service.js +1 -1
- package/dist-server/server/modules/providers/tests/claude-auth.test.js +8 -0
- package/dist-server/server/modules/providers/tests/claude-auth.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/codex-sessions.test.js +1 -1
- package/dist-server/server/modules/providers/tests/codex-sessions.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/mcp.test.js +3 -3
- package/dist-server/server/modules/providers/tests/opencode-sessions.test.js +98 -98
- package/dist-server/server/modules/providers/tests/opencode-sessions.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js +18 -18
- package/dist-server/server/modules/providers/tests/provider.routes.test.js +1 -1
- package/dist-server/server/modules/providers/tests/provider.routes.test.js.map +1 -1
- package/dist-server/server/modules/system/system.service.js +1 -1
- package/dist-server/server/modules/system/system.service.js.map +1 -1
- package/dist-server/server/modules/system/tests/system.service.test.js +1 -1
- package/dist-server/server/modules/system/tests/system.service.test.js.map +1 -1
- package/dist-server/server/modules/taskmaster/taskmaster.routes.js +423 -422
- package/dist-server/server/modules/taskmaster/taskmaster.routes.js.map +1 -1
- package/dist-server/server/modules/websocket/services/shell-websocket.service.js +1 -1
- package/dist-server/server/modules/websocket/services/shell-websocket.service.js.map +1 -1
- package/dist-server/server/modules/worktrees/services/worktree-list.service.js +1 -1
- package/dist-server/server/modules/worktrees/services/worktree-open.service.js +1 -1
- package/dist-server/server/modules/worktrees/services/worktree-remove.service.js +1 -1
- package/electron/cloud.js +260 -260
- package/electron/desktopNotifications.js +378 -378
- package/electron/desktopWindow.js +766 -766
- package/electron/launcher/index.html +14 -14
- package/electron/launcher/launcher.css +801 -801
- package/electron/launcher/launcher.js +687 -687
- package/electron/localServer.js +549 -549
- package/electron/main.js +944 -944
- package/electron/preload.cjs +60 -60
- package/electron/scripts/generate-macos-icon.js +62 -62
- package/electron/serverInstaller.js +277 -277
- package/electron/tabs.js +87 -87
- package/electron/viewHost.js +331 -331
- package/package.json +262 -247
- package/public/api-docs.html +901 -901
- package/scripts/build-client.mjs +30 -0
- package/scripts/fix-node-pty.js +67 -67
- package/scripts/generate-pwa-icons.ps1 +33 -0
- package/scripts/promote-dist-server.mjs +50 -50
- package/scripts/release/build-server-bundle.js +176 -176
- package/scripts/release/prepare-desktop-app.js +152 -152
- package/scripts/verify-pwa-build.mjs +37 -0
- package/server/index.ts +399 -399
- package/server/load-env.ts +49 -47
- package/server/modules/agent/agent.module.ts +52 -52
- package/server/modules/agent/agent.routes.ts +1287 -1287
- package/server/modules/agent/index.ts +2 -2
- package/server/modules/agent/tests/agent.routes.test.ts +205 -205
- package/server/modules/assets/assets.routes.ts +150 -150
- package/server/modules/assets/index.ts +3 -3
- package/server/modules/assets/services/image-assets.service.ts +127 -127
- package/server/modules/assets/tests/image-assets.service.test.ts +74 -74
- package/server/modules/auth/auth.middleware.ts +163 -163
- package/server/modules/auth/auth.module.ts +38 -38
- package/server/modules/auth/auth.routes.ts +57 -57
- package/server/modules/auth/auth.service.ts +155 -155
- package/server/modules/auth/index.ts +9 -9
- package/server/modules/auth/tests/auth.service.test.ts +95 -95
- package/server/modules/browser-use/browser-use-mcp.routes.ts +120 -120
- package/server/modules/browser-use/browser-use-mcp.ts +386 -386
- package/server/modules/browser-use/browser-use-runtime.ts +10 -10
- package/server/modules/browser-use/browser-use.routes.ts +96 -96
- package/server/modules/browser-use/browser-use.service.ts +836 -836
- package/server/modules/browser-use/index.ts +11 -11
- package/server/modules/browser-use/tests/browser-use.service.test.ts +10 -10
- package/server/modules/cli/cli.module.ts +93 -93
- package/server/modules/cli/cli.service.ts +248 -248
- package/server/modules/cli/cli.ts +19 -19
- package/server/modules/cli/index.ts +2 -2
- package/server/modules/cli/sandbox.service.ts +398 -398
- package/server/modules/cli/tests/cli-environment-bootstrap.test.ts +186 -186
- package/server/modules/cli/tests/cli.service.test.ts +86 -86
- package/server/modules/cli/tests/sandbox.service.test.ts +58 -58
- package/server/modules/commands/commands.module.ts +22 -22
- package/server/modules/commands/commands.routes.ts +587 -587
- package/server/modules/commands/index.ts +2 -2
- package/server/modules/commands/tests/commands.test.ts +110 -110
- package/server/modules/database/connection.ts +144 -143
- package/server/modules/database/index.ts +17 -17
- package/server/modules/database/init-db.ts +17 -17
- package/server/modules/database/migrations.ts +515 -515
- package/server/modules/database/repositories/api-keys.ts +119 -119
- package/server/modules/database/repositories/app-config.ts +53 -53
- package/server/modules/database/repositories/credentials.ts +106 -106
- package/server/modules/database/repositories/github-tokens.ts +100 -100
- package/server/modules/database/repositories/notification-channel-endpoints.ts +153 -153
- package/server/modules/database/repositories/notification-preferences.ts +117 -117
- package/server/modules/database/repositories/projects.db.ts +196 -196
- package/server/modules/database/repositories/provider-models.ts +168 -168
- package/server/modules/database/repositories/push-subscriptions.ts +80 -80
- package/server/modules/database/repositories/scan-state.db.ts +42 -42
- package/server/modules/database/repositories/sessions.db.ts +524 -524
- package/server/modules/database/repositories/users.ts +140 -140
- package/server/modules/database/repositories/vapid-keys.ts +57 -57
- package/server/modules/database/schema.ts +210 -210
- package/server/modules/database/tests/projects.db.integration.test.ts +73 -72
- package/server/modules/database/tests/provider-models.db.integration.test.ts +125 -125
- package/server/modules/database/tests/sessions-provider-mapping.test.ts +110 -109
- package/server/modules/database/tests/sessions.db.integration.test.ts +117 -116
- package/server/modules/file-tree/file-tree.module.ts +116 -116
- package/server/modules/file-tree/file-tree.routes.ts +258 -258
- package/server/modules/file-tree/file-tree.service.ts +675 -675
- package/server/modules/file-tree/index.ts +2 -2
- package/server/modules/file-tree/tests/file-tree.routes.test.ts +157 -157
- package/server/modules/file-tree/tests/file-tree.service.test.ts +371 -371
- package/server/modules/git/git-branch.service.ts +43 -43
- package/server/modules/git/git-parsing.service.ts +66 -66
- package/server/modules/git/git.module.ts +22 -22
- package/server/modules/git/git.routes.ts +1596 -1596
- package/server/modules/git/index.ts +2 -2
- package/server/modules/git/tests/git-branch.service.test.ts +60 -60
- package/server/modules/git/tests/git-init.routes.test.ts +132 -132
- package/server/modules/git/tests/git.test.ts +106 -106
- package/server/modules/notifications/index.ts +24 -24
- package/server/modules/notifications/notifications.routes.ts +127 -127
- package/server/modules/notifications/services/desktop-notification-clients.service.ts +124 -124
- package/server/modules/notifications/services/notification-orchestrator.service.js +310 -310
- package/server/modules/notifications/tests/notification-orchestrator.integration.test.ts +55 -55
- package/server/modules/notifications/vapid-keys.service.ts +38 -38
- package/server/modules/notifications/websocket/desktop-notifications-websocket.service.ts +109 -109
- package/server/modules/plugins/index.ts +9 -9
- package/server/modules/plugins/plugin-process.service.ts +217 -217
- package/server/modules/plugins/plugin-registry.service.ts +459 -459
- package/server/modules/plugins/plugins.module.ts +33 -33
- package/server/modules/plugins/plugins.routes.ts +66 -66
- package/server/modules/plugins/plugins.service.ts +147 -147
- package/server/modules/plugins/tests/plugins.service.test.ts +32 -32
- package/server/modules/projects/index.ts +11 -11
- package/server/modules/projects/projects.routes.ts +273 -273
- package/server/modules/projects/services/project-clone.service.ts +323 -323
- package/server/modules/projects/services/project-delete.service.ts +90 -90
- package/server/modules/projects/services/project-management.service.ts +144 -144
- package/server/modules/projects/services/project-star.service.ts +78 -78
- package/server/modules/projects/services/projects-has-taskmaster.service.ts +248 -248
- package/server/modules/projects/services/projects-with-sessions-fetch.service.ts +314 -314
- package/server/modules/projects/tests/project-clone.service.test.ts +183 -183
- package/server/modules/projects/tests/project-management.service.test.ts +118 -117
- package/server/modules/projects/tests/project-star.service.test.ts +123 -123
- package/server/modules/projects/tests/projects-has-taskmaster.service.test.ts +105 -105
- package/server/modules/providers/README.md +380 -380
- package/server/modules/providers/index.ts +10 -10
- package/server/modules/providers/list/claude/claude-auth.provider.ts +160 -160
- package/server/modules/providers/list/claude/claude-mcp.provider.ts +135 -135
- package/server/modules/providers/list/claude/claude-models.provider.ts +294 -294
- package/server/modules/providers/list/claude/claude-runtime.provider.js +1068 -1068
- package/server/modules/providers/list/claude/claude-session-synchronizer.provider.ts +203 -203
- package/server/modules/providers/list/claude/claude-sessions.provider.ts +684 -684
- package/server/modules/providers/list/claude/claude-skills.provider.ts +265 -265
- package/server/modules/providers/list/claude/claude.provider.ts +30 -30
- package/server/modules/providers/list/codex/codex-app-server.client.ts +2 -2
- package/server/modules/providers/list/codex/codex-auth.provider.ts +100 -100
- package/server/modules/providers/list/codex/codex-mcp.provider.ts +135 -135
- package/server/modules/providers/list/codex/codex-models.provider.ts +123 -123
- package/server/modules/providers/list/codex/codex-runtime.provider.js +533 -533
- package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +203 -203
- package/server/modules/providers/list/codex/codex-sessions.provider.ts +938 -938
- package/server/modules/providers/list/codex/codex-skills.provider.ts +73 -73
- package/server/modules/providers/list/codex/codex.provider.ts +28 -28
- package/server/modules/providers/list/cursor/cursor-auth.provider.ts +143 -143
- package/server/modules/providers/list/cursor/cursor-mcp.provider.ts +108 -108
- package/server/modules/providers/list/cursor/cursor-models.provider.ts +119 -119
- package/server/modules/providers/list/cursor/cursor-runtime.provider.js +385 -385
- package/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.ts +158 -159
- package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +654 -654
- package/server/modules/providers/list/cursor/cursor-skills.provider.ts +39 -39
- package/server/modules/providers/list/cursor/cursor.provider.ts +30 -30
- package/server/modules/providers/list/opencode/opencode-auth.provider.ts +110 -110
- package/server/modules/providers/list/opencode/opencode-mcp.provider.ts +228 -228
- package/server/modules/providers/list/opencode/opencode-models.provider.ts +203 -203
- package/server/modules/providers/list/opencode/opencode-runtime.provider.js +434 -434
- package/server/modules/providers/list/opencode/opencode-runtime.provider.test.js +261 -261
- package/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts +179 -179
- package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +500 -500
- package/server/modules/providers/list/opencode/opencode-skills.provider.ts +78 -78
- package/server/modules/providers/list/opencode/opencode.provider.ts +30 -30
- package/server/modules/providers/provider.registry.ts +36 -36
- package/server/modules/providers/provider.routes.ts +877 -877
- package/server/modules/providers/services/mcp.service.ts +109 -109
- package/server/modules/providers/services/provider-auth.service.ts +26 -26
- package/server/modules/providers/services/provider-capabilities.service.ts +97 -97
- package/server/modules/providers/services/provider-models.service.ts +401 -401
- package/server/modules/providers/services/provider-runtime.service.ts +108 -108
- package/server/modules/providers/services/provider-token-usage.service.ts +357 -357
- package/server/modules/providers/services/session-conversations-search.service.ts +1321 -1321
- package/server/modules/providers/services/session-synchronizer.service.ts +74 -74
- package/server/modules/providers/services/sessions-watcher.service.ts +247 -247
- package/server/modules/providers/services/sessions.service.ts +471 -471
- package/server/modules/providers/services/skills.service.ts +39 -39
- package/server/modules/providers/shared/base/abstract.provider.ts +32 -32
- package/server/modules/providers/shared/mcp/mcp.provider.ts +151 -151
- package/server/modules/providers/shared/skills/skills.provider.ts +287 -287
- package/server/modules/providers/tests/claude-auth.test.ts +157 -150
- package/server/modules/providers/tests/claude-sessions.test.ts +61 -61
- package/server/modules/providers/tests/codex-sessions.test.ts +231 -231
- package/server/modules/providers/tests/mcp.test.ts +349 -349
- package/server/modules/providers/tests/opencode-models.test.ts +41 -41
- package/server/modules/providers/tests/opencode-sessions.test.ts +522 -522
- package/server/modules/providers/tests/provider-attachment-history.test.ts +247 -247
- package/server/modules/providers/tests/provider-models.service.test.ts +353 -353
- package/server/modules/providers/tests/provider-runtime.service.test.ts +138 -138
- package/server/modules/providers/tests/provider-token-usage.service.test.ts +186 -186
- package/server/modules/providers/tests/provider.routes.test.ts +244 -244
- package/server/modules/providers/tests/sessions-details.test.ts +73 -73
- package/server/modules/providers/tests/sessions.service.test.ts +127 -127
- package/server/modules/providers/tests/skills.test.ts +692 -692
- package/server/modules/settings/index.ts +2 -2
- package/server/modules/settings/settings.module.ts +50 -50
- package/server/modules/settings/settings.routes.ts +51 -51
- package/server/modules/settings/settings.service.ts +186 -186
- package/server/modules/settings/tests/settings.service.test.ts +54 -54
- package/server/modules/system/index.ts +2 -2
- package/server/modules/system/system.module.ts +62 -62
- package/server/modules/system/system.routes.ts +21 -21
- package/server/modules/system/system.service.ts +76 -76
- package/server/modules/system/tests/system.service.test.ts +118 -118
- package/server/modules/taskmaster/index.ts +2 -2
- package/server/modules/taskmaster/taskmaster.module.ts +24 -24
- package/server/modules/taskmaster/taskmaster.routes.ts +1483 -1482
- package/server/modules/taskmaster/taskmaster.service.ts +132 -132
- package/server/modules/taskmaster/tests/taskmaster.routes.test.ts +144 -144
- package/server/modules/taskmaster/tests/taskmaster.service.test.ts +97 -97
- package/server/modules/user/index.ts +2 -2
- package/server/modules/user/tests/user.service.test.ts +61 -61
- package/server/modules/user/user.module.ts +58 -58
- package/server/modules/user/user.routes.ts +50 -50
- package/server/modules/user/user.service.ts +85 -85
- package/server/modules/voice/index.ts +2 -2
- package/server/modules/voice/tests/voice.service.test.ts +98 -98
- package/server/modules/voice/voice.module.ts +47 -47
- package/server/modules/voice/voice.routes.ts +113 -113
- package/server/modules/voice/voice.service.ts +192 -192
- package/server/modules/websocket/README.md +273 -273
- package/server/modules/websocket/index.ts +3 -3
- package/server/modules/websocket/services/chat-run-registry.service.ts +343 -343
- package/server/modules/websocket/services/chat-session-writer.service.ts +145 -145
- package/server/modules/websocket/services/chat-websocket.service.ts +425 -425
- package/server/modules/websocket/services/plugin-websocket-proxy.service.ts +65 -65
- package/server/modules/websocket/services/shell-websocket.service.ts +503 -503
- package/server/modules/websocket/services/websocket-auth.service.ts +59 -59
- package/server/modules/websocket/services/websocket-server.service.ts +124 -124
- package/server/modules/websocket/services/websocket-state.service.ts +16 -16
- package/server/modules/websocket/services/websocket-writer.service.ts +38 -38
- package/server/modules/websocket/tests/chat-attachment-filter.test.ts +59 -59
- package/server/modules/websocket/tests/chat-run-registry.test.ts +282 -282
- package/server/modules/websocket/tests/shell-websocket.service.test.ts +100 -100
- package/server/modules/websocket/tests/websocket-heartbeat.service.test.ts +78 -78
- package/server/modules/worktrees/index.ts +2 -2
- package/server/modules/worktrees/services/worktree-create-and-open.service.ts +53 -53
- package/server/modules/worktrees/services/worktree-create.service.ts +104 -104
- package/server/modules/worktrees/services/worktree-git.service.ts +207 -207
- package/server/modules/worktrees/services/worktree-list.service.ts +143 -143
- package/server/modules/worktrees/services/worktree-merge.service.ts +157 -157
- package/server/modules/worktrees/services/worktree-open.service.ts +85 -85
- package/server/modules/worktrees/services/worktree-remove.service.ts +88 -88
- package/server/modules/worktrees/tests/worktree-create-and-open.service.test.ts +68 -68
- package/server/modules/worktrees/tests/worktree-create.service.test.ts +142 -142
- package/server/modules/worktrees/tests/worktree-git.service.test.ts +84 -84
- package/server/modules/worktrees/tests/worktree-list.service.test.ts +45 -45
- package/server/modules/worktrees/tests/worktree-merge.service.test.ts +213 -213
- package/server/modules/worktrees/tests/worktree-open.service.test.ts +122 -122
- package/server/modules/worktrees/tests/worktree-remove.service.test.ts +227 -227
- package/server/modules/worktrees/tests/worktrees.routes.test.ts +199 -199
- package/server/modules/worktrees/worktrees.module.ts +113 -113
- package/server/modules/worktrees/worktrees.routes.ts +116 -116
- package/server/shared/claude-cli-path.ts +139 -139
- package/server/shared/frontmatter.ts +18 -18
- package/server/shared/image-attachments.ts +435 -435
- package/server/shared/interfaces.ts +179 -179
- package/server/shared/tests/claude-cli-path.test.ts +61 -61
- package/server/shared/tests/image-attachments.test.ts +353 -353
- package/server/shared/tests/slice-tail-page.test.ts +42 -42
- package/server/shared/types.ts +1228 -1228
- package/server/shared/utils.ts +1161 -1161
- package/server/tsconfig.json +40 -40
- package/shared/networkHosts.js +22 -22
- package/dist/assets/FileTree-N2IkkHlv.js +0 -127
- package/dist/assets/GitPanel-DZCeuFZ2.js +0 -29
- package/dist/assets/PluginTabContent-Bnuf-fb8.js +0 -1
- package/dist/assets/Shell-BfFnMZ-f.js +0 -81
- package/dist/assets/Shell-qxJ8_QYu.css +0 -32
- package/dist/assets/StandaloneShell-QmyYwNAl.js +0 -1
- package/dist/assets/TaskMasterPanel-fMsmvN9L.js +0 -32
- package/dist/assets/abnfDiagram-N423BO3Z-DjPU7HLs.js +0 -1
- package/dist/assets/arc-Pdq6Tvr3.js +0 -1
- package/dist/assets/architectureDiagram-T3A2C74G-D0JnT0Vh.js +0 -36
- package/dist/assets/c4Diagram-5PPSVZJV-MpqlPGFi.js +0 -10
- package/dist/assets/channel-Bm0ZLE4z.js +0 -1
- package/dist/assets/chunk-5RXB4S5H-B4UQ8snD.js +0 -231
- package/dist/assets/chunk-GF5L2VYU-DkEPedJP.js +0 -206
- package/dist/assets/classDiagram-JCYQIIEL-DqEOcNxw.js +0 -1
- package/dist/assets/classDiagram-v2-OCEON4UE-DqEOcNxw.js +0 -1
- package/dist/assets/code-xml-VK1YfShP.js +0 -6
- package/dist/assets/cose-bilkent-JH36ORCC-DrVTo1Nh.js +0 -1
- package/dist/assets/cynefinDiagram-MW4NZA55-CCxBjw9E.js +0 -62
- package/dist/assets/cytoscape.esm-C10L9Icf.js +0 -331
- package/dist/assets/dagre-VZM6K2ZE-C_GvE4lu.js +0 -4
- package/dist/assets/diagram-LBJQPF4R-hfKcrrxe.js +0 -24
- package/dist/assets/diagram-Q27KOJAE-C1semJfK.js +0 -24
- package/dist/assets/diagram-UB23O5K3-57hvXQXO.js +0 -41
- package/dist/assets/ebnfDiagram-BXEA7PRR-9KIAlAVw.js +0 -1
- package/dist/assets/erDiagram-JOGREHBK-B7OzJ767.js +0 -85
- package/dist/assets/ganttDiagram-PKOTCBZU-CbMNWWQu.js +0 -292
- package/dist/assets/gitGraphDiagram-DS77QQ5N-I_vZ5waO.js +0 -106
- package/dist/assets/graph-Cx-EqisW.js +0 -1
- package/dist/assets/index-DFnVoqpx.js +0 -1174
- package/dist/assets/index-DlSeAkw_.js +0 -11
- package/dist/assets/index-ZHwIojzi.js +0 -1
- package/dist/assets/index-t1w8KNxn.css +0 -1
- package/dist/assets/infoDiagram-6WML65LV-Dh20Tsbq.js +0 -2
- package/dist/assets/ishikawaDiagram-WSZJBQD7-DH2Sm3ko.js +0 -70
- package/dist/assets/journeyDiagram-NVQOT4AX-BNFQqlVu.js +0 -139
- package/dist/assets/map-CV9uif0g.js +0 -1
- package/dist/assets/mermaid.core-BYRQSshi.js +0 -312
- package/dist/assets/pegDiagram-VL7TDLO6-CNYpECr8.js +0 -1
- package/dist/assets/quadrantDiagram-CIZ2JOQS-BXVVQsa6.js +0 -7
- package/dist/assets/railroadDiagram-AXF67PYL-JTroIW8F.js +0 -1
- package/dist/assets/requirementDiagram-LRYGKXZP-C1Z_inLz.js +0 -84
- package/dist/assets/sankeyDiagram-W5VNT64P-DCJgKFUk.js +0 -40
- package/dist/assets/sequenceDiagram-SI44F4Z6-D8Wrx5Vc.js +0 -162
- package/dist/assets/sizeCapture-X5ZJPWSS-CXqD68vD.js +0 -1
- package/dist/assets/stateDiagram-OKZ733FA-CF0Ekqjo.js +0 -1
- package/dist/assets/stateDiagram-v2-UEYNNEHI-DARLpEFJ.js +0 -1
- package/dist/assets/swimlanes-SLNWSIFB-DVIax_k4.js +0 -2
- package/dist/assets/swimlanesDiagram-ULZ7WXOC-CrgDgaY8.js +0 -8
- package/dist/assets/timeline-definition-Z64GVDOM-DSqk3ApG.js +0 -120
- package/dist/assets/vendor-react-whEHfigN.js +0 -59
- package/dist/assets/vennDiagram-T6HMQDX7-DNbTqyG5.js +0 -34
- package/dist/assets/xychartDiagram-ELKLHX3M-XsVZphHr.js +0 -7
- package/dist/manifest.json +0 -61
|
@@ -1,434 +1,434 @@
|
|
|
1
|
-
import fsSync from 'node:fs';
|
|
2
|
-
|
|
3
|
-
import crossSpawn from 'cross-spawn';
|
|
4
|
-
import Database from 'better-sqlite3';
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
appendFilesInputTag,
|
|
8
|
-
appendImagesInputTag,
|
|
9
|
-
normalizeAttachmentDescriptors
|
|
10
|
-
} from '@/shared/image-attachments.js';
|
|
11
|
-
import { notifyRunFailed, notifyRunStopped } from '@/modules/notifications/index.js';
|
|
12
|
-
import { createCompleteMessage, createNormalizedMessage, flattenPromptForWindowsShell, getOpenCodeDatabasePath } from '@/shared/utils.js';
|
|
13
|
-
|
|
14
|
-
// cross-spawn resolves .cmd shims/PATHEXT on Windows and delegates to
|
|
15
|
-
// child_process.spawn everywhere else.
|
|
16
|
-
const spawnFunction = crossSpawn;
|
|
17
|
-
|
|
18
|
-
const activeOpenCodeProcesses = new Map();
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Maps the UI permission mode onto OpenCode's non-interactive controls.
|
|
22
|
-
*
|
|
23
|
-
* OpenCode has no single "permission mode" flag; each mode uses a different
|
|
24
|
-
* lever of the `opencode run` CLI (verified against v1.17.13):
|
|
25
|
-
* - plan → the built-in read-only `plan` agent (`--agent plan`).
|
|
26
|
-
* - bypassPermissions → `--auto`, which auto-approves every permission that
|
|
27
|
-
* is not explicitly denied in the user's config.
|
|
28
|
-
* - acceptEdits → the OPENCODE_PERMISSION env var, whose JSON body the
|
|
29
|
-
* CLI merges into its permission config. Forcing
|
|
30
|
-
* `edit: allow` guarantees file edits go through while
|
|
31
|
-
* every other rule stays under the user's own config.
|
|
32
|
-
* - default → nothing; the user's opencode.json governs. In
|
|
33
|
-
* non-interactive `run` mode any `ask` rule is denied.
|
|
34
|
-
*
|
|
35
|
-
* Exported for tests only.
|
|
36
|
-
*/
|
|
37
|
-
export function resolveOpenCodePermissionOptions(permissionMode) {
|
|
38
|
-
switch (permissionMode) {
|
|
39
|
-
case 'plan':
|
|
40
|
-
return { args: ['--agent', 'plan'], env: {} };
|
|
41
|
-
case 'bypassPermissions':
|
|
42
|
-
return { args: ['--auto'], env: {} };
|
|
43
|
-
case 'acceptEdits':
|
|
44
|
-
return { args: [], env: { OPENCODE_PERMISSION: JSON.stringify({ edit: 'allow' }) } };
|
|
45
|
-
default:
|
|
46
|
-
return { args: [], env: {} };
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function resolveOpenCodeEffort(model, effort, modelsDefinition) {
|
|
51
|
-
const selectedModel = modelsDefinition?.OPTIONS?.find((option) => option.value === model);
|
|
52
|
-
const allowedEfforts = selectedModel?.effort?.values?.map((value) => value.value) || [];
|
|
53
|
-
return typeof effort === 'string' && effort !== 'default' && allowedEfforts.includes(effort)
|
|
54
|
-
? effort
|
|
55
|
-
: undefined;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function readOpenCodeSessionId(event) {
|
|
59
|
-
if (!event || typeof event !== 'object') {
|
|
60
|
-
return null;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return event.sessionID || event.sessionId || null;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function readOpenCodeTokenUsage(sessionId) {
|
|
67
|
-
const dbPath = getOpenCodeDatabasePath();
|
|
68
|
-
if (!sessionId || !fsSync.existsSync(dbPath)) {
|
|
69
|
-
return null;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
let db = null;
|
|
73
|
-
try {
|
|
74
|
-
db = new Database(dbPath, { readonly: true, fileMustExist: true });
|
|
75
|
-
const columns = db.prepare('PRAGMA table_info(session)').all();
|
|
76
|
-
const columnNames = new Set(columns.map((column) => column.name));
|
|
77
|
-
const requiredColumns = ['tokens_input', 'tokens_output', 'tokens_reasoning', 'tokens_cache_read', 'tokens_cache_write'];
|
|
78
|
-
if (!requiredColumns.every((column) => columnNames.has(column))) {
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const row = db.prepare(`
|
|
83
|
-
SELECT
|
|
84
|
-
tokens_input AS inputTokens,
|
|
85
|
-
tokens_output AS outputTokens,
|
|
86
|
-
tokens_reasoning AS reasoningTokens,
|
|
87
|
-
tokens_cache_read AS cacheReadTokens,
|
|
88
|
-
tokens_cache_write AS cacheWriteTokens
|
|
89
|
-
FROM session
|
|
90
|
-
WHERE id = ?
|
|
91
|
-
`).get(sessionId);
|
|
92
|
-
|
|
93
|
-
if (!row) {
|
|
94
|
-
return null;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const inputTokens = Number(row.inputTokens || 0) + Number(row.cacheReadTokens || 0);
|
|
98
|
-
const outputTokens = Number(row.outputTokens || 0);
|
|
99
|
-
const used = Number(row.inputTokens || 0)
|
|
100
|
-
+ outputTokens
|
|
101
|
-
+ Number(row.reasoningTokens || 0)
|
|
102
|
-
+ Number(row.cacheReadTokens || 0)
|
|
103
|
-
+ Number(row.cacheWriteTokens || 0);
|
|
104
|
-
if (used <= 0) {
|
|
105
|
-
return null;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return {
|
|
109
|
-
used,
|
|
110
|
-
inputTokens,
|
|
111
|
-
outputTokens,
|
|
112
|
-
breakdown: {
|
|
113
|
-
input: inputTokens,
|
|
114
|
-
output: outputTokens,
|
|
115
|
-
},
|
|
116
|
-
};
|
|
117
|
-
} catch {
|
|
118
|
-
return null;
|
|
119
|
-
} finally {
|
|
120
|
-
if (db) {
|
|
121
|
-
db.close();
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
async function spawnOpenCode(command, options = {}, ws, context) {
|
|
127
|
-
return new Promise((resolve, reject) => {
|
|
128
|
-
const {
|
|
129
|
-
sessionId,
|
|
130
|
-
projectPath,
|
|
131
|
-
cwd,
|
|
132
|
-
model,
|
|
133
|
-
effort,
|
|
134
|
-
sessionSummary,
|
|
135
|
-
images,
|
|
136
|
-
files,
|
|
137
|
-
permissionMode
|
|
138
|
-
} = options;
|
|
139
|
-
// Callers pass the stable app session id; the CLI resumes with the
|
|
140
|
-
// provider-native id recorded on the session row.
|
|
141
|
-
const providerSessionId = context.resolveProviderSessionId(sessionId);
|
|
142
|
-
const workingDir = cwd || projectPath || process.cwd();
|
|
143
|
-
// Process-map key: the app session id when the caller supplied one, so
|
|
144
|
-
// abort-by-app-id always works.
|
|
145
|
-
const processKey = sessionId || Date.now().toString();
|
|
146
|
-
let capturedSessionId = providerSessionId;
|
|
147
|
-
let sessionCreatedSent = false;
|
|
148
|
-
let stdoutLineBuffer = '';
|
|
149
|
-
let terminalNotificationSent = false;
|
|
150
|
-
let opencodeProcess = null;
|
|
151
|
-
// Unified lifecycle contract: exactly one terminal `complete` per run
|
|
152
|
-
// (close and error handlers can both fire for spawn failures).
|
|
153
|
-
let completeSent = false;
|
|
154
|
-
|
|
155
|
-
const notifyTerminalState = ({ code = null, error = null } = {}) => {
|
|
156
|
-
if (terminalNotificationSent) {
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
terminalNotificationSent = true;
|
|
161
|
-
// Notifications are app-facing, so they carry the app session id.
|
|
162
|
-
const finalSessionId = sessionId || capturedSessionId || processKey;
|
|
163
|
-
if (code === 0 && !error) {
|
|
164
|
-
notifyRunStopped({
|
|
165
|
-
userId: ws?.userId || null,
|
|
166
|
-
provider: 'opencode',
|
|
167
|
-
sessionId: finalSessionId,
|
|
168
|
-
sessionName: sessionSummary,
|
|
169
|
-
stopReason: 'completed',
|
|
170
|
-
});
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
notifyRunFailed({
|
|
175
|
-
userId: ws?.userId || null,
|
|
176
|
-
provider: 'opencode',
|
|
177
|
-
sessionId: finalSessionId,
|
|
178
|
-
sessionName: sessionSummary,
|
|
179
|
-
error: error || `OpenCode CLI exited with code ${code}`,
|
|
180
|
-
});
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
const registerSession = (nextSessionId) => {
|
|
184
|
-
if (!nextSessionId || capturedSessionId === nextSessionId) {
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
capturedSessionId = nextSessionId;
|
|
189
|
-
// Legacy/direct callers without an app session id re-key the process
|
|
190
|
-
// under the provider-native id once it is known.
|
|
191
|
-
if (!sessionId && processKey !== capturedSessionId && opencodeProcess) {
|
|
192
|
-
activeOpenCodeProcesses.delete(processKey);
|
|
193
|
-
activeOpenCodeProcesses.set(capturedSessionId, opencodeProcess);
|
|
194
|
-
}
|
|
195
|
-
if (opencodeProcess) {
|
|
196
|
-
opencodeProcess.sessionId = capturedSessionId;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
if (ws.setSessionId && typeof ws.setSessionId === 'function') {
|
|
200
|
-
ws.setSessionId(capturedSessionId);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
if (!providerSessionId && !sessionCreatedSent) {
|
|
204
|
-
sessionCreatedSent = true;
|
|
205
|
-
ws.send(createNormalizedMessage({
|
|
206
|
-
kind: 'session_created',
|
|
207
|
-
newSessionId: capturedSessionId,
|
|
208
|
-
sessionId: capturedSessionId,
|
|
209
|
-
provider: 'opencode',
|
|
210
|
-
}));
|
|
211
|
-
}
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
const processOpenCodeOutputLine = (line) => {
|
|
215
|
-
if (!line || !line.trim()) {
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
let response;
|
|
220
|
-
try {
|
|
221
|
-
response = JSON.parse(line);
|
|
222
|
-
} catch {
|
|
223
|
-
ws.send(createNormalizedMessage({
|
|
224
|
-
kind: 'stream_delta',
|
|
225
|
-
content: line,
|
|
226
|
-
sessionId: capturedSessionId || sessionId || null,
|
|
227
|
-
provider: 'opencode',
|
|
228
|
-
}));
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
try {
|
|
233
|
-
registerSession(readOpenCodeSessionId(response));
|
|
234
|
-
const normalized = context.normalizeMessage(response, capturedSessionId || sessionId || null);
|
|
235
|
-
for (const msg of normalized) {
|
|
236
|
-
ws.send(msg);
|
|
237
|
-
}
|
|
238
|
-
} catch (error) {
|
|
239
|
-
const errorContent = error instanceof Error ? error.message : String(error);
|
|
240
|
-
console.error('[OpenCode] Failed to process JSON output:', errorContent);
|
|
241
|
-
ws.send(createNormalizedMessage({
|
|
242
|
-
kind: 'error',
|
|
243
|
-
content: errorContent,
|
|
244
|
-
sessionId: capturedSessionId || sessionId || null,
|
|
245
|
-
provider: 'opencode',
|
|
246
|
-
}));
|
|
247
|
-
}
|
|
248
|
-
};
|
|
249
|
-
|
|
250
|
-
void context.resolveResumeModel(sessionId, model).then(async (resolvedModel) => {
|
|
251
|
-
let effortModels = null;
|
|
252
|
-
try {
|
|
253
|
-
effortModels = await context.getProviderModels();
|
|
254
|
-
} catch (error) {
|
|
255
|
-
console.warn('[OpenCode] Unable to load provider models for effort validation:', error);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
const resolvedEffort = resolveOpenCodeEffort(resolvedModel, effort, effortModels);
|
|
259
|
-
const args = ['run', '--format', 'json'];
|
|
260
|
-
// OpenCode's `run` command owns workspace selection through `--dir`.
|
|
261
|
-
// Relying on the child-process cwd alone is not enough on Linux, where
|
|
262
|
-
// the CLI can still resolve the session under the server install dir.
|
|
263
|
-
args.push('--dir', workingDir);
|
|
264
|
-
if (providerSessionId) {
|
|
265
|
-
args.push('--session', providerSessionId);
|
|
266
|
-
}
|
|
267
|
-
if (resolvedModel) {
|
|
268
|
-
args.push('--model', resolvedModel);
|
|
269
|
-
}
|
|
270
|
-
if (resolvedEffort) {
|
|
271
|
-
args.push('--variant', resolvedEffort);
|
|
272
|
-
}
|
|
273
|
-
const permissionOptions = resolveOpenCodePermissionOptions(permissionMode);
|
|
274
|
-
args.push(...permissionOptions.args);
|
|
275
|
-
const hasAttachments =
|
|
276
|
-
normalizeAttachmentDescriptors(images).length > 0
|
|
277
|
-
|| normalizeAttachmentDescriptors(files).length > 0;
|
|
278
|
-
if ((command && command.trim()) || hasAttachments) {
|
|
279
|
-
// Image attachments ride along as an <images_input> path list appended
|
|
280
|
-
// to the prompt; the session history reader strips the tag back out.
|
|
281
|
-
// opencode is a .cmd shim on Windows, so the whole argument must be
|
|
282
|
-
// newline-free or cmd.exe silently truncates it at the first newline.
|
|
283
|
-
const promptWithAttachments = appendFilesInputTag(
|
|
284
|
-
appendImagesInputTag(command?.trim() || '', images),
|
|
285
|
-
files
|
|
286
|
-
);
|
|
287
|
-
args.push(flattenPromptForWindowsShell(promptWithAttachments));
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
opencodeProcess = spawnFunction('opencode', args, {
|
|
291
|
-
cwd: workingDir,
|
|
292
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
293
|
-
env: { ...process.env, ...permissionOptions.env },
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
activeOpenCodeProcesses.set(processKey, opencodeProcess);
|
|
297
|
-
opencodeProcess.sessionId = processKey;
|
|
298
|
-
opencodeProcess.stdin.end();
|
|
299
|
-
|
|
300
|
-
opencodeProcess.stdout.on('data', (data) => {
|
|
301
|
-
stdoutLineBuffer += data.toString();
|
|
302
|
-
const completeLines = stdoutLineBuffer.split(/\r?\n/);
|
|
303
|
-
stdoutLineBuffer = completeLines.pop() || '';
|
|
304
|
-
|
|
305
|
-
completeLines.forEach((line) => {
|
|
306
|
-
processOpenCodeOutputLine(line.trim());
|
|
307
|
-
});
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
opencodeProcess.stderr.on('data', (data) => {
|
|
311
|
-
const stderrText = data.toString();
|
|
312
|
-
if (!stderrText.trim()) {
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
ws.send(createNormalizedMessage({
|
|
317
|
-
kind: 'error',
|
|
318
|
-
content: stderrText,
|
|
319
|
-
sessionId: capturedSessionId || sessionId || null,
|
|
320
|
-
provider: 'opencode',
|
|
321
|
-
}));
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
opencodeProcess.on('close', async (code) => {
|
|
325
|
-
const finalSessionId = sessionId || capturedSessionId || processKey;
|
|
326
|
-
activeOpenCodeProcesses.delete(finalSessionId);
|
|
327
|
-
activeOpenCodeProcesses.delete(processKey);
|
|
328
|
-
|
|
329
|
-
if (stdoutLineBuffer.trim()) {
|
|
330
|
-
processOpenCodeOutputLine(stdoutLineBuffer.trim());
|
|
331
|
-
stdoutLineBuffer = '';
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
// OpenCode's own database is keyed by the provider-native id.
|
|
335
|
-
const tokenBudget = readOpenCodeTokenUsage(capturedSessionId);
|
|
336
|
-
if (tokenBudget) {
|
|
337
|
-
ws.send(createNormalizedMessage({
|
|
338
|
-
kind: 'status',
|
|
339
|
-
text: 'token_budget',
|
|
340
|
-
tokenBudget,
|
|
341
|
-
sessionId: finalSessionId,
|
|
342
|
-
provider: 'opencode',
|
|
343
|
-
}));
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// Terminal complete — skipped for aborted runs (abort-session
|
|
347
|
-
// already sent the aborted complete on this run's behalf).
|
|
348
|
-
if (!completeSent && !opencodeProcess.aborted) {
|
|
349
|
-
completeSent = true;
|
|
350
|
-
ws.send(createCompleteMessage({ provider: 'opencode', sessionId: finalSessionId, exitCode: code }));
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
if (code === 0) {
|
|
354
|
-
notifyTerminalState({ code });
|
|
355
|
-
resolve();
|
|
356
|
-
return;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
if (code === 127 || code === null) {
|
|
360
|
-
const installed = await context.isProviderInstalled();
|
|
361
|
-
if (!installed) {
|
|
362
|
-
ws.send(createNormalizedMessage({
|
|
363
|
-
kind: 'error',
|
|
364
|
-
content: 'OpenCode CLI is not installed. Install it from https://opencode.ai/docs/',
|
|
365
|
-
sessionId: finalSessionId,
|
|
366
|
-
provider: 'opencode',
|
|
367
|
-
}));
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
notifyTerminalState({ code });
|
|
372
|
-
reject(new Error(code === null ? 'OpenCode CLI process was terminated' : `OpenCode CLI exited with code ${code}`));
|
|
373
|
-
});
|
|
374
|
-
|
|
375
|
-
opencodeProcess.on('error', async (error) => {
|
|
376
|
-
const finalSessionId = sessionId || capturedSessionId || processKey;
|
|
377
|
-
activeOpenCodeProcesses.delete(finalSessionId);
|
|
378
|
-
activeOpenCodeProcesses.delete(processKey);
|
|
379
|
-
|
|
380
|
-
const installed = await context.isProviderInstalled();
|
|
381
|
-
const errorContent = !installed
|
|
382
|
-
? 'OpenCode CLI is not installed. Install it from https://opencode.ai/docs/'
|
|
383
|
-
: error.message;
|
|
384
|
-
|
|
385
|
-
ws.send(createNormalizedMessage({
|
|
386
|
-
kind: 'error',
|
|
387
|
-
content: errorContent,
|
|
388
|
-
sessionId: finalSessionId,
|
|
389
|
-
provider: 'opencode',
|
|
390
|
-
}));
|
|
391
|
-
if (!completeSent && !opencodeProcess.aborted) {
|
|
392
|
-
completeSent = true;
|
|
393
|
-
ws.send(createCompleteMessage({ provider: 'opencode', sessionId: finalSessionId, exitCode: 1 }));
|
|
394
|
-
}
|
|
395
|
-
notifyTerminalState({ error });
|
|
396
|
-
reject(error);
|
|
397
|
-
});
|
|
398
|
-
}).catch(reject);
|
|
399
|
-
});
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
function abortOpenCodeSession(sessionId) {
|
|
403
|
-
const process = activeOpenCodeProcesses.get(sessionId);
|
|
404
|
-
if (!process) {
|
|
405
|
-
return false;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
// The abort handler sends the terminal complete (aborted: true); flag the
|
|
409
|
-
// process so its close handler does not emit a second one.
|
|
410
|
-
process.aborted = true;
|
|
411
|
-
process.kill('SIGTERM');
|
|
412
|
-
activeOpenCodeProcesses.delete(sessionId);
|
|
413
|
-
return true;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
function isOpenCodeSessionActive(sessionId) {
|
|
417
|
-
return activeOpenCodeProcesses.has(sessionId);
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
function getActiveOpenCodeSessions() {
|
|
421
|
-
return Array.from(activeOpenCodeProcesses.keys());
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
export const opencodeRuntime = {
|
|
425
|
-
run: spawnOpenCode,
|
|
426
|
-
abort: abortOpenCodeSession,
|
|
427
|
-
};
|
|
428
|
-
|
|
429
|
-
export {
|
|
430
|
-
spawnOpenCode,
|
|
431
|
-
abortOpenCodeSession,
|
|
432
|
-
isOpenCodeSessionActive,
|
|
433
|
-
getActiveOpenCodeSessions,
|
|
434
|
-
};
|
|
1
|
+
import fsSync from 'node:fs';
|
|
2
|
+
|
|
3
|
+
import crossSpawn from 'cross-spawn';
|
|
4
|
+
import Database from 'better-sqlite3';
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
appendFilesInputTag,
|
|
8
|
+
appendImagesInputTag,
|
|
9
|
+
normalizeAttachmentDescriptors
|
|
10
|
+
} from '@/shared/image-attachments.js';
|
|
11
|
+
import { notifyRunFailed, notifyRunStopped } from '@/modules/notifications/index.js';
|
|
12
|
+
import { createCompleteMessage, createNormalizedMessage, flattenPromptForWindowsShell, getOpenCodeDatabasePath } from '@/shared/utils.js';
|
|
13
|
+
|
|
14
|
+
// cross-spawn resolves .cmd shims/PATHEXT on Windows and delegates to
|
|
15
|
+
// child_process.spawn everywhere else.
|
|
16
|
+
const spawnFunction = crossSpawn;
|
|
17
|
+
|
|
18
|
+
const activeOpenCodeProcesses = new Map();
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Maps the UI permission mode onto OpenCode's non-interactive controls.
|
|
22
|
+
*
|
|
23
|
+
* OpenCode has no single "permission mode" flag; each mode uses a different
|
|
24
|
+
* lever of the `opencode run` CLI (verified against v1.17.13):
|
|
25
|
+
* - plan → the built-in read-only `plan` agent (`--agent plan`).
|
|
26
|
+
* - bypassPermissions → `--auto`, which auto-approves every permission that
|
|
27
|
+
* is not explicitly denied in the user's config.
|
|
28
|
+
* - acceptEdits → the OPENCODE_PERMISSION env var, whose JSON body the
|
|
29
|
+
* CLI merges into its permission config. Forcing
|
|
30
|
+
* `edit: allow` guarantees file edits go through while
|
|
31
|
+
* every other rule stays under the user's own config.
|
|
32
|
+
* - default → nothing; the user's opencode.json governs. In
|
|
33
|
+
* non-interactive `run` mode any `ask` rule is denied.
|
|
34
|
+
*
|
|
35
|
+
* Exported for tests only.
|
|
36
|
+
*/
|
|
37
|
+
export function resolveOpenCodePermissionOptions(permissionMode) {
|
|
38
|
+
switch (permissionMode) {
|
|
39
|
+
case 'plan':
|
|
40
|
+
return { args: ['--agent', 'plan'], env: {} };
|
|
41
|
+
case 'bypassPermissions':
|
|
42
|
+
return { args: ['--auto'], env: {} };
|
|
43
|
+
case 'acceptEdits':
|
|
44
|
+
return { args: [], env: { OPENCODE_PERMISSION: JSON.stringify({ edit: 'allow' }) } };
|
|
45
|
+
default:
|
|
46
|
+
return { args: [], env: {} };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function resolveOpenCodeEffort(model, effort, modelsDefinition) {
|
|
51
|
+
const selectedModel = modelsDefinition?.OPTIONS?.find((option) => option.value === model);
|
|
52
|
+
const allowedEfforts = selectedModel?.effort?.values?.map((value) => value.value) || [];
|
|
53
|
+
return typeof effort === 'string' && effort !== 'default' && allowedEfforts.includes(effort)
|
|
54
|
+
? effort
|
|
55
|
+
: undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function readOpenCodeSessionId(event) {
|
|
59
|
+
if (!event || typeof event !== 'object') {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return event.sessionID || event.sessionId || null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function readOpenCodeTokenUsage(sessionId) {
|
|
67
|
+
const dbPath = getOpenCodeDatabasePath();
|
|
68
|
+
if (!sessionId || !fsSync.existsSync(dbPath)) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let db = null;
|
|
73
|
+
try {
|
|
74
|
+
db = new Database(dbPath, { readonly: true, fileMustExist: true });
|
|
75
|
+
const columns = db.prepare('PRAGMA table_info(session)').all();
|
|
76
|
+
const columnNames = new Set(columns.map((column) => column.name));
|
|
77
|
+
const requiredColumns = ['tokens_input', 'tokens_output', 'tokens_reasoning', 'tokens_cache_read', 'tokens_cache_write'];
|
|
78
|
+
if (!requiredColumns.every((column) => columnNames.has(column))) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const row = db.prepare(`
|
|
83
|
+
SELECT
|
|
84
|
+
tokens_input AS inputTokens,
|
|
85
|
+
tokens_output AS outputTokens,
|
|
86
|
+
tokens_reasoning AS reasoningTokens,
|
|
87
|
+
tokens_cache_read AS cacheReadTokens,
|
|
88
|
+
tokens_cache_write AS cacheWriteTokens
|
|
89
|
+
FROM session
|
|
90
|
+
WHERE id = ?
|
|
91
|
+
`).get(sessionId);
|
|
92
|
+
|
|
93
|
+
if (!row) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const inputTokens = Number(row.inputTokens || 0) + Number(row.cacheReadTokens || 0);
|
|
98
|
+
const outputTokens = Number(row.outputTokens || 0);
|
|
99
|
+
const used = Number(row.inputTokens || 0)
|
|
100
|
+
+ outputTokens
|
|
101
|
+
+ Number(row.reasoningTokens || 0)
|
|
102
|
+
+ Number(row.cacheReadTokens || 0)
|
|
103
|
+
+ Number(row.cacheWriteTokens || 0);
|
|
104
|
+
if (used <= 0) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
used,
|
|
110
|
+
inputTokens,
|
|
111
|
+
outputTokens,
|
|
112
|
+
breakdown: {
|
|
113
|
+
input: inputTokens,
|
|
114
|
+
output: outputTokens,
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
} catch {
|
|
118
|
+
return null;
|
|
119
|
+
} finally {
|
|
120
|
+
if (db) {
|
|
121
|
+
db.close();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function spawnOpenCode(command, options = {}, ws, context) {
|
|
127
|
+
return new Promise((resolve, reject) => {
|
|
128
|
+
const {
|
|
129
|
+
sessionId,
|
|
130
|
+
projectPath,
|
|
131
|
+
cwd,
|
|
132
|
+
model,
|
|
133
|
+
effort,
|
|
134
|
+
sessionSummary,
|
|
135
|
+
images,
|
|
136
|
+
files,
|
|
137
|
+
permissionMode
|
|
138
|
+
} = options;
|
|
139
|
+
// Callers pass the stable app session id; the CLI resumes with the
|
|
140
|
+
// provider-native id recorded on the session row.
|
|
141
|
+
const providerSessionId = context.resolveProviderSessionId(sessionId);
|
|
142
|
+
const workingDir = cwd || projectPath || process.cwd();
|
|
143
|
+
// Process-map key: the app session id when the caller supplied one, so
|
|
144
|
+
// abort-by-app-id always works.
|
|
145
|
+
const processKey = sessionId || Date.now().toString();
|
|
146
|
+
let capturedSessionId = providerSessionId;
|
|
147
|
+
let sessionCreatedSent = false;
|
|
148
|
+
let stdoutLineBuffer = '';
|
|
149
|
+
let terminalNotificationSent = false;
|
|
150
|
+
let opencodeProcess = null;
|
|
151
|
+
// Unified lifecycle contract: exactly one terminal `complete` per run
|
|
152
|
+
// (close and error handlers can both fire for spawn failures).
|
|
153
|
+
let completeSent = false;
|
|
154
|
+
|
|
155
|
+
const notifyTerminalState = ({ code = null, error = null } = {}) => {
|
|
156
|
+
if (terminalNotificationSent) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
terminalNotificationSent = true;
|
|
161
|
+
// Notifications are app-facing, so they carry the app session id.
|
|
162
|
+
const finalSessionId = sessionId || capturedSessionId || processKey;
|
|
163
|
+
if (code === 0 && !error) {
|
|
164
|
+
notifyRunStopped({
|
|
165
|
+
userId: ws?.userId || null,
|
|
166
|
+
provider: 'opencode',
|
|
167
|
+
sessionId: finalSessionId,
|
|
168
|
+
sessionName: sessionSummary,
|
|
169
|
+
stopReason: 'completed',
|
|
170
|
+
});
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
notifyRunFailed({
|
|
175
|
+
userId: ws?.userId || null,
|
|
176
|
+
provider: 'opencode',
|
|
177
|
+
sessionId: finalSessionId,
|
|
178
|
+
sessionName: sessionSummary,
|
|
179
|
+
error: error || `OpenCode CLI exited with code ${code}`,
|
|
180
|
+
});
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const registerSession = (nextSessionId) => {
|
|
184
|
+
if (!nextSessionId || capturedSessionId === nextSessionId) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
capturedSessionId = nextSessionId;
|
|
189
|
+
// Legacy/direct callers without an app session id re-key the process
|
|
190
|
+
// under the provider-native id once it is known.
|
|
191
|
+
if (!sessionId && processKey !== capturedSessionId && opencodeProcess) {
|
|
192
|
+
activeOpenCodeProcesses.delete(processKey);
|
|
193
|
+
activeOpenCodeProcesses.set(capturedSessionId, opencodeProcess);
|
|
194
|
+
}
|
|
195
|
+
if (opencodeProcess) {
|
|
196
|
+
opencodeProcess.sessionId = capturedSessionId;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (ws.setSessionId && typeof ws.setSessionId === 'function') {
|
|
200
|
+
ws.setSessionId(capturedSessionId);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (!providerSessionId && !sessionCreatedSent) {
|
|
204
|
+
sessionCreatedSent = true;
|
|
205
|
+
ws.send(createNormalizedMessage({
|
|
206
|
+
kind: 'session_created',
|
|
207
|
+
newSessionId: capturedSessionId,
|
|
208
|
+
sessionId: capturedSessionId,
|
|
209
|
+
provider: 'opencode',
|
|
210
|
+
}));
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const processOpenCodeOutputLine = (line) => {
|
|
215
|
+
if (!line || !line.trim()) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
let response;
|
|
220
|
+
try {
|
|
221
|
+
response = JSON.parse(line);
|
|
222
|
+
} catch {
|
|
223
|
+
ws.send(createNormalizedMessage({
|
|
224
|
+
kind: 'stream_delta',
|
|
225
|
+
content: line,
|
|
226
|
+
sessionId: capturedSessionId || sessionId || null,
|
|
227
|
+
provider: 'opencode',
|
|
228
|
+
}));
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
registerSession(readOpenCodeSessionId(response));
|
|
234
|
+
const normalized = context.normalizeMessage(response, capturedSessionId || sessionId || null);
|
|
235
|
+
for (const msg of normalized) {
|
|
236
|
+
ws.send(msg);
|
|
237
|
+
}
|
|
238
|
+
} catch (error) {
|
|
239
|
+
const errorContent = error instanceof Error ? error.message : String(error);
|
|
240
|
+
console.error('[OpenCode] Failed to process JSON output:', errorContent);
|
|
241
|
+
ws.send(createNormalizedMessage({
|
|
242
|
+
kind: 'error',
|
|
243
|
+
content: errorContent,
|
|
244
|
+
sessionId: capturedSessionId || sessionId || null,
|
|
245
|
+
provider: 'opencode',
|
|
246
|
+
}));
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
void context.resolveResumeModel(sessionId, model).then(async (resolvedModel) => {
|
|
251
|
+
let effortModels = null;
|
|
252
|
+
try {
|
|
253
|
+
effortModels = await context.getProviderModels();
|
|
254
|
+
} catch (error) {
|
|
255
|
+
console.warn('[OpenCode] Unable to load provider models for effort validation:', error);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const resolvedEffort = resolveOpenCodeEffort(resolvedModel, effort, effortModels);
|
|
259
|
+
const args = ['run', '--format', 'json'];
|
|
260
|
+
// OpenCode's `run` command owns workspace selection through `--dir`.
|
|
261
|
+
// Relying on the child-process cwd alone is not enough on Linux, where
|
|
262
|
+
// the CLI can still resolve the session under the server install dir.
|
|
263
|
+
args.push('--dir', workingDir);
|
|
264
|
+
if (providerSessionId) {
|
|
265
|
+
args.push('--session', providerSessionId);
|
|
266
|
+
}
|
|
267
|
+
if (resolvedModel) {
|
|
268
|
+
args.push('--model', resolvedModel);
|
|
269
|
+
}
|
|
270
|
+
if (resolvedEffort) {
|
|
271
|
+
args.push('--variant', resolvedEffort);
|
|
272
|
+
}
|
|
273
|
+
const permissionOptions = resolveOpenCodePermissionOptions(permissionMode);
|
|
274
|
+
args.push(...permissionOptions.args);
|
|
275
|
+
const hasAttachments =
|
|
276
|
+
normalizeAttachmentDescriptors(images).length > 0
|
|
277
|
+
|| normalizeAttachmentDescriptors(files).length > 0;
|
|
278
|
+
if ((command && command.trim()) || hasAttachments) {
|
|
279
|
+
// Image attachments ride along as an <images_input> path list appended
|
|
280
|
+
// to the prompt; the session history reader strips the tag back out.
|
|
281
|
+
// opencode is a .cmd shim on Windows, so the whole argument must be
|
|
282
|
+
// newline-free or cmd.exe silently truncates it at the first newline.
|
|
283
|
+
const promptWithAttachments = appendFilesInputTag(
|
|
284
|
+
appendImagesInputTag(command?.trim() || '', images),
|
|
285
|
+
files
|
|
286
|
+
);
|
|
287
|
+
args.push(flattenPromptForWindowsShell(promptWithAttachments));
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
opencodeProcess = spawnFunction('opencode', args, {
|
|
291
|
+
cwd: workingDir,
|
|
292
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
293
|
+
env: { ...process.env, ...permissionOptions.env },
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
activeOpenCodeProcesses.set(processKey, opencodeProcess);
|
|
297
|
+
opencodeProcess.sessionId = processKey;
|
|
298
|
+
opencodeProcess.stdin.end();
|
|
299
|
+
|
|
300
|
+
opencodeProcess.stdout.on('data', (data) => {
|
|
301
|
+
stdoutLineBuffer += data.toString();
|
|
302
|
+
const completeLines = stdoutLineBuffer.split(/\r?\n/);
|
|
303
|
+
stdoutLineBuffer = completeLines.pop() || '';
|
|
304
|
+
|
|
305
|
+
completeLines.forEach((line) => {
|
|
306
|
+
processOpenCodeOutputLine(line.trim());
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
opencodeProcess.stderr.on('data', (data) => {
|
|
311
|
+
const stderrText = data.toString();
|
|
312
|
+
if (!stderrText.trim()) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
ws.send(createNormalizedMessage({
|
|
317
|
+
kind: 'error',
|
|
318
|
+
content: stderrText,
|
|
319
|
+
sessionId: capturedSessionId || sessionId || null,
|
|
320
|
+
provider: 'opencode',
|
|
321
|
+
}));
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
opencodeProcess.on('close', async (code) => {
|
|
325
|
+
const finalSessionId = sessionId || capturedSessionId || processKey;
|
|
326
|
+
activeOpenCodeProcesses.delete(finalSessionId);
|
|
327
|
+
activeOpenCodeProcesses.delete(processKey);
|
|
328
|
+
|
|
329
|
+
if (stdoutLineBuffer.trim()) {
|
|
330
|
+
processOpenCodeOutputLine(stdoutLineBuffer.trim());
|
|
331
|
+
stdoutLineBuffer = '';
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// OpenCode's own database is keyed by the provider-native id.
|
|
335
|
+
const tokenBudget = readOpenCodeTokenUsage(capturedSessionId);
|
|
336
|
+
if (tokenBudget) {
|
|
337
|
+
ws.send(createNormalizedMessage({
|
|
338
|
+
kind: 'status',
|
|
339
|
+
text: 'token_budget',
|
|
340
|
+
tokenBudget,
|
|
341
|
+
sessionId: finalSessionId,
|
|
342
|
+
provider: 'opencode',
|
|
343
|
+
}));
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Terminal complete — skipped for aborted runs (abort-session
|
|
347
|
+
// already sent the aborted complete on this run's behalf).
|
|
348
|
+
if (!completeSent && !opencodeProcess.aborted) {
|
|
349
|
+
completeSent = true;
|
|
350
|
+
ws.send(createCompleteMessage({ provider: 'opencode', sessionId: finalSessionId, exitCode: code }));
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (code === 0) {
|
|
354
|
+
notifyTerminalState({ code });
|
|
355
|
+
resolve();
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (code === 127 || code === null) {
|
|
360
|
+
const installed = await context.isProviderInstalled();
|
|
361
|
+
if (!installed) {
|
|
362
|
+
ws.send(createNormalizedMessage({
|
|
363
|
+
kind: 'error',
|
|
364
|
+
content: 'OpenCode CLI is not installed. Install it from https://opencode.ai/docs/',
|
|
365
|
+
sessionId: finalSessionId,
|
|
366
|
+
provider: 'opencode',
|
|
367
|
+
}));
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
notifyTerminalState({ code });
|
|
372
|
+
reject(new Error(code === null ? 'OpenCode CLI process was terminated' : `OpenCode CLI exited with code ${code}`));
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
opencodeProcess.on('error', async (error) => {
|
|
376
|
+
const finalSessionId = sessionId || capturedSessionId || processKey;
|
|
377
|
+
activeOpenCodeProcesses.delete(finalSessionId);
|
|
378
|
+
activeOpenCodeProcesses.delete(processKey);
|
|
379
|
+
|
|
380
|
+
const installed = await context.isProviderInstalled();
|
|
381
|
+
const errorContent = !installed
|
|
382
|
+
? 'OpenCode CLI is not installed. Install it from https://opencode.ai/docs/'
|
|
383
|
+
: error.message;
|
|
384
|
+
|
|
385
|
+
ws.send(createNormalizedMessage({
|
|
386
|
+
kind: 'error',
|
|
387
|
+
content: errorContent,
|
|
388
|
+
sessionId: finalSessionId,
|
|
389
|
+
provider: 'opencode',
|
|
390
|
+
}));
|
|
391
|
+
if (!completeSent && !opencodeProcess.aborted) {
|
|
392
|
+
completeSent = true;
|
|
393
|
+
ws.send(createCompleteMessage({ provider: 'opencode', sessionId: finalSessionId, exitCode: 1 }));
|
|
394
|
+
}
|
|
395
|
+
notifyTerminalState({ error });
|
|
396
|
+
reject(error);
|
|
397
|
+
});
|
|
398
|
+
}).catch(reject);
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function abortOpenCodeSession(sessionId) {
|
|
403
|
+
const process = activeOpenCodeProcesses.get(sessionId);
|
|
404
|
+
if (!process) {
|
|
405
|
+
return false;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// The abort handler sends the terminal complete (aborted: true); flag the
|
|
409
|
+
// process so its close handler does not emit a second one.
|
|
410
|
+
process.aborted = true;
|
|
411
|
+
process.kill('SIGTERM');
|
|
412
|
+
activeOpenCodeProcesses.delete(sessionId);
|
|
413
|
+
return true;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function isOpenCodeSessionActive(sessionId) {
|
|
417
|
+
return activeOpenCodeProcesses.has(sessionId);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function getActiveOpenCodeSessions() {
|
|
421
|
+
return Array.from(activeOpenCodeProcesses.keys());
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
export const opencodeRuntime = {
|
|
425
|
+
run: spawnOpenCode,
|
|
426
|
+
abort: abortOpenCodeSession,
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
export {
|
|
430
|
+
spawnOpenCode,
|
|
431
|
+
abortOpenCodeSession,
|
|
432
|
+
isOpenCodeSessionActive,
|
|
433
|
+
getActiveOpenCodeSessions,
|
|
434
|
+
};
|