@mariozechner/pi-coding-agent 0.50.8 → 0.51.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.
Files changed (122) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/README.md +4 -3
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +2 -0
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/cli/session-picker.d.ts.map +1 -1
  7. package/dist/cli/session-picker.js +3 -1
  8. package/dist/cli/session-picker.js.map +1 -1
  9. package/dist/config.d.ts.map +1 -1
  10. package/dist/config.js +9 -0
  11. package/dist/config.js.map +1 -1
  12. package/dist/core/extensions/index.d.ts +2 -2
  13. package/dist/core/extensions/index.d.ts.map +1 -1
  14. package/dist/core/extensions/index.js +1 -1
  15. package/dist/core/extensions/index.js.map +1 -1
  16. package/dist/core/extensions/types.d.ts +67 -5
  17. package/dist/core/extensions/types.d.ts.map +1 -1
  18. package/dist/core/extensions/types.js +4 -1
  19. package/dist/core/extensions/types.js.map +1 -1
  20. package/dist/core/extensions/wrapper.d.ts.map +1 -1
  21. package/dist/core/extensions/wrapper.js +1 -1
  22. package/dist/core/extensions/wrapper.js.map +1 -1
  23. package/dist/core/keybindings.d.ts +1 -1
  24. package/dist/core/keybindings.d.ts.map +1 -1
  25. package/dist/core/keybindings.js +2 -0
  26. package/dist/core/keybindings.js.map +1 -1
  27. package/dist/core/model-registry.d.ts.map +1 -1
  28. package/dist/core/model-registry.js +19 -17
  29. package/dist/core/model-registry.js.map +1 -1
  30. package/dist/core/package-manager.d.ts.map +1 -1
  31. package/dist/core/package-manager.js +11 -9
  32. package/dist/core/package-manager.js.map +1 -1
  33. package/dist/core/skills.d.ts.map +1 -1
  34. package/dist/core/skills.js +1 -0
  35. package/dist/core/skills.js.map +1 -1
  36. package/dist/core/tools/bash.d.ts +11 -0
  37. package/dist/core/tools/bash.d.ts.map +1 -1
  38. package/dist/core/tools/bash.js +18 -3
  39. package/dist/core/tools/bash.js.map +1 -1
  40. package/dist/core/tools/edit.d.ts +2 -0
  41. package/dist/core/tools/edit.d.ts.map +1 -1
  42. package/dist/core/tools/edit.js.map +1 -1
  43. package/dist/core/tools/find.d.ts +2 -0
  44. package/dist/core/tools/find.d.ts.map +1 -1
  45. package/dist/core/tools/find.js.map +1 -1
  46. package/dist/core/tools/grep.d.ts +2 -0
  47. package/dist/core/tools/grep.d.ts.map +1 -1
  48. package/dist/core/tools/grep.js.map +1 -1
  49. package/dist/core/tools/index.d.ts +7 -7
  50. package/dist/core/tools/index.d.ts.map +1 -1
  51. package/dist/core/tools/index.js +5 -5
  52. package/dist/core/tools/index.js.map +1 -1
  53. package/dist/core/tools/ls.d.ts +2 -0
  54. package/dist/core/tools/ls.d.ts.map +1 -1
  55. package/dist/core/tools/ls.js.map +1 -1
  56. package/dist/core/tools/read.d.ts +2 -0
  57. package/dist/core/tools/read.d.ts.map +1 -1
  58. package/dist/core/tools/read.js.map +1 -1
  59. package/dist/core/tools/write.d.ts +2 -0
  60. package/dist/core/tools/write.d.ts.map +1 -1
  61. package/dist/core/tools/write.js.map +1 -1
  62. package/dist/index.d.ts +3 -3
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +1 -1
  65. package/dist/index.js.map +1 -1
  66. package/dist/modes/interactive/components/session-selector-search.d.ts +3 -1
  67. package/dist/modes/interactive/components/session-selector-search.d.ts.map +1 -1
  68. package/dist/modes/interactive/components/session-selector-search.js +13 -4
  69. package/dist/modes/interactive/components/session-selector-search.js.map +1 -1
  70. package/dist/modes/interactive/components/session-selector.d.ts +11 -2
  71. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  72. package/dist/modes/interactive/components/session-selector.js +58 -12
  73. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  74. package/dist/modes/interactive/components/tree-selector.d.ts +6 -0
  75. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  76. package/dist/modes/interactive/components/tree-selector.js +43 -16
  77. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  78. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  79. package/dist/modes/interactive/interactive-mode.js +6 -15
  80. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  81. package/dist/utils/clipboard-image.d.ts.map +1 -1
  82. package/dist/utils/clipboard-image.js +6 -3
  83. package/dist/utils/clipboard-image.js.map +1 -1
  84. package/dist/utils/clipboard-native.d.ts +7 -0
  85. package/dist/utils/clipboard-native.d.ts.map +1 -0
  86. package/dist/utils/clipboard-native.js +14 -0
  87. package/dist/utils/clipboard-native.js.map +1 -0
  88. package/dist/utils/clipboard.d.ts.map +1 -1
  89. package/dist/utils/clipboard.js +10 -1
  90. package/dist/utils/clipboard.js.map +1 -1
  91. package/dist/utils/tools-manager.d.ts.map +1 -1
  92. package/dist/utils/tools-manager.js +14 -0
  93. package/dist/utils/tools-manager.js.map +1 -1
  94. package/docs/extensions.md +57 -9
  95. package/docs/keybindings.md +1 -0
  96. package/docs/models.md +43 -14
  97. package/docs/rpc.md +188 -1
  98. package/docs/termux.md +127 -0
  99. package/examples/extensions/README.md +2 -0
  100. package/examples/extensions/antigravity-image-gen.ts +1 -1
  101. package/examples/extensions/bash-spawn-hook.ts +30 -0
  102. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  103. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  104. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  105. package/examples/extensions/custom-provider-qwen-cli/package.json +1 -1
  106. package/examples/extensions/hello.ts +1 -1
  107. package/examples/extensions/question.ts +1 -1
  108. package/examples/extensions/questionnaire.ts +1 -1
  109. package/examples/extensions/rpc-demo.ts +124 -0
  110. package/examples/extensions/sandbox/index.ts +1 -1
  111. package/examples/extensions/shutdown-command.ts +2 -2
  112. package/examples/extensions/ssh.ts +4 -4
  113. package/examples/extensions/subagent/index.ts +1 -1
  114. package/examples/extensions/titlebar-spinner.ts +58 -0
  115. package/examples/extensions/todo.ts +1 -1
  116. package/examples/extensions/tool-override.ts +1 -1
  117. package/examples/extensions/truncated-tool.ts +1 -1
  118. package/examples/extensions/with-deps/package-lock.json +2 -2
  119. package/examples/extensions/with-deps/package.json +1 -1
  120. package/examples/rpc-extension-ui.ts +632 -0
  121. package/examples/sdk/06-extensions.ts +1 -1
  122. package/package.json +7 -5
package/docs/rpc.md CHANGED
@@ -903,6 +903,191 @@ Emitted when an extension throws an error.
903
903
  }
904
904
  ```
905
905
 
906
+ ## Extension UI Protocol
907
+
908
+ Extensions can request user interaction via `ctx.ui.select()`, `ctx.ui.confirm()`, etc. In RPC mode, these are translated into a request/response sub-protocol on top of the base command/event flow.
909
+
910
+ There are two categories of extension UI methods:
911
+
912
+ - **Dialog methods** (`select`, `confirm`, `input`, `editor`): emit an `extension_ui_request` on stdout and block until the client sends back an `extension_ui_response` on stdin with the matching `id`.
913
+ - **Fire-and-forget methods** (`notify`, `setStatus`, `setWidget`, `setTitle`, `set_editor_text`): emit an `extension_ui_request` on stdout but do not expect a response. The client can display the information or ignore it.
914
+
915
+ If a dialog method includes a `timeout` field, the agent-side will auto-resolve with a default value when the timeout expires. The client does not need to track timeouts.
916
+
917
+ Some `ExtensionUIContext` methods are not supported in RPC mode because they require direct TUI access:
918
+ - `custom()` returns `undefined`
919
+ - `setWorkingMessage()`, `setFooter()`, `setHeader()`, `setEditorComponent()` are no-ops
920
+ - `getEditorText()` returns `""`
921
+
922
+ ### Extension UI Requests (stdout)
923
+
924
+ All requests have `type: "extension_ui_request"`, a unique `id`, and a `method` field.
925
+
926
+ #### select
927
+
928
+ Prompt the user to choose from a list. Dialog methods with a `timeout` field include the timeout in milliseconds; the agent auto-resolves with `undefined` if the client doesn't respond in time.
929
+
930
+ ```json
931
+ {
932
+ "type": "extension_ui_request",
933
+ "id": "uuid-1",
934
+ "method": "select",
935
+ "title": "Allow dangerous command?",
936
+ "options": ["Allow", "Block"],
937
+ "timeout": 10000
938
+ }
939
+ ```
940
+
941
+ Expected response: `extension_ui_response` with `value` (the selected option string) or `cancelled: true`.
942
+
943
+ #### confirm
944
+
945
+ Prompt the user for yes/no confirmation.
946
+
947
+ ```json
948
+ {
949
+ "type": "extension_ui_request",
950
+ "id": "uuid-2",
951
+ "method": "confirm",
952
+ "title": "Clear session?",
953
+ "message": "All messages will be lost.",
954
+ "timeout": 5000
955
+ }
956
+ ```
957
+
958
+ Expected response: `extension_ui_response` with `confirmed: true/false` or `cancelled: true`.
959
+
960
+ #### input
961
+
962
+ Prompt the user for free-form text.
963
+
964
+ ```json
965
+ {
966
+ "type": "extension_ui_request",
967
+ "id": "uuid-3",
968
+ "method": "input",
969
+ "title": "Enter a value",
970
+ "placeholder": "type something..."
971
+ }
972
+ ```
973
+
974
+ Expected response: `extension_ui_response` with `value` (the entered text) or `cancelled: true`.
975
+
976
+ #### editor
977
+
978
+ Open a multi-line text editor with optional prefilled content.
979
+
980
+ ```json
981
+ {
982
+ "type": "extension_ui_request",
983
+ "id": "uuid-4",
984
+ "method": "editor",
985
+ "title": "Edit some text",
986
+ "prefill": "Line 1\nLine 2\nLine 3"
987
+ }
988
+ ```
989
+
990
+ Expected response: `extension_ui_response` with `value` (the edited text) or `cancelled: true`.
991
+
992
+ #### notify
993
+
994
+ Display a notification. Fire-and-forget, no response expected.
995
+
996
+ ```json
997
+ {
998
+ "type": "extension_ui_request",
999
+ "id": "uuid-5",
1000
+ "method": "notify",
1001
+ "message": "Command blocked by user",
1002
+ "notifyType": "warning"
1003
+ }
1004
+ ```
1005
+
1006
+ The `notifyType` field is `"info"`, `"warning"`, or `"error"`. Defaults to `"info"` if omitted.
1007
+
1008
+ #### setStatus
1009
+
1010
+ Set or clear a status entry in the footer/status bar. Fire-and-forget.
1011
+
1012
+ ```json
1013
+ {
1014
+ "type": "extension_ui_request",
1015
+ "id": "uuid-6",
1016
+ "method": "setStatus",
1017
+ "statusKey": "my-ext",
1018
+ "statusText": "Turn 3 running..."
1019
+ }
1020
+ ```
1021
+
1022
+ Send `statusText: undefined` (or omit it) to clear the status entry for that key.
1023
+
1024
+ #### setWidget
1025
+
1026
+ Set or clear a widget (block of text lines) displayed above or below the editor. Fire-and-forget.
1027
+
1028
+ ```json
1029
+ {
1030
+ "type": "extension_ui_request",
1031
+ "id": "uuid-7",
1032
+ "method": "setWidget",
1033
+ "widgetKey": "my-ext",
1034
+ "widgetLines": ["--- My Widget ---", "Line 1", "Line 2"],
1035
+ "widgetPlacement": "aboveEditor"
1036
+ }
1037
+ ```
1038
+
1039
+ Send `widgetLines: undefined` (or omit it) to clear the widget. The `widgetPlacement` field is `"aboveEditor"` (default) or `"belowEditor"`. Only string arrays are supported in RPC mode; component factories are ignored.
1040
+
1041
+ #### setTitle
1042
+
1043
+ Set the terminal window/tab title. Fire-and-forget.
1044
+
1045
+ ```json
1046
+ {
1047
+ "type": "extension_ui_request",
1048
+ "id": "uuid-8",
1049
+ "method": "setTitle",
1050
+ "title": "pi - my project"
1051
+ }
1052
+ ```
1053
+
1054
+ #### set_editor_text
1055
+
1056
+ Set the text in the input editor. Fire-and-forget.
1057
+
1058
+ ```json
1059
+ {
1060
+ "type": "extension_ui_request",
1061
+ "id": "uuid-9",
1062
+ "method": "set_editor_text",
1063
+ "text": "prefilled text for the user"
1064
+ }
1065
+ ```
1066
+
1067
+ ### Extension UI Responses (stdin)
1068
+
1069
+ Responses are sent for dialog methods only (`select`, `confirm`, `input`, `editor`). The `id` must match the request.
1070
+
1071
+ #### Value response (select, input, editor)
1072
+
1073
+ ```json
1074
+ {"type": "extension_ui_response", "id": "uuid-1", "value": "Allow"}
1075
+ ```
1076
+
1077
+ #### Confirmation response (confirm)
1078
+
1079
+ ```json
1080
+ {"type": "extension_ui_response", "id": "uuid-2", "confirmed": true}
1081
+ ```
1082
+
1083
+ #### Cancellation response (any dialog)
1084
+
1085
+ Dismiss any dialog method. The extension receives `undefined` (for select/input/editor) or `false` (for confirm).
1086
+
1087
+ ```json
1088
+ {"type": "extension_ui_response", "id": "uuid-3", "cancelled": true}
1089
+ ```
1090
+
906
1091
  ## Error Handling
907
1092
 
908
1093
  Failed commands return a response with `success: false`:
@@ -933,7 +1118,7 @@ Source files:
933
1118
  - [`packages/ai/src/types.ts`](../../ai/src/types.ts) - `Model`, `UserMessage`, `AssistantMessage`, `ToolResultMessage`
934
1119
  - [`packages/agent/src/types.ts`](../../agent/src/types.ts) - `AgentMessage`, `AgentEvent`
935
1120
  - [`src/core/messages.ts`](../src/core/messages.ts) - `BashExecutionMessage`
936
- - [`src/modes/rpc/rpc-types.ts`](../src/modes/rpc/rpc-types.ts) - RPC command/response types
1121
+ - [`src/modes/rpc/rpc-types.ts`](../src/modes/rpc/rpc-types.ts) - RPC command/response types, extension UI request/response types
937
1122
 
938
1123
  ### Model
939
1124
 
@@ -1082,6 +1267,8 @@ for event in read_events():
1082
1267
 
1083
1268
  See [`test/rpc-example.ts`](../test/rpc-example.ts) for a complete interactive example, or [`src/modes/rpc/rpc-client.ts`](../src/modes/rpc/rpc-client.ts) for a typed client implementation.
1084
1269
 
1270
+ For a complete example of handling the extension UI protocol, see [`examples/rpc-extension-ui.ts`](../examples/rpc-extension-ui.ts) which pairs with the [`examples/extensions/rpc-demo.ts`](../examples/extensions/rpc-demo.ts) extension.
1271
+
1085
1272
  ```javascript
1086
1273
  const { spawn } = require("child_process");
1087
1274
  const readline = require("readline");
package/docs/termux.md ADDED
@@ -0,0 +1,127 @@
1
+ # Termux (Android) Setup
2
+
3
+ Pi runs on Android via [Termux](https://termux.dev/), a terminal emulator and Linux environment for Android.
4
+
5
+ ## Prerequisites
6
+
7
+ 1. Install [Termux](https://github.com/termux/termux-app#installation) from GitHub or F-Droid (not Google Play, that version is deprecated)
8
+ 2. Install [Termux:API](https://github.com/termux/termux-api#installation) from GitHub or F-Droid for clipboard and other device integrations
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ # Update packages
14
+ pkg update && pkg upgrade
15
+
16
+ # Install dependencies
17
+ pkg install nodejs termux-api git
18
+
19
+ # Install pi
20
+ npm install -g @mariozechner/pi-coding-agent
21
+
22
+ # Create config directory
23
+ mkdir -p ~/.pi/agent
24
+
25
+ # Run pi
26
+ pi
27
+ ```
28
+
29
+ ## Clipboard Support
30
+
31
+ Clipboard operations use `termux-clipboard-set` and `termux-clipboard-get` when running in Termux. The Termux:API app must be installed for these to work.
32
+
33
+ Image clipboard is not supported on Termux (the `ctrl+v` image paste feature will not work).
34
+
35
+ ## Example AGENTS.md for Termux
36
+
37
+ Create `~/.pi/agent/AGENTS.md` to help the agent understand the Termux environment:
38
+
39
+ ```markdown
40
+ # Agent Environment: Termux on Android
41
+
42
+ ## Location
43
+ - **OS**: Android (Termux terminal emulator)
44
+ - **Home**: `/data/data/com.termux/files/home`
45
+ - **Prefix**: `/data/data/com.termux/files/usr`
46
+ - **Shared storage**: `/storage/emulated/0` (Downloads, Documents, etc.)
47
+
48
+ ## Opening URLs
49
+ ```bash
50
+ termux-open-url "https://example.com"
51
+ ```
52
+
53
+ ## Opening Files
54
+ ```bash
55
+ termux-open file.pdf # Opens with default app
56
+ termux-open -c image.jpg # Choose app
57
+ ```
58
+
59
+ ## Clipboard
60
+ ```bash
61
+ termux-clipboard-set "text" # Copy
62
+ termux-clipboard-get # Paste
63
+ ```
64
+
65
+ ## Notifications
66
+ ```bash
67
+ termux-notification -t "Title" -c "Content"
68
+ ```
69
+
70
+ ## Device Info
71
+ ```bash
72
+ termux-battery-status # Battery info
73
+ termux-wifi-connectioninfo # WiFi info
74
+ termux-telephony-deviceinfo # Device info
75
+ ```
76
+
77
+ ## Sharing
78
+ ```bash
79
+ termux-share -a send file.txt # Share file
80
+ ```
81
+
82
+ ## Other Useful Commands
83
+ ```bash
84
+ termux-toast "message" # Quick toast popup
85
+ termux-vibrate # Vibrate device
86
+ termux-tts-speak "hello" # Text to speech
87
+ termux-camera-photo out.jpg # Take photo
88
+ ```
89
+
90
+ ## Notes
91
+ - Termux:API app must be installed for `termux-*` commands
92
+ - Use `pkg install termux-api` for the command-line tools
93
+ - Storage permission needed for `/storage/emulated/0` access
94
+ ```
95
+
96
+ ## Limitations
97
+
98
+ - **No image clipboard**: Termux clipboard API only supports text
99
+ - **No native binaries**: Some optional native dependencies (like the clipboard module) are unavailable on Android ARM64 and are skipped during installation
100
+ - **Storage access**: To access files in `/storage/emulated/0` (Downloads, etc.), run `termux-setup-storage` once to grant permissions
101
+
102
+ ## Troubleshooting
103
+
104
+ ### Clipboard not working
105
+
106
+ Ensure both apps are installed:
107
+ 1. Termux (from GitHub or F-Droid)
108
+ 2. Termux:API (from GitHub or F-Droid)
109
+
110
+ Then install the CLI tools:
111
+ ```bash
112
+ pkg install termux-api
113
+ ```
114
+
115
+ ### Permission denied for shared storage
116
+
117
+ Run once to grant storage permissions:
118
+ ```bash
119
+ termux-setup-storage
120
+ ```
121
+
122
+ ### Node.js installation issues
123
+
124
+ If npm fails, try clearing the cache:
125
+ ```bash
126
+ npm cache clean --force
127
+ ```
@@ -53,9 +53,11 @@ cp permission-gate.ts ~/.pi/agent/extensions/
53
53
  | `snake.ts` | Snake game with custom UI, keyboard handling, and session persistence |
54
54
  | `send-user-message.ts` | Demonstrates `pi.sendUserMessage()` for sending user messages from extensions |
55
55
  | `timed-confirm.ts` | Demonstrates AbortSignal for auto-dismissing `ctx.ui.confirm()` and `ctx.ui.select()` dialogs |
56
+ | `rpc-demo.ts` | Exercises all RPC-supported extension UI methods; pair with [`examples/rpc-extension-ui.ts`](../rpc-extension-ui.ts) |
56
57
  | `modal-editor.ts` | Custom vim-like modal editor via `ctx.ui.setEditorComponent()` |
57
58
  | `rainbow-editor.ts` | Animated rainbow text effect via custom editor |
58
59
  | `notify.ts` | Desktop notifications via OSC 777 when agent finishes (Ghostty, iTerm2, WezTerm) |
60
+ | `titlebar-spinner.ts` | Braille spinner animation in terminal title while the agent is working |
59
61
  | `summarize.ts` | Summarize conversation with GPT-5.2 and show in transient UI |
60
62
  | `custom-footer.ts` | Custom footer with git branch and token stats via `ctx.ui.setFooter()` |
61
63
  | `custom-header.ts` | Custom header via `ctx.ui.setHeader()` |
@@ -352,7 +352,7 @@ export default function antigravityImageGen(pi: ExtensionAPI) {
352
352
  description:
353
353
  "Generate an image via Google Antigravity image models. Returns the image as a tool result attachment. Optional saving via save=project|global|custom|none, or PI_IMAGE_SAVE_MODE/PI_IMAGE_SAVE_DIR.",
354
354
  parameters: TOOL_PARAMS,
355
- async execute(_toolCallId, params: ToolParams, onUpdate, ctx, signal) {
355
+ async execute(_toolCallId, params: ToolParams, signal, onUpdate, ctx) {
356
356
  const { accessToken, projectId } = await getCredentials(ctx);
357
357
  const model = params.model || DEFAULT_MODEL;
358
358
  const aspectRatio = params.aspectRatio || DEFAULT_ASPECT_RATIO;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Bash Spawn Hook Example
3
+ *
4
+ * Adjusts command, cwd, and env before execution.
5
+ *
6
+ * Usage:
7
+ * pi -e ./bash-spawn-hook.ts
8
+ */
9
+
10
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
11
+ import { createBashTool } from "@mariozechner/pi-coding-agent";
12
+
13
+ export default function (pi: ExtensionAPI) {
14
+ const cwd = process.cwd();
15
+
16
+ const bashTool = createBashTool(cwd, {
17
+ spawnHook: ({ command, cwd, env }) => ({
18
+ command: `source ~/.profile\n${command}`,
19
+ cwd,
20
+ env: { ...env, PI_SPAWN_HOOK: "1" },
21
+ }),
22
+ });
23
+
24
+ pi.registerTool({
25
+ ...bashTool,
26
+ execute: async (id, params, signal, onUpdate, _ctx) => {
27
+ return bashTool.execute(id, params, signal, onUpdate);
28
+ },
29
+ });
30
+ }
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider",
3
- "version": "1.1.8",
3
+ "version": "1.2.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-custom-provider",
9
- "version": "1.1.8",
9
+ "version": "1.2.0",
10
10
  "dependencies": {
11
11
  "@anthropic-ai/sdk": "^0.52.0"
12
12
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider-anthropic",
3
3
  "private": true,
4
- "version": "1.1.8",
4
+ "version": "1.2.0",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider-gitlab-duo",
3
3
  "private": true,
4
- "version": "1.1.8",
4
+ "version": "1.2.0",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider-qwen-cli",
3
3
  "private": true,
4
- "version": "1.0.1",
4
+ "version": "1.1.0",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -14,7 +14,7 @@ export default function (pi: ExtensionAPI) {
14
14
  name: Type.String({ description: "Name to greet" }),
15
15
  }),
16
16
 
17
- async execute(_toolCallId, params, _onUpdate, _ctx, _signal) {
17
+ async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
18
18
  const { name } = params as { name: string };
19
19
  return {
20
20
  content: [{ type: "text", text: `Hello, ${name}!` }],
@@ -40,7 +40,7 @@ export default function question(pi: ExtensionAPI) {
40
40
  description: "Ask the user a question and let them pick from options. Use when you need user input to proceed.",
41
41
  parameters: QuestionParams,
42
42
 
43
- async execute(_toolCallId, params, _onUpdate, ctx, _signal) {
43
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
44
44
  if (!ctx.hasUI) {
45
45
  return {
46
46
  content: [{ type: "text", text: "Error: UI not available (running in non-interactive mode)" }],
@@ -81,7 +81,7 @@ export default function questionnaire(pi: ExtensionAPI) {
81
81
  "Ask the user one or more questions. Use for clarifying requirements, getting preferences, or confirming decisions. For single questions, shows a simple option list. For multiple questions, shows a tab-based interface.",
82
82
  parameters: QuestionnaireParams,
83
83
 
84
- async execute(_toolCallId, params, _onUpdate, ctx, _signal) {
84
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
85
85
  if (!ctx.hasUI) {
86
86
  return errorResult("Error: UI not available (running in non-interactive mode)");
87
87
  }
@@ -0,0 +1,124 @@
1
+ /**
2
+ * RPC Extension UI Demo
3
+ *
4
+ * Purpose-built extension that exercises all RPC-supported extension UI methods.
5
+ * Designed to be loaded alongside the rpc-extension-ui-example.ts script to
6
+ * demonstrate the full extension UI protocol.
7
+ *
8
+ * UI methods exercised:
9
+ * - select() - on tool_call for dangerous bash commands
10
+ * - confirm() - on session_before_switch
11
+ * - input() - via /rpc-input command
12
+ * - editor() - via /rpc-editor command
13
+ * - notify() - after each dialog completes
14
+ * - setStatus() - on turn_start/turn_end
15
+ * - setWidget() - on session_start
16
+ * - setTitle() - on session_start and session_switch
17
+ * - setEditorText() - via /rpc-prefill command
18
+ */
19
+
20
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
21
+
22
+ export default function (pi: ExtensionAPI) {
23
+ let turnCount = 0;
24
+
25
+ // -- setTitle, setWidget, setStatus on session lifecycle --
26
+
27
+ pi.on("session_start", async (_event, ctx) => {
28
+ ctx.ui.setTitle("pi RPC Demo");
29
+ ctx.ui.setWidget("rpc-demo", ["--- RPC Extension UI Demo ---", "Loaded and ready."]);
30
+ ctx.ui.setStatus("rpc-demo", `Turns: ${turnCount}`);
31
+ });
32
+
33
+ pi.on("session_switch", async (_event, ctx) => {
34
+ turnCount = 0;
35
+ ctx.ui.setTitle("pi RPC Demo (new session)");
36
+ ctx.ui.setStatus("rpc-demo", `Turns: ${turnCount}`);
37
+ });
38
+
39
+ // -- setStatus on turn lifecycle --
40
+
41
+ pi.on("turn_start", async (_event, ctx) => {
42
+ turnCount++;
43
+ ctx.ui.setStatus("rpc-demo", `Turn ${turnCount} running...`);
44
+ });
45
+
46
+ pi.on("turn_end", async (_event, ctx) => {
47
+ ctx.ui.setStatus("rpc-demo", `Turn ${turnCount} done`);
48
+ });
49
+
50
+ // -- select on dangerous tool calls --
51
+
52
+ pi.on("tool_call", async (event, ctx) => {
53
+ if (event.toolName !== "bash") return undefined;
54
+
55
+ const command = event.input.command as string;
56
+ const isDangerous = /\brm\s+(-rf?|--recursive)/i.test(command) || /\bsudo\b/i.test(command);
57
+
58
+ if (isDangerous) {
59
+ if (!ctx.hasUI) {
60
+ return { block: true, reason: "Dangerous command blocked (no UI)" };
61
+ }
62
+
63
+ const choice = await ctx.ui.select(`Dangerous command: ${command}`, ["Allow", "Block"]);
64
+ if (choice !== "Allow") {
65
+ ctx.ui.notify("Command blocked by user", "warning");
66
+ return { block: true, reason: "Blocked by user" };
67
+ }
68
+ ctx.ui.notify("Command allowed", "info");
69
+ }
70
+
71
+ return undefined;
72
+ });
73
+
74
+ // -- confirm on session clear --
75
+
76
+ pi.on("session_before_switch", async (event, ctx) => {
77
+ if (event.reason !== "new") return;
78
+ if (!ctx.hasUI) return;
79
+
80
+ const confirmed = await ctx.ui.confirm("Clear session?", "All messages will be lost.");
81
+ if (!confirmed) {
82
+ ctx.ui.notify("Clear cancelled", "info");
83
+ return { cancel: true };
84
+ }
85
+ });
86
+
87
+ // -- input via command --
88
+
89
+ pi.registerCommand("rpc-input", {
90
+ description: "Prompt for text input (demonstrates ctx.ui.input in RPC)",
91
+ handler: async (_args, ctx) => {
92
+ const value = await ctx.ui.input("Enter a value", "type something...");
93
+ if (value) {
94
+ ctx.ui.notify(`You entered: ${value}`, "info");
95
+ } else {
96
+ ctx.ui.notify("Input cancelled", "info");
97
+ }
98
+ },
99
+ });
100
+
101
+ // -- editor via command --
102
+
103
+ pi.registerCommand("rpc-editor", {
104
+ description: "Open multi-line editor (demonstrates ctx.ui.editor in RPC)",
105
+ handler: async (_args, ctx) => {
106
+ const text = await ctx.ui.editor("Edit some text", "Line 1\nLine 2\nLine 3");
107
+ if (text) {
108
+ ctx.ui.notify(`Editor submitted (${text.split("\n").length} lines)`, "info");
109
+ } else {
110
+ ctx.ui.notify("Editor cancelled", "info");
111
+ }
112
+ },
113
+ });
114
+
115
+ // -- setEditorText via command --
116
+
117
+ pi.registerCommand("rpc-prefill", {
118
+ description: "Prefill the input editor (demonstrates ctx.ui.setEditorText in RPC)",
119
+ handler: async (_args, ctx) => {
120
+ ctx.ui.setEditorText("This text was set by the rpc-demo extension.");
121
+ ctx.ui.notify("Editor prefilled", "info");
122
+ },
123
+ });
124
+ }
@@ -211,7 +211,7 @@ export default function (pi: ExtensionAPI) {
211
211
  pi.registerTool({
212
212
  ...localBash,
213
213
  label: "bash (sandboxed)",
214
- async execute(id, params, onUpdate, _ctx, signal) {
214
+ async execute(id, params, signal, onUpdate, _ctx) {
215
215
  if (!sandboxEnabled || !sandboxInitialized) {
216
216
  return localBash.execute(id, params, signal, onUpdate);
217
217
  }
@@ -23,7 +23,7 @@ export default function (pi: ExtensionAPI) {
23
23
  label: "Finish and Exit",
24
24
  description: "Complete a task and exit pi",
25
25
  parameters: Type.Object({}),
26
- async execute(_toolCallId, _params, _onUpdate, ctx, _signal) {
26
+ async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
27
27
  // Do any final work here...
28
28
  // Request graceful shutdown (deferred until agent is idle)
29
29
  ctx.shutdown();
@@ -44,7 +44,7 @@ export default function (pi: ExtensionAPI) {
44
44
  parameters: Type.Object({
45
45
  environment: Type.String({ description: "Target environment (e.g., production, staging)" }),
46
46
  }),
47
- async execute(_toolCallId, params, onUpdate, ctx, _signal) {
47
+ async execute(_toolCallId, params, _signal, onUpdate, ctx) {
48
48
  onUpdate?.({ content: [{ type: "text", text: `Deploying to ${params.environment}...` }], details: {} });
49
49
 
50
50
  // Example deployment logic
@@ -127,7 +127,7 @@ export default function (pi: ExtensionAPI) {
127
127
 
128
128
  pi.registerTool({
129
129
  ...localRead,
130
- async execute(id, params, onUpdate, _ctx, signal) {
130
+ async execute(id, params, signal, onUpdate, _ctx) {
131
131
  const ssh = getSsh();
132
132
  if (ssh) {
133
133
  const tool = createReadTool(localCwd, {
@@ -141,7 +141,7 @@ export default function (pi: ExtensionAPI) {
141
141
 
142
142
  pi.registerTool({
143
143
  ...localWrite,
144
- async execute(id, params, onUpdate, _ctx, signal) {
144
+ async execute(id, params, signal, onUpdate, _ctx) {
145
145
  const ssh = getSsh();
146
146
  if (ssh) {
147
147
  const tool = createWriteTool(localCwd, {
@@ -155,7 +155,7 @@ export default function (pi: ExtensionAPI) {
155
155
 
156
156
  pi.registerTool({
157
157
  ...localEdit,
158
- async execute(id, params, onUpdate, _ctx, signal) {
158
+ async execute(id, params, signal, onUpdate, _ctx) {
159
159
  const ssh = getSsh();
160
160
  if (ssh) {
161
161
  const tool = createEditTool(localCwd, {
@@ -169,7 +169,7 @@ export default function (pi: ExtensionAPI) {
169
169
 
170
170
  pi.registerTool({
171
171
  ...localBash,
172
- async execute(id, params, onUpdate, _ctx, signal) {
172
+ async execute(id, params, signal, onUpdate, _ctx) {
173
173
  const ssh = getSsh();
174
174
  if (ssh) {
175
175
  const tool = createBashTool(localCwd, {
@@ -416,7 +416,7 @@ export default function (pi: ExtensionAPI) {
416
416
  ].join(" "),
417
417
  parameters: SubagentParams,
418
418
 
419
- async execute(_toolCallId, params, onUpdate, ctx, signal) {
419
+ async execute(_toolCallId, params, signal, onUpdate, ctx) {
420
420
  const agentScope: AgentScope = params.agentScope ?? "user";
421
421
  const discovery = discoverAgents(ctx.cwd, agentScope);
422
422
  const agents = discovery.agents;