fix: refresh pi dependency compatibility

Update Pi packages to 0.79.9 and align pi-ask with current TUI/RPC behavior.

- guard custom ask/settings flows to TUI mode with a clear non-TUI fallback

- reset config cache on extension reload so edited settings are picked up

- clean up remote ask flow listeners on session shutdown

- forward editor focus/invalidation and keep @ file autocomplete explicit

- document the non-TUI contract and cover the compatibility fixes with tests
This commit is contained in:
Erik Kotsiuba
2026-06-22 16:41:37 +02:00
parent 69e8bab6ef
commit d6db94a39d
20 changed files with 457 additions and 137 deletions
+3 -3
View File
@@ -255,9 +255,9 @@ Dirty dismiss:
- when `Confirm dismiss when dirty` is enabled, cancelling or dismissing a dirty ask flow requires the same action a second time
- the dirty-dismiss warning stays visible until the user changes tabs in the ask flow
## Non-interactive mode
## Non-TUI and non-interactive modes
If `ctx.hasUI === false`, the tool returns a `Needs user input` message in `content` and a cancelled result in `details`.
The rich ask flow uses `ctx.ui.custom()` and opens only in TUI mode. In print, JSON, RPC, or any other non-TUI mode, the tool returns a `Needs user input: ask_user requires interactive TUI mode.` message in `content` and a cancelled result in `details` instead of opening custom UI.
Validation is handled inside the tool so malformed calls produce the same structured error shape as other invalid payloads instead of relying on pre-execution schema failures.
@@ -269,7 +269,7 @@ When enabled, pi-ask emits one best-effort external notification per ask session
## Remote inter-extension events
pi-ask exposes a local `pi.events` contract for trusted Pi extensions. It does not expose a network API and does not automate terminal keystrokes.
pi-ask exposes a local `pi.events` contract for trusted Pi extensions. It does not expose a network API and does not automate terminal keystrokes. RPC or headless integrations should use a trusted in-process bridge extension that consumes these events rather than expecting the TUI-only custom surface to open.
Channels:
+7 -7
View File
@@ -93,9 +93,9 @@
"@biomejs/biome": "2.4.12",
"@commitlint/cli": "^19.8.1",
"@commitlint/config-conventional": "^19.8.1",
"@earendil-works/pi-ai": "0.75.4",
"@earendil-works/pi-coding-agent": "0.75.4",
"@earendil-works/pi-tui": "0.75.4",
"@earendil-works/pi-ai": "0.79.9",
"@earendil-works/pi-coding-agent": "0.79.9",
"@earendil-works/pi-tui": "0.79.9",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/commit-analyzer": "^13.0.1",
"@semantic-release/git": "^10.0.1",
@@ -118,10 +118,10 @@
},
"pnpm": {
"overrides": {
"@earendil-works/pi-agent-core": "0.75.4",
"@earendil-works/pi-ai": "0.75.4",
"@earendil-works/pi-tui": "0.75.4",
"@earendil-works/pi-coding-agent": "0.75.4"
"@earendil-works/pi-agent-core": "0.79.9",
"@earendil-works/pi-ai": "0.79.9",
"@earendil-works/pi-tui": "0.79.9",
"@earendil-works/pi-coding-agent": "0.79.9"
},
"ignoredBuiltDependencies": [
"lefthook"
+130 -105
View File
@@ -5,10 +5,10 @@ settings:
excludeLinksFromLockfile: false
overrides:
'@earendil-works/pi-agent-core': 0.75.4
'@earendil-works/pi-ai': 0.75.4
'@earendil-works/pi-tui': 0.75.4
'@earendil-works/pi-coding-agent': 0.75.4
'@earendil-works/pi-agent-core': 0.79.9
'@earendil-works/pi-ai': 0.79.9
'@earendil-works/pi-tui': 0.79.9
'@earendil-works/pi-coding-agent': 0.79.9
importers:
@@ -24,14 +24,14 @@ importers:
specifier: ^19.8.1
version: 19.8.1
'@earendil-works/pi-ai':
specifier: 0.75.4
version: 0.75.4(ws@8.20.0)(zod@4.3.6)
specifier: 0.79.9
version: 0.79.9(ws@8.20.0)(zod@4.3.6)
'@earendil-works/pi-coding-agent':
specifier: 0.75.4
version: 0.75.4(ws@8.20.0)(zod@4.3.6)
specifier: 0.79.9
version: 0.79.9(ws@8.20.0)(zod@4.3.6)
'@earendil-works/pi-tui':
specifier: 0.75.4
version: 0.75.4
specifier: 0.79.9
version: 0.79.9
'@semantic-release/changelog':
specifier: ^6.0.3
version: 6.0.3(semantic-release@25.0.3(typescript@6.0.2))
@@ -367,22 +367,22 @@ packages:
resolution: {integrity: sha512-ZJoS8oSq2CAZEpc/YI9SulLrdiIyXeHb/OGqGrkUP6Q7YV+0ouNAa7GjqRdXeQPncHQIDz/jbCTlHScvYvO/gA==}
engines: {node: '>=v18'}
'@earendil-works/pi-agent-core@0.75.4':
resolution: {integrity: sha512-cGYbysb4EqUf0B28OeqFq2ppm1XF3bYBOP71q9dv38yf/UJfzMjiXBeNelrcio+QWIoVrW+xzYm7sMzYIUc9Og==}
'@earendil-works/pi-agent-core@0.79.9':
resolution: {integrity: sha512-GsFbPR85nhncKoU9++fTKa11PzwUkAmkrKXo97dBOzi10Td72rVV6vmfxKjwPLHTzRbJMa0byr12YhODZ59yLA==}
engines: {node: '>=22.19.0'}
'@earendil-works/pi-ai@0.75.4':
resolution: {integrity: sha512-m/w8Hh3vQ0rAycwJiJWdzkypkn4295f4eq/966lDRy8aX5sk6bgYXH8TQmL16TO7Uwc7MbJG0QoyFHgX8RqXUQ==}
'@earendil-works/pi-ai@0.79.9':
resolution: {integrity: sha512-fHmgNMONwCCE7bQAKbcz76sgm3iQuA7km1mpIc4H5xXd9+zhPh/faULz6ARkgjQE0EufHnfZPJY39+lNf8Sa9g==}
engines: {node: '>=22.19.0'}
hasBin: true
'@earendil-works/pi-coding-agent@0.75.4':
resolution: {integrity: sha512-Fb+FRo08b5H9pYKbQJ708/5OKL0+K/yclhfCMEhrBzSPTZZ4c85nY1YsBo4qwL20ohBMlBezHMRuHzcJ1ylEoQ==}
'@earendil-works/pi-coding-agent@0.79.9':
resolution: {integrity: sha512-8TZ796Zn0NE4vmhxG9hv4ZtJDGJzhqMjlmFg8ZkUKxfqB7LJa4ums2jSJKtnyAZfAamN6VzqzN0A82RNDqv8Ag==}
engines: {node: '>=22.19.0'}
hasBin: true
'@earendil-works/pi-tui@0.75.4':
resolution: {integrity: sha512-PDhKU7u6fmEcvHUFHzrRwGc/Ytokj/hO+X4RPf+MWKEGpvg3B1vHv88Ee+Dy33004tYkQF5YeXV4btJZcp5x1g==}
'@earendil-works/pi-tui@0.79.9':
resolution: {integrity: sha512-XcqfoGyoX64OSMQklMR1vG2MRi1TCPSUERRCmPDvYKCK6LtZoBqJ6idNCLRklNYveCj4gHDOzOsLhGqn04jmYw==}
engines: {node: '>=22.19.0'}
'@google/genai@1.52.0':
@@ -394,76 +394,81 @@ packages:
'@modelcontextprotocol/sdk':
optional: true
'@mariozechner/clipboard-darwin-arm64@0.3.6':
resolution: {integrity: sha512-HjaisYCAbHi/1+N1yDAQHc8ZXGffufIUT5NSOSVR3f3AuMDusxTtnbK8tZ7JFDkShua1oNGZoNwQHsc8MPtE0Q==}
'@mariozechner/clipboard-darwin-arm64@0.3.9':
resolution: {integrity: sha512-BfgV7vCEWZwJwZJw03r6bP5+tf0iI/ANuQYCxi9RNn7FrWB3yzGuMKCrNLRl6V761vXRdL8+OqZ0wd4TqlsNOQ==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [darwin]
'@mariozechner/clipboard-darwin-universal@0.3.6':
resolution: {integrity: sha512-8BWtPjOtJOJoykml3w0fx0zRrfWP31mXrJwfoA7xzNprkZw1uolCNfgmjDiVBseoKjp16EGITz7bN+61qn8dWA==}
'@mariozechner/clipboard-darwin-universal@0.3.9':
resolution: {integrity: sha512-BGGR4iA9Z2shAjI65eI5xtyb3LYNlDW9X3gxKxDbqtbnREohsrqznov6zpKoIrsRWpzlYVEdKphS7ksJ0/ndSQ==}
engines: {node: '>= 10'}
os: [darwin]
'@mariozechner/clipboard-darwin-x64@0.3.6':
resolution: {integrity: sha512-p9syiZD1kU4I+1ya7f7g+zD1GiUvR8fdlRlNmgsZNWlyjtc8rlV2EjTLd/35x1LsdBq020GVvtzp0ZmPgBI09Q==}
'@mariozechner/clipboard-darwin-x64@0.3.9':
resolution: {integrity: sha512-4kURmCbS6nt8uYhtmWpUcJWyPHfmAr5dTpXD1nO3pIfa+TSQ9DbrGOYCKH+aEFW47XhQ4Vp8ZTszie+wfFvDKg==}
engines: {node: '>= 10'}
cpu: [x64]
os: [darwin]
'@mariozechner/clipboard-linux-arm64-gnu@0.3.6':
resolution: {integrity: sha512-5JFf5rGofrm+V29HNF+wLthXphHdQpMbKDUYJ5tML6/Z5DLlLOV/9Ak4kDPtYyZ+Dzf+kAusE0VsFg4+tfP1IA==}
'@mariozechner/clipboard-linux-arm64-gnu@0.3.9':
resolution: {integrity: sha512-g59OkUGP2DDfCOIKypHeYgv2M55u/cKvXa5dSxFbEJ34XvIQMdcVmpKCkGUro3ZgefXiGVdwguvTMQGpHWzIXw==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@mariozechner/clipboard-linux-arm64-musl@0.3.6':
resolution: {integrity: sha512-JlVjxxw0GbGC0djXYWRIqyteO3J1KZ/QG3udlEFaOD5TLOM1FnmXXAPDQBqr+aBVr720ef9K00dirYnJ0LDCtw==}
'@mariozechner/clipboard-linux-arm64-musl@0.3.9':
resolution: {integrity: sha512-AGuJdgKsmJdm4Pych7kv3sqe591ERRaAHW3xjLooiFzn8J+PxUyof++7YZrB5Y5tpnTO+K18Og3taj2NpluCRQ==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@mariozechner/clipboard-linux-riscv64-gnu@0.3.6':
resolution: {integrity: sha512-4t8BUi5zZ+L77otFQVnVSlaTyAX4TVk9EqQm4syMrEQp96trFEHEwwNHcNEBGzYv5+K7mxay50TthYkz47OWzQ==}
'@mariozechner/clipboard-linux-riscv64-gnu@0.3.9':
resolution: {integrity: sha512-DXBEAiuMpk7dhS1a9NzNxVAFi1vaKoPu7rQNgY8LIDLGrK3lnIp3nT10DUum+PKVJoJppIP+NAA8IZe4DMNDPw==}
engines: {node: '>= 10'}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@mariozechner/clipboard-linux-x64-gnu@0.3.6':
resolution: {integrity: sha512-trtPwcNLW37irwQCJLtCxLw757jjJZk3TSnY/MU9bhtWtA3K9b/eLW0e4RGhUXDoFRds9opNWWaUDuFLa8dm0w==}
'@mariozechner/clipboard-linux-x64-gnu@0.3.9':
resolution: {integrity: sha512-WORrMLd6EpElEME7JRKfSaY34nW1P5LbdgK5YNCS1ncG2LqmITsSMEJ8nh2mpvxb3TxqbOOKgY7k9eMJYlW9Mw==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@mariozechner/clipboard-linux-x64-musl@0.3.6':
resolution: {integrity: sha512-WfnzIvOCCWQiN0MmltCEo6cLceUDbYe+I7xyFZjaps5A+2Op/M2CY7Rey+C4ucQhrvmpoHmTSFgY9ODWk7snoA==}
'@mariozechner/clipboard-linux-x64-musl@0.3.9':
resolution: {integrity: sha512-/DHn+1DrfL6oRaPPWXaOKvonFFrni666fxd+zFqiQEfvBH0tsHVWjq9iqBk0oDp0qaPA72lIMy5BptxISBEhZQ==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
libc: [musl]
'@mariozechner/clipboard-win32-arm64-msvc@0.3.6':
resolution: {integrity: sha512-+8+1aHYsBPUjmW3otmWlg+Hijt0iJvoBBs5e0mxFeUd4gDaKMB8Bn6x7c6KVtscg7E5j5NFXnwQqNSIAO4p8zQ==}
'@mariozechner/clipboard-win32-arm64-msvc@0.3.9':
resolution: {integrity: sha512-O5FHD3ErkMwMhNzAfu3ggy0ug4z7btZuoQgwwxlzPrwV2bxlD6WDpqBY4NCgICAgZdDKdp+loUEKVAVt8aYnhQ==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [win32]
'@mariozechner/clipboard-win32-x64-msvc@0.3.6':
resolution: {integrity: sha512-S4xfPmERC8ZkiLHe3vekZCjdDwNEETCuvCgQK2kP6/TnvmUkq1y2Pk+DjM4t8uh9KMX9bH4zs5ePcKa8GTXmfg==}
'@mariozechner/clipboard-win32-x64-msvc@0.3.9':
resolution: {integrity: sha512-ihQC3EufqEY81vhXBgVBtK4prL+wc62zJsSvxrgz7K1hsdt6OObz6v9p3Rn1OG3GJksTTKMJF0u/guMISHPhSA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
'@mariozechner/clipboard@0.3.6':
resolution: {integrity: sha512-MXdtr+6+ntlIVHdrZYuZNQydu6o8yZswFJ2Ln81j2O/Y9B/LDHvEaIm95xWNPkjGTWriSOeLnQJRFs6dYb60bg==}
'@mariozechner/clipboard@0.3.9':
resolution: {integrity: sha512-ABnA53mdfkGZwOFUdZNv2S0CWGO/EIuPj8Vv9xmBFmSYg/qFc7ihO6q5FcQjvoE67kZpWkEc4AhD6B/os04yuA==}
engines: {node: '>= 10'}
'@mistralai/mistralai@2.2.1':
resolution: {integrity: sha512-uKU8CZmL2RzYKmplsU01hii4p3pe4HqJefpWNRWXm1Tcm0Sm4xXfwSLIy4k7ZCPlbETCGcp69E7hZs+WOJ5itQ==}
'@mistralai/mistralai@2.2.6':
resolution: {integrity: sha512-W8pX7zHxjJvMIpw8JMxeJEleapXX0Q9NPszdNzqkM3MIEoIGPObdodujj+WHteXEvGfaP/AMwlNyRfEzSY6dQQ==}
peerDependencies:
'@opentelemetry/api': ^1.9.0
peerDependenciesMeta:
'@opentelemetry/api':
optional: true
'@nodable/entities@2.1.0':
resolution: {integrity: sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==}
@@ -516,6 +521,14 @@ packages:
'@octokit/types@16.0.0':
resolution: {integrity: sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==}
'@opentelemetry/api@1.9.0':
resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==}
engines: {node: '>=8.0.0'}
'@opentelemetry/semantic-conventions@1.41.1':
resolution: {integrity: sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==}
engines: {node: '>=14'}
'@pnpm/config.env-replace@1.1.0':
resolution: {integrity: sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==}
engines: {node: '>=12.22.0'}
@@ -636,6 +649,10 @@ packages:
resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==}
engines: {node: '>=14.0.0'}
'@smithy/node-http-handler@4.7.3':
resolution: {integrity: sha512-/jPhevcTFPMVl6KNjbaI47iOg1zxC7IsnX4PQDGVZKMFceOXtB8IEYaB7a9VvkP/3oC60WzTeKocvSI7vLT0vA==}
engines: {node: '>=18.0.0'}
'@smithy/node-http-handler@4.7.4':
resolution: {integrity: sha512-HIeF+1vrDGzPkkv39Hj2vlHSXHY3p958jd/8ZnePIY6+ZOsQX8coyEUKO5yQu4r0bQIVsbpotVIrXXwyycMStQ==}
engines: {node: '>=18.0.0'}
@@ -644,10 +661,6 @@ packages:
resolution: {integrity: sha512-e5UtkMvsatzBfbeBZjEOt0k0Z3BEsjTFL/n6fdO5vtBLe67tdy0dX7xw2DU7uZ3acwoHyeCqpU2Fzb7pxwHb6Q==}
engines: {node: '>=18.0.0'}
'@smithy/types@4.14.1':
resolution: {integrity: sha512-59b5HtSVrVR/eYNei3BUj3DCPKD/G7EtDDe7OEJE7i7FtQFugYo6MxbotS8mVJkLNVf8gYaAlEBwwtJ9HzhWSg==}
engines: {node: '>=18.0.0'}
'@smithy/types@4.14.2':
resolution: {integrity: sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw==}
engines: {node: '>=18.0.0'}
@@ -1182,10 +1195,6 @@ packages:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*}
get-east-asian-width@1.5.0:
resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==}
engines: {node: '>=18'}
get-east-asian-width@1.6.0:
resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==}
engines: {node: '>=18'}
@@ -1487,9 +1496,6 @@ packages:
jws@4.0.1:
resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==}
koffi@2.16.2:
resolution: {integrity: sha512-owU0MRwv6xkrVqCd+33uw6BaYppkTRXbO/rVdJNI2dvZG0gzyRhYwW25eWtc5pauwK8TGh3AbkFONSezdykfSA==}
lefthook-darwin-arm64@2.1.6:
resolution: {integrity: sha512-hyB7eeiX78BS66f70byTJacDLC/xV1vgMv9n+idFUsrM7J3Udd/ag9Ag5NP3t0eN0EqQqAtrNnt35EH01lxnRQ==}
cpu: [arm64]
@@ -1640,6 +1646,11 @@ packages:
engines: {node: '>= 18'}
hasBin: true
marked@18.0.5:
resolution: {integrity: sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w==}
engines: {node: '>= 20'}
hasBin: true
meow@12.1.1:
resolution: {integrity: sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==}
engines: {node: '>=16.10'}
@@ -2107,6 +2118,11 @@ packages:
engines: {node: '>=10'}
hasBin: true
semver@7.8.0:
resolution: {integrity: sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==}
engines: {node: '>=10'}
hasBin: true
shebang-command@2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
engines: {node: '>=8'}
@@ -2350,8 +2366,8 @@ packages:
resolution: {integrity: sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==}
engines: {node: '>=20.18.1'}
undici@8.3.0:
resolution: {integrity: sha512-TkUDgb6tl7KOGZ+7e8E3d2FYgUQgF6z5YypqjWmixVQSQERFcVrVg0ySADm2LVLRh5ljAaHTCR5Fmz3Q34rB7Q==}
undici@8.5.0:
resolution: {integrity: sha512-xamtWoB1EshgjpmlXd7GGm2VfdDtw1+rD8uhry8pSNW3If6S8E0m2T2+orSKeZXEn/aPJMviCpDBA65WJt8zhg==}
engines: {node: '>=22.19.0'}
unicode-emoji-modifier-base@1.0.0:
@@ -2572,7 +2588,7 @@ snapshots:
'@smithy/core': 3.24.4
'@smithy/fetch-http-handler': 5.4.4
'@smithy/node-http-handler': 4.7.4
'@smithy/types': 4.14.1
'@smithy/types': 4.14.2
tslib: 2.8.1
'@aws-sdk/core@3.974.13':
@@ -2721,7 +2737,7 @@ snapshots:
'@aws-sdk/nested-clients': 3.997.11
'@aws-sdk/types': 3.973.9
'@smithy/core': 3.24.4
'@smithy/types': 4.14.1
'@smithy/types': 4.14.2
tslib: 2.8.1
'@aws-sdk/token-providers@3.1052.0':
@@ -2962,9 +2978,9 @@ snapshots:
picocolors: 1.1.1
optional: true
'@earendil-works/pi-agent-core@0.75.4(ws@8.20.0)(zod@4.3.6)':
'@earendil-works/pi-agent-core@0.79.9(ws@8.20.0)(zod@4.3.6)':
dependencies:
'@earendil-works/pi-ai': 0.75.4(ws@8.20.0)(zod@4.3.6)
'@earendil-works/pi-ai': 0.79.9(ws@8.20.0)(zod@4.3.6)
ignore: 7.0.5
typebox: 1.1.38
yaml: 2.9.0
@@ -2976,12 +2992,14 @@ snapshots:
- ws
- zod
'@earendil-works/pi-ai@0.75.4(ws@8.20.0)(zod@4.3.6)':
'@earendil-works/pi-ai@0.79.9(ws@8.20.0)(zod@4.3.6)':
dependencies:
'@anthropic-ai/sdk': 0.91.1(zod@4.3.6)
'@aws-sdk/client-bedrock-runtime': 3.1048.0
'@google/genai': 1.52.0
'@mistralai/mistralai': 2.2.1
'@mistralai/mistralai': 2.2.6(@opentelemetry/api@1.9.0)
'@opentelemetry/api': 1.9.0
'@smithy/node-http-handler': 4.7.3
http-proxy-agent: 7.0.2
https-proxy-agent: 7.0.6
openai: 6.26.0(ws@8.20.0)(zod@4.3.6)
@@ -2995,11 +3013,11 @@ snapshots:
- ws
- zod
'@earendil-works/pi-coding-agent@0.75.4(ws@8.20.0)(zod@4.3.6)':
'@earendil-works/pi-coding-agent@0.79.9(ws@8.20.0)(zod@4.3.6)':
dependencies:
'@earendil-works/pi-agent-core': 0.75.4(ws@8.20.0)(zod@4.3.6)
'@earendil-works/pi-ai': 0.75.4(ws@8.20.0)(zod@4.3.6)
'@earendil-works/pi-tui': 0.75.4
'@earendil-works/pi-agent-core': 0.79.9(ws@8.20.0)(zod@4.3.6)
'@earendil-works/pi-ai': 0.79.9(ws@8.20.0)(zod@4.3.6)
'@earendil-works/pi-tui': 0.79.9
'@silvia-odwyer/photon-node': 0.3.4
chalk: 5.6.2
cross-spawn: 7.0.6
@@ -3011,11 +3029,12 @@ snapshots:
jiti: 2.7.0
minimatch: 10.2.5
proper-lockfile: 4.1.2
semver: 7.8.0
typebox: 1.1.38
undici: 8.3.0
undici: 8.5.0
yaml: 2.9.0
optionalDependencies:
'@mariozechner/clipboard': 0.3.6
'@mariozechner/clipboard': 0.3.9
transitivePeerDependencies:
- '@modelcontextprotocol/sdk'
- bufferutil
@@ -3024,12 +3043,10 @@ snapshots:
- ws
- zod
'@earendil-works/pi-tui@0.75.4':
'@earendil-works/pi-tui@0.79.9':
dependencies:
get-east-asian-width: 1.6.0
marked: 15.0.12
optionalDependencies:
koffi: 2.16.2
marked: 18.0.5
'@google/genai@1.52.0':
dependencies:
@@ -3042,55 +3059,58 @@ snapshots:
- supports-color
- utf-8-validate
'@mariozechner/clipboard-darwin-arm64@0.3.6':
'@mariozechner/clipboard-darwin-arm64@0.3.9':
optional: true
'@mariozechner/clipboard-darwin-universal@0.3.6':
'@mariozechner/clipboard-darwin-universal@0.3.9':
optional: true
'@mariozechner/clipboard-darwin-x64@0.3.6':
'@mariozechner/clipboard-darwin-x64@0.3.9':
optional: true
'@mariozechner/clipboard-linux-arm64-gnu@0.3.6':
'@mariozechner/clipboard-linux-arm64-gnu@0.3.9':
optional: true
'@mariozechner/clipboard-linux-arm64-musl@0.3.6':
'@mariozechner/clipboard-linux-arm64-musl@0.3.9':
optional: true
'@mariozechner/clipboard-linux-riscv64-gnu@0.3.6':
'@mariozechner/clipboard-linux-riscv64-gnu@0.3.9':
optional: true
'@mariozechner/clipboard-linux-x64-gnu@0.3.6':
'@mariozechner/clipboard-linux-x64-gnu@0.3.9':
optional: true
'@mariozechner/clipboard-linux-x64-musl@0.3.6':
'@mariozechner/clipboard-linux-x64-musl@0.3.9':
optional: true
'@mariozechner/clipboard-win32-arm64-msvc@0.3.6':
'@mariozechner/clipboard-win32-arm64-msvc@0.3.9':
optional: true
'@mariozechner/clipboard-win32-x64-msvc@0.3.6':
'@mariozechner/clipboard-win32-x64-msvc@0.3.9':
optional: true
'@mariozechner/clipboard@0.3.6':
'@mariozechner/clipboard@0.3.9':
optionalDependencies:
'@mariozechner/clipboard-darwin-arm64': 0.3.6
'@mariozechner/clipboard-darwin-universal': 0.3.6
'@mariozechner/clipboard-darwin-x64': 0.3.6
'@mariozechner/clipboard-linux-arm64-gnu': 0.3.6
'@mariozechner/clipboard-linux-arm64-musl': 0.3.6
'@mariozechner/clipboard-linux-riscv64-gnu': 0.3.6
'@mariozechner/clipboard-linux-x64-gnu': 0.3.6
'@mariozechner/clipboard-linux-x64-musl': 0.3.6
'@mariozechner/clipboard-win32-arm64-msvc': 0.3.6
'@mariozechner/clipboard-win32-x64-msvc': 0.3.6
'@mariozechner/clipboard-darwin-arm64': 0.3.9
'@mariozechner/clipboard-darwin-universal': 0.3.9
'@mariozechner/clipboard-darwin-x64': 0.3.9
'@mariozechner/clipboard-linux-arm64-gnu': 0.3.9
'@mariozechner/clipboard-linux-arm64-musl': 0.3.9
'@mariozechner/clipboard-linux-riscv64-gnu': 0.3.9
'@mariozechner/clipboard-linux-x64-gnu': 0.3.9
'@mariozechner/clipboard-linux-x64-musl': 0.3.9
'@mariozechner/clipboard-win32-arm64-msvc': 0.3.9
'@mariozechner/clipboard-win32-x64-msvc': 0.3.9
optional: true
'@mistralai/mistralai@2.2.1':
'@mistralai/mistralai@2.2.6(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/semantic-conventions': 1.41.1
ws: 8.20.0
zod: 4.3.6
zod-to-json-schema: 3.25.2(zod@4.3.6)
optionalDependencies:
'@opentelemetry/api': 1.9.0
transitivePeerDependencies:
- bufferutil
- utf-8-validate
@@ -3157,6 +3177,10 @@ snapshots:
dependencies:
'@octokit/openapi-types': 27.0.0
'@opentelemetry/api@1.9.0': {}
'@opentelemetry/semantic-conventions@1.41.1': {}
'@pnpm/config.env-replace@1.1.0': {}
'@pnpm/network.ca-file@1.0.2':
@@ -3322,6 +3346,12 @@ snapshots:
dependencies:
tslib: 2.8.1
'@smithy/node-http-handler@4.7.3':
dependencies:
'@smithy/core': 3.24.4
'@smithy/types': 4.14.2
tslib: 2.8.1
'@smithy/node-http-handler@4.7.4':
dependencies:
'@smithy/core': 3.24.4
@@ -3334,10 +3364,6 @@ snapshots:
'@smithy/types': 4.14.2
tslib: 2.8.1
'@smithy/types@4.14.1':
dependencies:
tslib: 2.8.1
'@smithy/types@4.14.2':
dependencies:
tslib: 2.8.1
@@ -3902,8 +3928,6 @@ snapshots:
get-caller-file@2.0.5: {}
get-east-asian-width@1.5.0: {}
get-east-asian-width@1.6.0: {}
get-stream@6.0.1: {}
@@ -4195,9 +4219,6 @@ snapshots:
jwa: 2.0.1
safe-buffer: 5.2.1
koffi@2.16.2:
optional: true
lefthook-darwin-arm64@2.1.6:
optional: true
@@ -4323,6 +4344,8 @@ snapshots:
marked@15.0.12: {}
marked@18.0.5: {}
meow@12.1.1: {}
meow@13.2.0: {}
@@ -4393,7 +4416,7 @@ snapshots:
normalize-package-data@8.0.0:
dependencies:
hosted-git-info: 9.0.2
hosted-git-info: 9.0.3
semver: 7.7.4
validate-npm-package-license: 3.0.4
@@ -4722,6 +4745,8 @@ snapshots:
semver@7.7.4: {}
semver@7.8.0: {}
shebang-command@2.0.0:
dependencies:
shebang-regex: 3.0.0
@@ -4782,7 +4807,7 @@ snapshots:
string-width@7.2.0:
dependencies:
emoji-regex: 10.6.0
get-east-asian-width: 1.5.0
get-east-asian-width: 1.6.0
strip-ansi: 7.2.0
string_decoder@1.1.1:
@@ -4928,7 +4953,7 @@ snapshots:
undici@7.25.0: {}
undici@8.3.0: {}
undici@8.5.0: {}
unicode-emoji-modifier-base@1.0.0: {}
+4 -4
View File
@@ -79,8 +79,8 @@ async function runAnswerCommand(
ctx: ExtensionCommandContext,
remoteAsk?: RemoteAskRuntime
): Promise<void> {
if (!ctx.hasUI) {
ctx.ui.notify("/answer requires interactive UI.", "error");
if (ctx.mode !== "tui") {
ctx.ui.notify("/answer requires interactive TUI mode.", "error");
return;
}
@@ -253,8 +253,8 @@ async function runReplayCommand(
source: AskPayloadSource;
}
): Promise<void> {
if (!ctx.hasUI) {
ctx.ui.notify("Ask replay requires interactive UI.", "error");
if (ctx.mode !== "tui") {
ctx.ui.notify("Ask replay requires interactive TUI mode.", "error");
return;
}
const lookup = findLatestPayloadInCurrentBranch(ctx, options.source);
+1 -1
View File
@@ -8,7 +8,7 @@ export function registerAskSettingsCommand(pi: ExtensionAPI) {
pi.registerCommand("ask-settings", {
description: "Open ask settings",
handler: async (_args: string, ctx: ExtensionCommandContext) => {
await showAskSettings(ctx.ui);
await showAskSettings(ctx);
},
});
}
+2 -2
View File
@@ -153,8 +153,8 @@ function formatNonInteractiveMessage(
state: ReturnType<typeof createInitialState>
): string {
const lines = [
"Needs user input: ask_user requires interactive UI.",
"Run same tool call in interactive session, or ask user these questions manually:",
"Needs user input: ask_user requires interactive TUI mode.",
"Run same tool call in interactive TUI mode, or ask user these questions manually:",
];
for (const [index, question] of state.questions.entries()) {
+1 -1
View File
@@ -67,7 +67,7 @@ async function executeAskTool(
source: "tool",
sourceEntryId: toolCallId,
});
if (!ctx.hasUI) {
if (ctx.mode !== "tui") {
return nonInteractiveResponse(validation.state);
}
ctx.ui.setWorkingVisible(false);
+4
View File
@@ -171,6 +171,10 @@ export function getAskConfigStore(): AskConfigStore {
return askConfigStore;
}
export function resetAskConfigStore(): void {
askConfigStore = undefined;
}
export function getAskConfigPath(): string {
return join(getAgentDir(), "extensions", "eko24ive-pi-ask.json");
}
+5
View File
@@ -4,6 +4,7 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { registerAnswerCommands } from "./answer-commands.ts";
import { registerAskSettingsCommand } from "./ask-settings-command.ts";
import { registerAskTool } from "./ask-tool.ts";
import { resetAskConfigStore } from "./config/store.ts";
import { createRemoteAskRuntime } from "./remote-ask.ts";
const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
@@ -15,10 +16,14 @@ const CONFIGURATION_DOC_PATH = resolve(
const PI_ASK_CONFIG_PROMPT = `When the user asks to configure, customize, debug, or explain @eko24ive/pi-ask settings or keymaps, first read ${CONFIGURATION_DOC_PATH} and follow it as the source of truth before editing config files.`;
export default function askExtension(pi: ExtensionAPI) {
resetAskConfigStore();
pi.on("before_agent_start", async (event) => ({
systemPrompt: `${event.systemPrompt}\n\n${PI_ASK_CONFIG_PROMPT}`,
}));
const remoteAsk = createRemoteAskRuntime(pi.events);
pi.on("session_shutdown", () => {
remoteAsk.disposeAll();
});
registerAskTool(pi, remoteAsk);
registerAskSettingsCommand(pi);
registerAnswerCommands(pi, remoteAsk);
+6 -2
View File
@@ -95,6 +95,7 @@ export interface RemoteAskFlowHandle {
}
export interface RemoteAskRuntime {
disposeAll: () => void;
startFlow: (flow: RemoteAskFlowInput) => RemoteAskFlowHandle;
}
@@ -102,12 +103,15 @@ type EventBus = ExtensionAPI["events"];
export function createRemoteAskRuntime(events: EventBus): RemoteAskRuntime {
const activeFlows = new Map<string, ActiveRemoteAskFlow>();
events.on(PI_ASK_SUBMIT_EVENT, (data) => {
const unsubscribeSubmit = events.on(PI_ASK_SUBMIT_EVENT, (data) => {
handleSubmitEvent(events, activeFlows, data);
});
return {
disposeAll() {
activeFlows.clear();
unsubscribeSubmit();
},
startFlow(flow) {
return startRemoteAskFlow(events, activeFlows, flow);
},
+4 -1
View File
@@ -13,10 +13,13 @@ const FD_BINARY_NAMES =
* themselves when reusing CombinedAutocompleteProvider for `@` file mentions.
*/
export function createAskAutocompleteProvider(cwd: string) {
return new CombinedAutocompleteProvider(
return Object.assign(
new CombinedAutocompleteProvider(
[],
cwd,
findAutocompleteBinary(FD_BINARY_NAMES)
),
{ triggerCharacters: ["@"] }
);
}
+14 -2
View File
@@ -113,6 +113,12 @@ export async function runAskFlow(
presentSingleAsMulti:
options.presentSingleAsMulti ?? config.behaviour.presentSingleAsMulti,
};
if (ctx.mode !== "tui") {
return {
...toAskResult(createInitialState(params, flowOptions)),
cancelled: true,
};
}
return ctx.ui.custom<AskResult>((...args) =>
createAskFlowController(args, {
...params,
@@ -172,9 +178,15 @@ function createAskFlowController(
});
return {
get focused() {
return controller.editor.focused;
},
set focused(value: boolean) {
controller.editor.focused = value;
},
render: (width: number) => renderController(controller, width),
invalidate() {
// The editor manages its own async autocomplete redraws.
controller.editor.invalidate();
},
handleInput(data: string) {
handleControllerInput(controller, data);
@@ -536,7 +548,7 @@ function showSettingsModal(controller: AskFlowController) {
return;
}
controller.settingsOpen = true;
showAskSettings(controller.ctx.ui).finally(() => {
showAskSettings(controller.ctx).finally(() => {
controller.settingsOpen = false;
refresh(controller);
});
+6 -2
View File
@@ -3,11 +3,15 @@ import { getAskConfigPath, getAskConfigStore } from "../config/store.ts";
import { AskSettingsList } from "./settings-list.ts";
export async function showAskSettings(
ui: Pick<ExtensionContext, "ui">["ui"]
ctx: Pick<ExtensionContext, "mode" | "ui">
): Promise<void> {
if (ctx.mode !== "tui") {
ctx.ui.notify("/ask-settings requires interactive TUI mode.", "error");
return;
}
const store = getAskConfigStore();
const { config, notice } = await store.ensureLoaded();
return ui.custom<void>(
return ctx.ui.custom<void>(
(tui, theme, _keybindings, done) =>
new AskSettingsList(theme, {
configPath: getAskConfigPath(),
+47
View File
@@ -0,0 +1,47 @@
import assert from "node:assert/strict";
import test from "node:test";
import { registerAnswerCommands } from "../src/answer-commands.ts";
function registerCommands() {
const commands = new Map<
string,
{ handler: (args: string, ctx: any) => Promise<void> }
>();
registerAnswerCommands({
registerCommand(
name: string,
command: { handler: (args: string, ctx: any) => Promise<void> }
) {
commands.set(name, command);
},
} as never);
return commands;
}
test("answer commands do not open custom UI outside TUI mode", async () => {
const commands = registerCommands();
const notifications: Array<{ message: string; type: string }> = [];
let customOpened = false;
const ctx = {
mode: "rpc",
ui: {
custom() {
customOpened = true;
},
notify(message: string, type: string) {
notifications.push({ message, type });
},
},
};
await commands.get("answer")?.handler("", ctx);
await commands.get("answer:again")?.handler("", ctx);
await commands.get("ask:replay")?.handler("", ctx);
assert.equal(customOpened, false);
assert.deepEqual(notifications, [
{ message: "/answer requires interactive TUI mode.", type: "error" },
{ message: "Ask replay requires interactive TUI mode.", type: "error" },
{ message: "Ask replay requires interactive TUI mode.", type: "error" },
]);
});
+38
View File
@@ -37,6 +37,7 @@ test("registers /ask-settings and opens the shared settings overlay", async () =
const customCalls: Array<{ options: unknown; lines: string[] }> = [];
await commands.get("ask-settings")?.handler("", {
mode: "tui",
ui: {
custom(callback: any, options: unknown) {
const done = () => {
@@ -75,3 +76,40 @@ test("registers /ask-settings and opens the shared settings overlay", async () =
delete process.env.PI_CODING_AGENT_DIR;
await rm(agentDir, { force: true, recursive: true });
});
test("/ask-settings notifies outside TUI instead of opening custom UI", async () => {
const commands = new Map<
string,
{ handler: (args: string, ctx: any) => Promise<void> }
>();
registerAskSettingsCommand({
registerCommand(
name: string,
command: { handler: (args: string, ctx: any) => Promise<void> }
) {
commands.set(name, command);
},
} as never);
const notifications: Array<{ message: string; type: string }> = [];
let customOpened = false;
await commands.get("ask-settings")?.handler("", {
mode: "rpc",
ui: {
custom() {
customOpened = true;
},
notify(message: string, type: string) {
notifications.push({ message, type });
},
},
});
assert.equal(customOpened, false);
assert.deepEqual(notifications, [
{
message: "/ask-settings requires interactive TUI mode.",
type: "error",
},
]);
});
+22 -3
View File
@@ -4,7 +4,7 @@ import { registerAskTool } from "../src/ask-tool.ts";
import type { AskParams } from "../src/types.ts";
const NON_INTERACTIVE_MESSAGE_RE =
/Needs user input: ask_user requires interactive UI\./;
/Needs user input: ask_user requires interactive TUI mode\./;
const FIRST_QUESTION_RE = /1\. Goal: What should I optimize for\?/;
const SPEED_OPTION_RE = /- Speed \[speed\]/;
const CUSTOM_OPTION_RE = /- Type your own \[custom\]/;
@@ -47,8 +47,8 @@ function registerMockTool() {
};
}
function makeCtx(hasUi: boolean): unknown {
return { [HAS_UI]: hasUi };
function makeCtx(hasUi: boolean, mode = hasUi ? "tui" : "print"): unknown {
return { [HAS_UI]: hasUi, mode };
}
function sampleParams(): AskParams {
@@ -113,6 +113,25 @@ test("ask tool returns pending questions in non-interactive mode", async () => {
assert.match(result.content[0].text, CUSTOM_OPTION_RE);
});
test("ask tool does not open custom UI outside TUI mode", async () => {
const { tool } = registerMockTool();
let customOpened = false;
const result = await tool.execute("call-1", sampleParams(), undefined, noop, {
[HAS_UI]: true,
mode: "rpc",
ui: {
custom() {
customOpened = true;
},
},
});
assert.equal(customOpened, false);
assert.equal(result.details.cancelled, true);
assert.match(result.content[0].text, NON_INTERACTIVE_MESSAGE_RE);
});
test("ask tool includes custom answer fallback for preview questions", async () => {
const { tool } = registerMockTool();
+9
View File
@@ -0,0 +1,9 @@
import assert from "node:assert/strict";
import test from "node:test";
import { createAskAutocompleteProvider } from "../src/ui/autocomplete.ts";
test("ask autocomplete explicitly triggers on file mention marker", () => {
const provider = createAskAutocompleteProvider(process.cwd());
assert.deepEqual(provider.triggerCharacters, ["@"]);
});
+37 -1
View File
@@ -5,7 +5,11 @@ import { dirname, join } from "node:path";
import test from "node:test";
import { DEFAULT_ASK_CONFIG } from "../src/config/defaults.ts";
import type { AskConfig } from "../src/config/schema.ts";
import { AskConfigStore } from "../src/config/store.ts";
import {
AskConfigStore,
getAskConfigStore,
resetAskConfigStore,
} from "../src/config/store.ts";
const DEFAULT_KEYMAPS_NOTICE_PATTERN =
/Using default ask keymaps for this session/;
@@ -31,6 +35,38 @@ async function makeTempPath(name: string): Promise<string> {
return join(root, "eko24ive-pi-ask.json");
}
test("resetAskConfigStore reloads the global store from disk", async () => {
const root = await import("node:fs/promises").then(({ mkdtemp }) =>
mkdtemp(join(tmpdir(), "pi-ask-config-reset-"))
);
const path = join(root, "extensions", "eko24ive-pi-ask.json");
await mkdir(dirname(path), { recursive: true });
await writeFile(
path,
JSON.stringify({
...expectedConfigFile(),
behaviour: {
...DEFAULT_ASK_CONFIG.behaviour,
autoSubmitWhenAnsweredWithoutNotes: true,
},
})
);
process.env.PI_CODING_AGENT_DIR = root;
resetAskConfigStore();
getAskConfigStore().setConfig(DEFAULT_ASK_CONFIG);
resetAskConfigStore();
const result = await getAskConfigStore().ensureLoaded();
assert.equal(
result.config.behaviour.autoSubmitWhenAnsweredWithoutNotes,
true
);
delete process.env.PI_CODING_AGENT_DIR;
resetAskConfigStore();
await rm(root, { force: true, recursive: true });
});
test("config store writes defaults when file is missing", async () => {
const path = await makeTempPath("pi-ask-config-missing-");
const store = new AskConfigStore(path);
+74
View File
@@ -1,5 +1,6 @@
import assert from "node:assert/strict";
import test from "node:test";
import { Editor } from "@earendil-works/pi-tui";
import { DEFAULT_ASK_CONFIG } from "../src/config/defaults.ts";
import { getAskConfigStore } from "../src/config/store.ts";
import { createInitialState } from "../src/state/create.ts";
@@ -279,6 +280,7 @@ test("custom editor submit key controls actual editor submission", async () => {
const resultPromise = runAskFlow(
{
cwd: process.cwd(),
mode: "tui",
ui: {
custom(callback: (...args: unknown[]) => unknown) {
return new Promise((resolve) => {
@@ -331,6 +333,78 @@ function plainTheme() {
};
}
test("ask flow forwards focus and invalidation to its editor", async () => {
getAskConfigStore().setConfig({
...DEFAULT_ASK_CONFIG,
notifications: {
...DEFAULT_ASK_CONFIG.notifications,
enabled: false,
},
});
const originalInvalidate = Editor.prototype.invalidate;
let invalidateCalls = 0;
Editor.prototype.invalidate = function patchedInvalidate(this: Editor) {
invalidateCalls += 1;
return originalInvalidate.call(this);
};
let component:
| {
focused: boolean;
handleInput(data: string): void;
invalidate(): void;
}
| undefined;
try {
const resultPromise = runAskFlow(
{
cwd: process.cwd(),
mode: "tui",
ui: {
custom(callback: (...args: unknown[]) => unknown) {
return new Promise((resolve) => {
component = callback(
{
requestRender() {
// Rendering is not needed for this controller test.
},
},
plainTheme(),
{},
resolve
) as typeof component;
});
},
},
} as never,
{
questions: [
{
id: "q1",
prompt: "Question?",
options: [{ value: "a", label: "A" }],
},
],
}
);
await new Promise((resolve) => setImmediate(resolve));
assert(component);
component.focused = true;
assert.equal(component.focused, true);
component.focused = false;
assert.equal(component.focused, false);
component.invalidate();
assert.equal(invalidateCalls, 1);
component.handleInput("\x1b");
const result = await resultPromise;
assert.equal(result.cancelled, true);
} finally {
Editor.prototype.invalidate = originalInvalidate;
getAskConfigStore().setConfig(DEFAULT_ASK_CONFIG);
}
});
test("custom configured note shortcuts are used at runtime", () => {
const navigation = createInitialState({
questions: [
+40
View File
@@ -172,6 +172,45 @@ test("started event questions are cloned", async () => {
assert.equal(questions[0].options[0].label, "Yes");
});
test("remote runtime disposes active flows and submit listener", () => {
const bus = new TestEventBus();
const remoteAsk = createRemoteAskRuntime(bus as never);
let submitCalls = 0;
const flow = remoteAsk.startFlow({
source: "tool",
questions: createInitialState({
questions: [
{
id: "decision",
prompt: "Proceed?",
options: [{ value: "yes", label: "Yes" }],
},
],
}).questions,
onSubmit: () => {
submitCalls += 1;
return { ok: true };
},
});
remoteAsk.disposeAll();
bus.emit(PI_ASK_SUBMIT_EVENT, {
version: 1,
requestId: "request-1",
flowId: flow.flowId,
response: {
kind: "answer",
answers: { decision: { values: ["yes"] } },
},
});
assert.equal(submitCalls, 0);
assert.equal(
bus.events.some((event) => event.channel === PI_ASK_SUBMIT_RESULT_EVENT),
false
);
});
test("remote submit resolves an active ask flow and emits lifecycle events", async () => {
getAskConfigStore().setConfig({
...DEFAULT_ASK_CONFIG,
@@ -197,6 +236,7 @@ test("remote submit resolves an active ask flow and emits lifecycle events", asy
const result = await runAskFlow(
{
cwd: process.cwd(),
mode: "tui",
ui: {
custom(callback: (...args: unknown[]) => unknown) {
return new Promise((resolve) => {