From 14efbe074bb5db5abd3d0f85af5c0659cf208def Mon Sep 17 00:00:00 2001 From: Daniel Saldarriaga Date: Thu, 3 Sep 2026 11:50:51 +0200 Subject: [PATCH 1/2] fix: support current V2 command API --- bun.lock | 82 ++++++++++++++++-------------------------- dist/server.js | 16 ++++++--- package.json | 8 ++--- src/server.ts | 18 +++++++--- src/tui.tsx | 7 ++-- test/server-v2.test.ts | 66 +++++++++++++++++++++++----------- test/tui-v2.test.ts | 9 +++-- 7 files changed, 116 insertions(+), 90 deletions(-) diff --git a/bun.lock b/bun.lock index cfe811a..1f21e70 100644 --- a/bun.lock +++ b/bun.lock @@ -6,16 +6,16 @@ "name": "@prevalentware/opencode-loop-plugin", "dependencies": { "@opencode-ai/plugin": "^1.17.1", - "@opentui/solid": "^0.4.0", + "@opentui/solid": "0.5.9", "effect": "^3.21.2", "solid-js": "1.9.12", "zod": "4.1.8", }, "devDependencies": { "@eslint/js": "^10.0.1", - "@opencode-ai/plugin-v2": "npm:@opencode-ai/plugin@0.0.0-next-17055", - "@opencode-ai/theme": "0.0.0-next-17055", - "@opentui/core": "^0.4.0", + "@opencode-ai/plugin-v2": "npm:@opencode-ai/plugin@0.0.0-beta-18593", + "@opencode-ai/theme": "0.0.0-beta-18593", + "@opentui/core": "0.5.9", "@types/bun": "^1.3.13", "eslint": "^10.3.0", "typescript": "^6.0.3", @@ -138,41 +138,41 @@ "@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4", "", { "os": "win32", "cpu": "x64" }, "sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ=="], - "@opencode-ai/ai": ["@opencode-ai/ai@0.0.0-next-17055", "", { "dependencies": { "@opencode-ai/schema": "0.0.0-next-17055", "@smithy/eventstream-codec": "4.2.14", "@smithy/util-utf8": "4.2.2", "aws4fetch": "1.0.20", "effect": "4.0.0-beta.101", "google-auth-library": "10.5.0" } }, "sha512-7mau+eF28boRehV89aIV13k4WhKP3WQ1A6KTIk2WSjvZurf2LVFePPi0VIbEr43BL242rzR+U0jDn7S9R+tr3w=="], + "@opencode-ai/ai": ["@opencode-ai/ai@0.0.0-beta-18593", "", { "dependencies": { "@opencode-ai/schema": "0.0.0-beta-18593", "@smithy/eventstream-codec": "4.2.14", "@smithy/util-utf8": "4.2.2", "aws4fetch": "1.0.20", "effect": "4.0.0-rc.112", "google-auth-library": "10.5.0" } }, "sha512-FwMNHcvKd0NV5iJErN9/174drHhm21brxM07liTm2mGttUlfbBU9Tvzf2nZyPPWrZBEnr1+pGXtyd/7yZOZbJQ=="], - "@opencode-ai/client": ["@opencode-ai/client@0.0.0-next-17055", "", { "dependencies": { "@opencode-ai/protocol": "0.0.0-next-17055", "@opencode-ai/schema": "0.0.0-next-17055" }, "peerDependencies": { "effect": "4.0.0-beta.101" }, "optionalPeers": ["effect"] }, "sha512-+Ts98xLsGFiaXN+0QhZ/8xLgacEvp0ZcxYWxbo7Aegu1rj8NBYcp9y4DtqrryOgsrlkOWpEA9i47TDzudPNdGg=="], + "@opencode-ai/client": ["@opencode-ai/client@0.0.0-beta-18593", "", { "dependencies": { "@opencode-ai/protocol": "0.0.0-beta-18593", "@opencode-ai/schema": "0.0.0-beta-18593" }, "peerDependencies": { "effect": "4.0.0-rc.112", "solid-js": ">=1.9.0" }, "optionalPeers": ["effect", "solid-js"] }, "sha512-St9canLk4U+8EPEIGR12yDbqvVnbIvYrlB9YGjgBfdktpZmn64hloIVWa/pruLxAEdr2EB3m+5Z5MG7FhpmGkA=="], "@opencode-ai/plugin": ["@opencode-ai/plugin@1.17.13", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@opencode-ai/sdk": "1.17.13", "effect": "4.0.0-beta.83", "zod": "4.1.8" }, "peerDependencies": { "@opentui/core": ">=0.3.4", "@opentui/keymap": ">=0.3.4", "@opentui/solid": ">=0.3.4" }, "optionalPeers": ["@opentui/core", "@opentui/keymap", "@opentui/solid"] }, "sha512-JjoIs6qTPl0IrXo+J1GTqX9lcb2h2dMoW6BWMR2IbTT3MY2FQ/lvBXDzkx0d3zVRaYN+NTmV4u8Nth396xq+sQ=="], - "@opencode-ai/plugin-v2": ["@opencode-ai/plugin@0.0.0-next-17055", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@opencode-ai/ai": "0.0.0-next-17055", "@opencode-ai/client": "0.0.0-next-17055", "@opencode-ai/schema": "0.0.0-next-17055", "@opencode-ai/sdk": "1.18.5", "@standard-schema/spec": "1.1.0", "effect": "4.0.0-beta.101", "zod": "4.1.8" }, "peerDependencies": { "@opencode-ai/theme": "0.0.0-next-17055", "@opentui/core": ">=0.4.5", "@opentui/keymap": ">=0.4.5", "@opentui/solid": ">=0.4.5", "solid-js": ">=1.9.0" }, "optionalPeers": ["@opencode-ai/theme", "@opentui/core", "@opentui/keymap", "@opentui/solid", "solid-js"] }, "sha512-O+jnBm2435zTv1S2XJUV/o/IJLnSk3CDFhD1L4V9/6mf16dvxW2sRQ6gino/d+Z6HyYgLk37UJh+svvp+aEhNw=="], + "@opencode-ai/plugin-v2": ["@opencode-ai/plugin@0.0.0-beta-18593", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@opencode-ai/ai": "0.0.0-beta-18593", "@opencode-ai/client": "0.0.0-beta-18593", "@opencode-ai/protocol": "0.0.0-beta-18593", "@opencode-ai/schema": "0.0.0-beta-18593", "@standard-schema/spec": "1.1.0", "effect": "4.0.0-rc.112", "zod": "4.1.8" }, "peerDependencies": { "@opencode-ai/theme": "0.0.0-beta-18593", "@opentui/core": ">=0.5.9", "@opentui/solid": ">=0.5.9", "solid-js": ">=1.9.0" }, "optionalPeers": ["@opencode-ai/theme", "@opentui/core", "@opentui/solid", "solid-js"] }, "sha512-y/A7M+pEZuGt3FNPlqOxj5QaIy5VBKqymmxpsER0mp2fp15nOCC3hMOQwt37OF3KMO1lieG5L8oSFl/Ij5YCiw=="], - "@opencode-ai/protocol": ["@opencode-ai/protocol@0.0.0-next-17055", "", { "dependencies": { "@opencode-ai/schema": "0.0.0-next-17055", "effect": "4.0.0-beta.101" } }, "sha512-xPxtFIg9a/8QDvDcoKuyhbqjPFE9CMZP/Hi9Bf25JJDBNysrIimVpTEtCsexxBn2O/ZS+ebxmTfGGbRwgSvs/Q=="], + "@opencode-ai/protocol": ["@opencode-ai/protocol@0.0.0-beta-18593", "", { "dependencies": { "@opencode-ai/schema": "0.0.0-beta-18593", "effect": "4.0.0-rc.112" } }, "sha512-mZgJpNxHiOhZshizDpEFOlBaMJvjBTzwRQxhJBkZ0D0Oc3w43C6Hms4Qr2Fy+73JlR+pvzUc5S5OBpyT6fVzTA=="], - "@opencode-ai/schema": ["@opencode-ai/schema@0.0.0-next-17055", "", { "dependencies": { "@standard-schema/spec": "1.1.0", "effect": "4.0.0-beta.101" } }, "sha512-/E6gaHOVhojt0kJxu+tQ+3/G1xoL5IOn3PY9QP68JVgccXpkNJR3bZF0EX71D1oaRD+EdHLHTFWp2n0x6jUp7A=="], + "@opencode-ai/schema": ["@opencode-ai/schema@0.0.0-beta-18593", "", { "dependencies": { "@standard-schema/spec": "1.1.0", "effect": "4.0.0-rc.112" } }, "sha512-jZgIR17nG6KucYqvCiMcNVuRz4amSTbL9VJXQpo4kA9R8UjMlnpyGqsEqrHQ7xGUhib8FR1NtN6UXWyboaE3pQ=="], - "@opencode-ai/sdk": ["@opencode-ai/sdk@1.18.5", "", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-7KgMvP5/1oxbhHj6kYBtPSTEdFKYpUeEYOzBTKdzSaRpapUpFFdn6Hkus3rr0rljO0kukWZIgRd3DrVBwTULGA=="], + "@opencode-ai/sdk": ["@opencode-ai/sdk@1.17.13", "", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-VItOGjMzRQx3zypwmeFLNhCiIx32kxS7FqzIJvVZLfyNGCifs3rfGC9qzNKWcxQo4SjNvAw++v4gWWU6Inv+JQ=="], - "@opencode-ai/theme": ["@opencode-ai/theme@0.0.0-next-17055", "", { "dependencies": { "@opentui/core": "0.4.5", "effect": "4.0.0-beta.101" } }, "sha512-9RurytAQwsBjine2D1CBYVVVV748q9AZPhp39WZaxK/j2hlmVWZDA5dwgm3VXzKa5XPNN4MAk/2JkRDW7atdFw=="], + "@opencode-ai/theme": ["@opencode-ai/theme@0.0.0-beta-18593", "", { "dependencies": { "@opentui/core": "0.5.9", "effect": "4.0.0-rc.112" } }, "sha512-Pz9esAE632n9x9PZDEm1pVX34z8dgqcO2u/7a9qhQ0JGUcg2+WwpBSH8HFfC+qUg7CT/6H0y5g04khlgQHTw/g=="], - "@opentui/core": ["@opentui/core@0.4.2", "", { "dependencies": { "bun-ffi-structs": "0.2.3", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2" }, "optionalDependencies": { "@opentui/core-darwin-arm64": "0.4.2", "@opentui/core-darwin-x64": "0.4.2", "@opentui/core-linux-arm64": "0.4.2", "@opentui/core-linux-arm64-musl": "0.4.2", "@opentui/core-linux-x64": "0.4.2", "@opentui/core-linux-x64-musl": "0.4.2", "@opentui/core-win32-arm64": "0.4.2", "@opentui/core-win32-x64": "0.4.2" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-ulx6RMqftf2fm7Itf9e81GcCDMNY6NAhmnKYhllDOMYD+PxYXR+vomy2bxQNV5ow31RE7s8WQFnb7hWTRUbx2g=="], + "@opentui/core": ["@opentui/core@0.5.9", "", { "dependencies": { "bun-ffi-structs": "0.3.1", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2" }, "optionalDependencies": { "@opentui/core-darwin-arm64": "0.5.9", "@opentui/core-darwin-x64": "0.5.9", "@opentui/core-linux-arm64": "0.5.9", "@opentui/core-linux-arm64-musl": "0.5.9", "@opentui/core-linux-x64": "0.5.9", "@opentui/core-linux-x64-musl": "0.5.9", "@opentui/core-win32-arm64": "0.5.9", "@opentui/core-win32-x64": "0.5.9" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-d0EWYyp6djitu1N1R0o75NrLl4TxY3oJEmRNrX9vSKKC5/jriGQdSV6lJmwiB77O0cxtBnPzWztAV79vA1J2fA=="], - "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.4.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-is+O+sS/l3E9cZXyM9pRF1WhqnE+hYSPYoZkbseR9CthJcaWPGi3R3jUJa1cLj325252jWgxVupnDqFUtKg36w=="], + "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.5.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-YcpWGD8GwoO3UYYw0kLDI4qof3ElSwWW5M3fNG+Kw87BSjCa5frZqak5xebdZ/XU5Xsk+BjgbBsxbzo9yrIjZQ=="], - "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.4.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-ACi42h81DurSeybUAD1XyKT6xmXZcKeTxS54lZFi0CVZh46w0g99vNj8PlQzIFXvvFLT0e0IlRS//eWSWS2zGQ=="], + "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.5.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-xgdZwgcwlDCqi0WPCS1d0GVSd+oKDFX+UVE1ZPei8u8W3pirXGSExxVHNku7QbEYCRvpCeMtoKIa6xN9cVTsoQ=="], - "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.4.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-RjOx2HcjLRtGSy9WrAGSdr5M9SpJuPifPORpImx6Mciovw0ltnE0uoYjIyor82uf6/LExWC7YA2AcAl+YBxayA=="], + "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.5.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-vHBiJp69wMaL+fRMDpnXCCIpPyQRPZEWCWBatnblUUGIWUseqeQlaPow6ljHSbgAd/GjygAf4tFCyTqNT2ezZA=="], - "@opentui/core-linux-arm64-musl": ["@opentui/core-linux-arm64-musl@0.4.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-heNciL2ngPU+kq1h01PHLsxn6Fr8iqTFtbxSdVbhaY3XihuIjkuXyEhFeuoa1lsXY7Bb2gpWnX5EQVWnZsAuDQ=="], + "@opentui/core-linux-arm64-musl": ["@opentui/core-linux-arm64-musl@0.5.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-A7GW4NCTInj2vy3X2gVO2Sm8jllgdLxwPdOklPqyl+xqivQtyG9qwS5YXoCk9fBJt10ORhYKETEjYEnIE5xvMQ=="], - "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.4.2", "", { "os": "linux", "cpu": "x64" }, "sha512-9s0s/ooK+AhWP306By3gu+XhzcVEThC2sqKMPK1nQmGDujQhd+xOrtbtfCVcJSx62UzAovC2VNqypvP8vHByOg=="], + "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.5.9", "", { "os": "linux", "cpu": "x64" }, "sha512-rdw7QVopVHwj6fMrJc7hPGVaYg2V5NBlW3MUfkrQBlUlo/87OC4aYDAh5oRIL1CIFD9LjDdh7hwmUNbn/K9Pcw=="], - "@opentui/core-linux-x64-musl": ["@opentui/core-linux-x64-musl@0.4.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Cjv6Bv7l3p/KLNJr5RyqCS0FmRlAGJnkA2IK3S+HkHhCOv/O02S1G+DBUY6POnyjp1eNy95vauustApobhdbig=="], + "@opentui/core-linux-x64-musl": ["@opentui/core-linux-x64-musl@0.5.9", "", { "os": "linux", "cpu": "x64" }, "sha512-J4wQs1OMPZ4hR93Op1C/BLFmIta2mUJm4M7djevlgcWcal4RSNO4V8QvUXfogWGci+ADZqEmX2osQQDY5NVJpw=="], - "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.4.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-mfJZrJ0TNPFRZUzXNsxAPe1YdiWsy/vbTl93+yeXGHPI1B8Qnk9V5hpzSxxEyBGhlTHSfGNtgiO+VrrdRC3kZA=="], + "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.5.9", "", { "os": "win32", "cpu": "arm64" }, "sha512-OnmEsGLWwsHrm6sTsWSSesu4LGjSV1JNWqCsEItbuitWaqH6rXdSahSc5jMFsS57bZxUyaRz01yAqiZzUSXnVQ=="], - "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.4.2", "", { "os": "win32", "cpu": "x64" }, "sha512-P2oguG3ng3OMjAdasFSA3GhHaQXtzDUsIRDGbzWFOimpZ/zMemidp+JQ0V8V6XwK6Utk5G0aQ03oBaRCoLyYDw=="], + "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.5.9", "", { "os": "win32", "cpu": "x64" }, "sha512-/CnAIfKL7+ZeGLyZoXV5zS71Nd8Zn97RUir2DAIY05MJozmfg5s7XOAdUNYuK1cg5mbwlAuqeAsfbuWUAEe15g=="], - "@opentui/solid": ["@opentui/solid@0.4.2", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.4.2", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.12", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.12" } }, "sha512-zuYXsnrlsMtnXrS7QCYBdPzMtUSonG2LqnJikBR2NjEE2O4zEKvJd48n3eB1igcxjv96tiotTXRNCylYS0SNdQ=="], + "@opentui/solid": ["@opentui/solid@0.5.9", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.5.9", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.12", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.12" } }, "sha512-zGSP/ia9ww+TTMvQMZjJW2+h7fg9YkVRIM723EgvP+v7ZBZ/0vMzPNh7FJ0jET3ysw0h5hsbvcYISs4sUcf+UA=="], "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], @@ -256,7 +256,7 @@ "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="], - "bun-ffi-structs": ["bun-ffi-structs@0.2.3", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-pgJiXP+hEgFo9qG51J6ItfY4ocs3vniwNzJ9WhoakB3QB2GdzQxX2EXssentPYlB2hOfJrTjO6iIQkWYzUodpg=="], + "bun-ffi-structs": ["bun-ffi-structs@0.3.1", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-3gM7PpVWLyrwxWjcilSiGuhWanhZivvo6l0u573NziPH6f/gwk6McbaYgn7oJWov6pKGRTDbrg94W5DcJsKTtQ=="], "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], @@ -430,11 +430,11 @@ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - "msgpackr": ["msgpackr@2.0.4", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.4" } }, "sha512-o1C5KRmuRt+apqMr1HuGSqWStZoRBUpEsCsl15uM9VdAF1qHLtvMOU2En747EnTyEl6c4pzPewRMFF31s1CNbA=="], + "msgpackr": ["msgpackr@2.1.0", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.4" } }, "sha512-p/pBCVO63CsvvpkomUnNNag6+n38rULuDA6HHe70o2gtC8ODI52foF/4ko2qQcp6OiErJXTmrZeXmsGGHsIQNQ=="], "msgpackr-extract": ["msgpackr-extract@3.0.4", "", { "dependencies": { "node-gyp-build-optional-packages": "5.2.2" }, "optionalDependencies": { "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.4", "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.4", "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.4" }, "bin": { "download-msgpackr-prebuilds": "bin/download-prebuilds.js" } }, "sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw=="], - "multipasta": ["multipasta@0.2.8", "", {}, "sha512-ZPWuMKyv0cSO29f7hozp+k6+crZbQijV8ipMvxNxRf2SwtYGTX1ZX89Kd20VV4H9Znonx+EQn+iy1wGQsJ+b+Q=="], + "multipasta": ["multipasta@0.2.7", "", {}, "sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA=="], "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], @@ -566,21 +566,17 @@ "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], - "@opencode-ai/ai/effect": ["effect@4.0.0-beta.101", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.9.0", "find-my-way-ts": "^0.1.6", "ini": "^7.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^2.0.4", "multipasta": "^0.2.8", "toml": "^4.1.2", "uuid": "^14.0.1", "yaml": "^2.9.0" } }, "sha512-HjowumlIo+orthn4jMlEJPuzIYPBV+uq/XiciHWhiedLsXQpWHdNJHO5d59BVDP5s1LPuvERcktwFqRXnJqnhA=="], - - "@opencode-ai/plugin/@opencode-ai/sdk": ["@opencode-ai/sdk@1.17.13", "", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-VItOGjMzRQx3zypwmeFLNhCiIx32kxS7FqzIJvVZLfyNGCifs3rfGC9qzNKWcxQo4SjNvAw++v4gWWU6Inv+JQ=="], + "@opencode-ai/ai/effect": ["effect@4.0.0-rc.112", "", { "dependencies": { "fast-check": "^4.9.0", "msgpackr": "^2.0.5" } }, "sha512-wXxwuh1Ywnv4cPRM3Wfa0vDwuOHnZ1TsTgHJkG9XgzND6inhBH9n1vBxhg3iIXOia/OrpmvVmd3lrD4vq6bF3A=="], "@opencode-ai/plugin/effect": ["effect@4.0.0-beta.83", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.8.0", "find-my-way-ts": "^0.1.6", "ini": "^7.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^2.0.1", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^14.0.0", "yaml": "^2.9.0" } }, "sha512-0wsak8RtgGAr9UWSbVDgJHZcUqMSvicHcvaZv1MbMM7MCGgW4Rn/137J1MHQbwYPcwYGxT/IqehFd+UbYuj78w=="], - "@opencode-ai/plugin-v2/effect": ["effect@4.0.0-beta.101", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.9.0", "find-my-way-ts": "^0.1.6", "ini": "^7.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^2.0.4", "multipasta": "^0.2.8", "toml": "^4.1.2", "uuid": "^14.0.1", "yaml": "^2.9.0" } }, "sha512-HjowumlIo+orthn4jMlEJPuzIYPBV+uq/XiciHWhiedLsXQpWHdNJHO5d59BVDP5s1LPuvERcktwFqRXnJqnhA=="], - - "@opencode-ai/protocol/effect": ["effect@4.0.0-beta.101", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.9.0", "find-my-way-ts": "^0.1.6", "ini": "^7.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^2.0.4", "multipasta": "^0.2.8", "toml": "^4.1.2", "uuid": "^14.0.1", "yaml": "^2.9.0" } }, "sha512-HjowumlIo+orthn4jMlEJPuzIYPBV+uq/XiciHWhiedLsXQpWHdNJHO5d59BVDP5s1LPuvERcktwFqRXnJqnhA=="], + "@opencode-ai/plugin-v2/effect": ["effect@4.0.0-rc.112", "", { "dependencies": { "fast-check": "^4.9.0", "msgpackr": "^2.0.5" } }, "sha512-wXxwuh1Ywnv4cPRM3Wfa0vDwuOHnZ1TsTgHJkG9XgzND6inhBH9n1vBxhg3iIXOia/OrpmvVmd3lrD4vq6bF3A=="], - "@opencode-ai/schema/effect": ["effect@4.0.0-beta.101", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.9.0", "find-my-way-ts": "^0.1.6", "ini": "^7.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^2.0.4", "multipasta": "^0.2.8", "toml": "^4.1.2", "uuid": "^14.0.1", "yaml": "^2.9.0" } }, "sha512-HjowumlIo+orthn4jMlEJPuzIYPBV+uq/XiciHWhiedLsXQpWHdNJHO5d59BVDP5s1LPuvERcktwFqRXnJqnhA=="], + "@opencode-ai/protocol/effect": ["effect@4.0.0-rc.112", "", { "dependencies": { "fast-check": "^4.9.0", "msgpackr": "^2.0.5" } }, "sha512-wXxwuh1Ywnv4cPRM3Wfa0vDwuOHnZ1TsTgHJkG9XgzND6inhBH9n1vBxhg3iIXOia/OrpmvVmd3lrD4vq6bF3A=="], - "@opencode-ai/theme/@opentui/core": ["@opentui/core@0.4.5", "", { "dependencies": { "bun-ffi-structs": "0.2.4", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2" }, "optionalDependencies": { "@opentui/core-darwin-arm64": "0.4.5", "@opentui/core-darwin-x64": "0.4.5", "@opentui/core-linux-arm64": "0.4.5", "@opentui/core-linux-arm64-musl": "0.4.5", "@opentui/core-linux-x64": "0.4.5", "@opentui/core-linux-x64-musl": "0.4.5", "@opentui/core-win32-arm64": "0.4.5", "@opentui/core-win32-x64": "0.4.5" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-JsgRTPkA6e+Vxmumxai6SElOSlRQkbzNKHlCfemlArRiLhfC1IZ9RXJo2QH4xSu+uBOWAM90uss73/pPlkdEig=="], + "@opencode-ai/schema/effect": ["effect@4.0.0-rc.112", "", { "dependencies": { "fast-check": "^4.9.0", "msgpackr": "^2.0.5" } }, "sha512-wXxwuh1Ywnv4cPRM3Wfa0vDwuOHnZ1TsTgHJkG9XgzND6inhBH9n1vBxhg3iIXOia/OrpmvVmd3lrD4vq6bF3A=="], - "@opencode-ai/theme/effect": ["effect@4.0.0-beta.101", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.9.0", "find-my-way-ts": "^0.1.6", "ini": "^7.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^2.0.4", "multipasta": "^0.2.8", "toml": "^4.1.2", "uuid": "^14.0.1", "yaml": "^2.9.0" } }, "sha512-HjowumlIo+orthn4jMlEJPuzIYPBV+uq/XiciHWhiedLsXQpWHdNJHO5d59BVDP5s1LPuvERcktwFqRXnJqnhA=="], + "@opencode-ai/theme/effect": ["effect@4.0.0-rc.112", "", { "dependencies": { "fast-check": "^4.9.0", "msgpackr": "^2.0.5" } }, "sha512-wXxwuh1Ywnv4cPRM3Wfa0vDwuOHnZ1TsTgHJkG9XgzND6inhBH9n1vBxhg3iIXOia/OrpmvVmd3lrD4vq6bF3A=="], "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], @@ -626,30 +622,12 @@ "@opencode-ai/plugin/effect/fast-check": ["fast-check@4.8.0", "", { "dependencies": { "pure-rand": "^8.0.0" } }, "sha512-GOJ158CUMnN6cSahsv4+ExARvIDuzzinFjkp0E9WtiBa5zcVeLozVkWaE4IzFcc+Y48Wp1EDlUZsXRyAztQcSg=="], - "@opencode-ai/plugin/effect/multipasta": ["multipasta@0.2.7", "", {}, "sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA=="], + "@opencode-ai/plugin/effect/msgpackr": ["msgpackr@2.0.4", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.4" } }, "sha512-o1C5KRmuRt+apqMr1HuGSqWStZoRBUpEsCsl15uM9VdAF1qHLtvMOU2En747EnTyEl6c4pzPewRMFF31s1CNbA=="], "@opencode-ai/protocol/effect/fast-check": ["fast-check@4.9.0", "", { "dependencies": { "pure-rand": "^8.0.0" } }, "sha512-7ms6T7SybUev/PQITciI0yLM2pOSFy5zpG8Ty7tQofcVaQUvrMXp6CBwqF6fThLCLOrfBtuHAtwq6Yu4XPCllg=="], "@opencode-ai/schema/effect/fast-check": ["fast-check@4.9.0", "", { "dependencies": { "pure-rand": "^8.0.0" } }, "sha512-7ms6T7SybUev/PQITciI0yLM2pOSFy5zpG8Ty7tQofcVaQUvrMXp6CBwqF6fThLCLOrfBtuHAtwq6Yu4XPCllg=="], - "@opencode-ai/theme/@opentui/core/@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.4.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-8KUG0oRidnR+oW1RSZJ72/PhZLl+qRRMk5U/mieF4c0SJ5V3tYACpBZAKzQfHNd1f7QzD8FHZct1lPpQgtmkWg=="], - - "@opencode-ai/theme/@opentui/core/@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.4.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-R2bocsg55gwjOqCp/MWFgFYzRmsduKegB6nzgFAPCvAD/L5Jf30xpWJWFlSg3x8vxe1L9WJ84dfqa4M7mZZ3wA=="], - - "@opencode-ai/theme/@opentui/core/@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.4.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-R4MZ25a4CzOAGVjW9aj1hUfzQGVfCJwrwBDbNs2SXaIvzcZqkxCVtU4FoQ5LsaD0j/BdNQVg2CIfFkFsm1fDuQ=="], - - "@opencode-ai/theme/@opentui/core/@opentui/core-linux-arm64-musl": ["@opentui/core-linux-arm64-musl@0.4.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-ieqdyKI6EIYPalYAETB2wsdP83hr5Ifi+dFnBFUmdEEFHsoKwBmn2S7bsTOYlX7Bg03F4/YPIg+IvRpeC+cUJw=="], - - "@opencode-ai/theme/@opentui/core/@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.4.5", "", { "os": "linux", "cpu": "x64" }, "sha512-SNyuQoxMKI1vuJhgxSSW96adWM6LqFl2SoS3GM4tGeneGOanVVG2Y06PvlytXvF4cKik97t0rqkVMRetmOs93w=="], - - "@opencode-ai/theme/@opentui/core/@opentui/core-linux-x64-musl": ["@opentui/core-linux-x64-musl@0.4.5", "", { "os": "linux", "cpu": "x64" }, "sha512-mKVKcIcPiSVVZZsdPSBoWwoa2/TCeQAaMDeHF7PFw2kt5bTXZPP7xxWfRQLCNIcA1eaGl59UuwUWHDR2Ve548Q=="], - - "@opencode-ai/theme/@opentui/core/@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.4.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-GHTTsqeR45q2Iek9Rb7ty+x/hAKn2jZ1ujlCgPR8LBKyF7h0E1dNFryoZ7ehMc3kJndP1sKn836IemKFqxuDdQ=="], - - "@opencode-ai/theme/@opentui/core/@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.4.5", "", { "os": "win32", "cpu": "x64" }, "sha512-Y8T/yXCDGagRGiQrtmuB6AhRcPucKFs/Dre3v8kJwNYqDccI4FzUPKclZ7djfmRZNjl7JUqPhZZP/PwDpQocMg=="], - - "@opencode-ai/theme/@opentui/core/bun-ffi-structs": ["bun-ffi-structs@0.2.4", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-AJzsqoVFs1KBbJbWHIYrVZLDC3NhTqqh25awRXqzoLzmBAKr5oqk6+CwuYHAekKx+VBCYVohBoKuRq40dV+TYg=="], - "@opencode-ai/theme/effect/fast-check": ["fast-check@4.9.0", "", { "dependencies": { "pure-rand": "^8.0.0" } }, "sha512-7ms6T7SybUev/PQITciI0yLM2pOSFy5zpG8Ty7tQofcVaQUvrMXp6CBwqF6fThLCLOrfBtuHAtwq6Yu4XPCllg=="], "glob/minimatch/brace-expansion": ["brace-expansion@2.1.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA=="], diff --git a/dist/server.js b/dist/server.js index e82a81a..6c2c519 100644 --- a/dist/server.js +++ b/dist/server.js @@ -1194,11 +1194,17 @@ async function setupV2(context) { }; if (registerCommand) { registrations.push(await context.command.transform((draft) => { - if (draft.get(commandName)) - return; - draft.update(commandName, (command) => { - command.description = "Run an instruction on a recurring interval while this session is idle"; - command.template = loopCommandTemplate(commandName, minIntervalSeconds); + draft.add({ + name: commandName, + description: "Run an instruction on a recurring interval while this session is idle", + execute: async (input) => { + await context.session.prompt({ + ...input.prompt, + sessionID: input.sessionID, + text: loopCommandTemplate(commandName, minIntervalSeconds).replaceAll("$ARGUMENTS", () => input.prompt.text.trim()), + delivery: input.delivery + }); + } }); })); } diff --git a/package.json b/package.json index 36b1afb..c19e778 100644 --- a/package.json +++ b/package.json @@ -54,16 +54,16 @@ }, "dependencies": { "@opencode-ai/plugin": "^1.17.1", - "@opentui/solid": "^0.4.0", + "@opentui/solid": "0.5.9", "effect": "^3.21.2", "solid-js": "1.9.12", "zod": "4.1.8" }, "devDependencies": { "@eslint/js": "^10.0.1", - "@opencode-ai/plugin-v2": "npm:@opencode-ai/plugin@0.0.0-next-17055", - "@opencode-ai/theme": "0.0.0-next-17055", - "@opentui/core": "^0.4.0", + "@opencode-ai/plugin-v2": "npm:@opencode-ai/plugin@0.0.0-beta-18593", + "@opencode-ai/theme": "0.0.0-beta-18593", + "@opentui/core": "0.5.9", "@types/bun": "^1.3.13", "eslint": "^10.3.0", "typescript": "^6.0.3", diff --git a/src/server.ts b/src/server.ts index 09105a6..09323eb 100644 --- a/src/server.ts +++ b/src/server.ts @@ -771,10 +771,20 @@ async function setupV2(context: PluginV2.Plugin.Context): Promise { - if (draft.get(commandName)) return - draft.update(commandName, (command) => { - command.description = "Run an instruction on a recurring interval while this session is idle" - command.template = loopCommandTemplate(commandName, minIntervalSeconds) + draft.add({ + name: commandName, + description: "Run an instruction on a recurring interval while this session is idle", + execute: async (input) => { + await context.session.prompt({ + ...input.prompt, + sessionID: input.sessionID, + text: loopCommandTemplate(commandName, minIntervalSeconds).replaceAll( + "$ARGUMENTS", + () => input.prompt.text.trim(), + ), + delivery: input.delivery, + }) + }, }) }), ) diff --git a/src/tui.tsx b/src/tui.tsx index e2a57f4..4660f06 100644 --- a/src/tui.tsx +++ b/src/tui.tsx @@ -493,8 +493,11 @@ function LoopKeymapLayerV2(api: TuiPluginV2.Context) { } export function setupTuiV2(context: TuiPluginV2.Context): TuiPluginV2.Cleanup { - const offSidebar = context.ui.slot("sidebar.content", (props) => LoopSidebarV2(context, props.sessionID)) - const offApp = context.ui.slot("app", () => LoopKeymapLayerV2(context)) + const offSidebar = context.ui.slot({ + append: "sidebar.content", + render: (props) => LoopSidebarV2(context, props.sessionID), + }) + const offApp = context.ui.slot({ append: "app", render: () => LoopKeymapLayerV2(context) }) return () => { offSidebar() offApp() diff --git a/test/server-v2.test.ts b/test/server-v2.test.ts index 2f6fe85..48c367a 100644 --- a/test/server-v2.test.ts +++ b/test/server-v2.test.ts @@ -27,8 +27,15 @@ type ToolDraft = { } type MockCommandDraft = { - get(name: string): { name: string; template: string } | undefined - update(name: string, update: (command: { description?: string; template: string }) => void): void + add(command: { + name: string + description?: string + execute: (input: { + sessionID: string + prompt: { text: string; files?: Array<{ uri: string }> } + delivery: "steer" | "queue" + }) => Promise + }): void } type Registration = { dispose: () => Promise } @@ -66,9 +73,15 @@ function controlledStream() { type MockContext = { options: Record activeSessions: Set - promptCalls: Array<{ sessionID: string; text: string; agents?: Array<{ name: string }> }> + promptCalls: Array<{ + sessionID: string + text: string + files?: Array<{ uri: string }> + agents?: Array<{ name: string }> + delivery?: "steer" | "queue" + }> tools: Array void ? T : never> - commandDraft: MockCommandDraft + commands: Array void ? T : never> hooks: Record void> stream: ReturnType disposals: string[] @@ -81,7 +94,13 @@ type MockContext = { session: { hook: (name: string, callback: (input: unknown) => void) => Promise active: () => Promise> - prompt: (input: { sessionID: string; text: string; agents?: Array<{ name: string }> }) => Promise + prompt: (input: { + sessionID: string + text: string + files?: Array<{ uri: string }> + agents?: Array<{ name: string }> + delivery?: "steer" | "queue" + }) => Promise } event: { subscribe: (options?: { signal?: AbortSignal }) => AsyncIterable @@ -90,19 +109,12 @@ type MockContext = { function makeMockContext(options: Record = {}): MockContext { const tools: MockContext["tools"] = [] + const commands: MockContext["commands"] = [] const hooks: MockContext["hooks"] = {} const activeSessions = new Set() const promptCalls: MockContext["promptCalls"] = [] const disposals: string[] = [] const stream = controlledStream() - const commandDraft: MockCommandDraft = { - get: () => undefined, - update: (name, update) => { - const command = { name, template: "" } - update(command) - commandDraft.get = () => command - }, - } const registration = (name: string): Registration => ({ dispose: async () => { disposals.push(name) @@ -113,13 +125,13 @@ function makeMockContext(options: Record = {}): MockContext { activeSessions, promptCalls, tools, - commandDraft, + commands, hooks, stream, disposals, command: { transform: async (callback) => { - callback(commandDraft) + callback({ add: (command) => commands.push(command) }) return registration("command.transform") }, }, @@ -228,11 +240,25 @@ test("V2 setup registers the /loop command via command transform", async () => { const mock = makeMockContext() const cleanup = await plugin.setup(mock as never) - const command = mock.commandDraft.get("loop") + const command = mock.commands.find((candidate) => candidate.name === "loop") expect(command).toBeDefined() - expect(command?.template).toContain('OpenCode loop mode command "/loop" was invoked') - expect(command?.template).toContain("$ARGUMENTS") - expect(command?.template).toContain("create_loop") + expect(command?.description).toBe("Run an instruction on a recurring interval while this session is idle") + + await command?.execute({ + sessionID: "ses_command", + prompt: { text: "5m check CI", files: [{ uri: "file:///tmp/context.txt" }] }, + delivery: "queue", + }) + expect(mock.promptCalls).toHaveLength(1) + expect(mock.promptCalls[0]).toMatchObject({ + sessionID: "ses_command", + files: [{ uri: "file:///tmp/context.txt" }], + delivery: "queue", + }) + expect(mock.promptCalls[0]?.text).toContain('OpenCode loop mode command "/loop" was invoked') + expect(mock.promptCalls[0]?.text).toContain("5m check CI") + expect(mock.promptCalls[0]?.text).toContain("create_loop") + expect(mock.promptCalls[0]?.text).not.toContain("$ARGUMENTS") mock.stream.end() await cleanup() @@ -242,7 +268,7 @@ test("V2 setup skips command registration when register_command is false", async const mock = makeMockContext({ register_command: false }) const cleanup = await plugin.setup(mock as never) - expect(mock.commandDraft.get("loop")).toBeUndefined() + expect(mock.commands).toHaveLength(0) expect(mock.disposals).not.toContain("command.transform") mock.stream.end() diff --git a/test/tui-v2.test.ts b/test/tui-v2.test.ts index a9538a8..8d4b4bf 100644 --- a/test/tui-v2.test.ts +++ b/test/tui-v2.test.ts @@ -111,9 +111,12 @@ function mockContext() { current: () => route, }, tabs: undefined, - slot(name: string, render: (props: { sessionID: string }) => unknown) { - slots.set(name, render) - return () => disposed.push(name) + slot(claim: { + append: string + render: (props: { sessionID: string }) => unknown + }) { + slots.set(claim.append, claim.render) + return () => disposed.push(claim.append) }, }, } From 92f0857287beef665b32b754226cafd2ff49268c Mon Sep 17 00:00:00 2001 From: Daniel Saldarriaga Date: Thu, 3 Sep 2026 12:22:56 +0200 Subject: [PATCH 2/2] fix: preserve command attachment metadata --- bun.lock | 2 +- dist/server.js | 4 ++++ package.json | 2 +- src/server.ts | 4 ++++ test/server-v2.test.ts | 39 ++++++++++++++++++++++++++------------- 5 files changed, 36 insertions(+), 15 deletions(-) diff --git a/bun.lock b/bun.lock index 1f21e70..cafa773 100644 --- a/bun.lock +++ b/bun.lock @@ -6,7 +6,7 @@ "name": "@prevalentware/opencode-loop-plugin", "dependencies": { "@opencode-ai/plugin": "^1.17.1", - "@opentui/solid": "0.5.9", + "@opentui/solid": ">=0.4.0 <0.6.0", "effect": "^3.21.2", "solid-js": "1.9.12", "zod": "4.1.8", diff --git a/dist/server.js b/dist/server.js index 6c2c519..72bc224 100644 --- a/dist/server.js +++ b/dist/server.js @@ -1198,8 +1198,12 @@ async function setupV2(context) { name: commandName, description: "Run an instruction on a recurring interval while this session is idle", execute: async (input) => { + const stripMention = ({ mention: _mention, ...attachment }) => attachment; await context.session.prompt({ ...input.prompt, + files: input.prompt.files?.map(stripMention), + agents: input.prompt.agents?.map(stripMention), + skills: input.prompt.skills?.map(stripMention), sessionID: input.sessionID, text: loopCommandTemplate(commandName, minIntervalSeconds).replaceAll("$ARGUMENTS", () => input.prompt.text.trim()), delivery: input.delivery diff --git a/package.json b/package.json index c19e778..e15decb 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ }, "dependencies": { "@opencode-ai/plugin": "^1.17.1", - "@opentui/solid": "0.5.9", + "@opentui/solid": ">=0.4.0 <0.6.0", "effect": "^3.21.2", "solid-js": "1.9.12", "zod": "4.1.8" diff --git a/src/server.ts b/src/server.ts index 09323eb..69dd28d 100644 --- a/src/server.ts +++ b/src/server.ts @@ -775,8 +775,12 @@ async function setupV2(context: PluginV2.Plugin.Context): Promise { + const stripMention = ({ mention: _mention, ...attachment }: T) => attachment await context.session.prompt({ ...input.prompt, + files: input.prompt.files?.map(stripMention), + agents: input.prompt.agents?.map(stripMention), + skills: input.prompt.skills?.map(stripMention), sessionID: input.sessionID, text: loopCommandTemplate(commandName, minIntervalSeconds).replaceAll( "$ARGUMENTS", diff --git a/test/server-v2.test.ts b/test/server-v2.test.ts index 48c367a..a8f7149 100644 --- a/test/server-v2.test.ts +++ b/test/server-v2.test.ts @@ -26,13 +26,21 @@ type ToolDraft = { }): void } +type MockMention = { start: number; end: number; text: string } +type MockPrompt = { + text: string + files?: Array<{ uri: string; mention?: MockMention }> + agents?: Array<{ name: string; mention?: MockMention }> + skills?: Array<{ id: string; mention?: MockMention }> +} + type MockCommandDraft = { add(command: { name: string description?: string execute: (input: { sessionID: string - prompt: { text: string; files?: Array<{ uri: string }> } + prompt: MockPrompt delivery: "steer" | "queue" }) => Promise }): void @@ -75,11 +83,8 @@ type MockContext = { activeSessions: Set promptCalls: Array<{ sessionID: string - text: string - files?: Array<{ uri: string }> - agents?: Array<{ name: string }> delivery?: "steer" | "queue" - }> + } & MockPrompt> tools: Array void ? T : never> commands: Array void ? T : never> hooks: Record void> @@ -96,11 +101,8 @@ type MockContext = { active: () => Promise> prompt: (input: { sessionID: string - text: string - files?: Array<{ uri: string }> - agents?: Array<{ name: string }> delivery?: "steer" | "queue" - }) => Promise + } & MockPrompt) => Promise } event: { subscribe: (options?: { signal?: AbortSignal }) => AsyncIterable @@ -246,19 +248,30 @@ test("V2 setup registers the /loop command via command transform", async () => { await command?.execute({ sessionID: "ses_command", - prompt: { text: "5m check CI", files: [{ uri: "file:///tmp/context.txt" }] }, + prompt: { + text: "5m check $& and $ARGUMENTS", + files: [{ uri: "file:///tmp/context.txt", mention: { start: 9, end: 11, text: "$&" } }], + agents: [{ name: "build", mention: { start: 9, end: 11, text: "$&" } }], + skills: [{ id: "review", mention: { start: 9, end: 11, text: "$&" } }], + }, delivery: "queue", }) expect(mock.promptCalls).toHaveLength(1) expect(mock.promptCalls[0]).toMatchObject({ sessionID: "ses_command", - files: [{ uri: "file:///tmp/context.txt" }], delivery: "queue", }) + expect(mock.promptCalls[0]?.files).toEqual([{ uri: "file:///tmp/context.txt" }]) + expect(mock.promptCalls[0]?.agents).toEqual([{ name: "build" }]) + expect(mock.promptCalls[0]?.skills).toEqual([{ id: "review" }]) expect(mock.promptCalls[0]?.text).toContain('OpenCode loop mode command "/loop" was invoked') - expect(mock.promptCalls[0]?.text).toContain("5m check CI") + expect(mock.promptCalls[0]?.text).toContain("5m check $& and $ARGUMENTS") expect(mock.promptCalls[0]?.text).toContain("create_loop") - expect(mock.promptCalls[0]?.text).not.toContain("$ARGUMENTS") + expect(mock.promptCalls[0]?.text.match(/\$ARGUMENTS/g)).toHaveLength(1) + + await command?.execute({ sessionID: "ses_empty", prompt: { text: "" }, delivery: "steer" }) + expect(mock.promptCalls[1]).toMatchObject({ sessionID: "ses_empty", delivery: "steer" }) + expect(mock.promptCalls[1]?.text).toContain('If the arguments are empty, "list", or "status", call list_loops') mock.stream.end() await cleanup()