@hyperspaceng/neural-coding-agent 0.60.0 → 0.61.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,8 +4,48 @@
4
4
 
5
5
  ### Fixed
6
6
 
7
+ - Fixed `ctrl+z` suspend and `fg` resume reliability by keeping the process alive until the `SIGCONT` handler restores the TUI, avoiding immediate process exit in environments with no other live event-loop handles ([#2454](https://github.com/badlogic/pi-mono/issues/2454))
8
+ - Fixed `createAgentSession({ agentDir })` to derive the default persisted session path from the provided `agentDir`, keeping session storage aligned with settings, auth, models, and resource loading ([#2457](https://github.com/badlogic/pi-mono/issues/2457))
9
+
10
+ ## [0.61.0] - 2026-03-20
11
+
12
+ ### New Features
13
+
14
+ - Namespaced keybinding ids and a unified keybinding manager across the app and TUI. See [docs/keybindings.md](docs/keybindings.md) and [docs/extensions.md](docs/extensions.md).
15
+ - JSONL session export and import via `/export <path.jsonl>` and `/import <path.jsonl>`. See [README.md](README.md) and [docs/session.md](docs/session.md).
16
+ - Resizable sidebar in HTML share and export views. See [README.md](README.md).
17
+
18
+ ### Breaking Changes
19
+
20
+ - Interactive keybinding ids are now namespaced, and `keybindings.json` now uses those same canonical namespaced ids. Older config files are migrated automatically on startup. Custom editors and extension UI components still receive an injected `keybindings: KeybindingsManager`. They do not call `getKeybindings()` or `setKeybindings()` themselves. Declaration merging applies to that injected type ([#2391](https://github.com/badlogic/pi-mono/issues/2391))
21
+ - Extension author migration: update `keyHint()`, `keyText()`, and injected `keybindings.matches(...)` calls from old built-in names like `"expandTools"`, `"selectConfirm"`, and `"interrupt"` to namespaced ids like `"app.tools.expand"`, `"tui.select.confirm"`, and `"app.interrupt"`. See [docs/keybindings.md](docs/keybindings.md) for the full list. `pi.registerShortcut("ctrl+shift+p", ...)` is unchanged because extension shortcuts still use raw key combos, not keybinding ids.
22
+
23
+ ### Added
24
+
25
+ - Added `gpt-5.4-mini` to the `openai-codex` model catalog ([#2334](https://github.com/badlogic/pi-mono/pull/2334) by [@justram](https://github.com/justram))
26
+ - Added JSONL session export and import via `/export <path.jsonl>` and `/import <path.jsonl>` ([#2356](https://github.com/badlogic/pi-mono/pull/2356) by [@hjanuschka](https://github.com/hjanuschka))
27
+ - Added a resizable sidebar to HTML share and export views ([#2435](https://github.com/badlogic/pi-mono/pull/2435) by [@dmmulroy](https://github.com/dmmulroy))
28
+
29
+ ### Fixed
30
+
7
31
  - Tests for session-selector-rename and tree-selector are now keybinding-agnostic, resetting editor keybindings to defaults before each test so user `keybindings.json` cannot cause failures ([#2360](https://github.com/badlogic/pi-mono/issues/2360))
8
- - Fixed Windows bash execution hanging for commands that spawn detached descendants inheriting stdout/stderr handles, which caused `agent-browser` and similar commands to spin forever.
32
+ - Fixed custom `keybindings.json` overrides to shadow conflicting default shortcuts globally, so bindings such as `cursorUp: ["up", "ctrl+p"]` no longer leave default actions like model cycling active ([#2391](https://github.com/badlogic/pi-mono/issues/2391))
33
+ - Fixed concurrent `edit` and `write` mutations targeting the same file to run serially, preventing interleaved file writes from overwriting each other ([#2327](https://github.com/badlogic/pi-mono/issues/2327))
34
+ - Fixed RPC mode to redirect unexpected stdout writes to stderr so JSONL responses remain parseable ([#2388](https://github.com/badlogic/pi-mono/issues/2388))
35
+ - Fixed auto-retry with tool-using retry responses so `session.prompt()` waits for the full retry loop, including tool execution, before returning ([#2440](https://github.com/badlogic/pi-mono/pull/2440) by [@pasky](https://github.com/pasky))
36
+ - Fixed `/model` to refresh scoped model lists after `models.json` changes, avoiding stale selector contents ([#2408](https://github.com/badlogic/pi-mono/pull/2408) by [@Perlence](https://github.com/Perlence))
37
+ - Fixed `validateToolArguments()` to fall back gracefully when AJV schema compilation is blocked in restricted runtimes such as Cloudflare Workers, allowing tool execution to proceed without schema validation ([#2395](https://github.com/badlogic/pi-mono/issues/2395))
38
+ - Fixed CLI startup to suppress process warnings from leaking into terminal, print, and RPC output ([#2404](https://github.com/badlogic/pi-mono/issues/2404))
39
+ - Fixed bash tool rendering to show elapsed time at the bottom of the tool block ([#2406](https://github.com/badlogic/pi-mono/issues/2406))
40
+ - Fixed custom theme file watching to reload updated theme contents from disk instead of keeping stale cached theme data ([#2417](https://github.com/badlogic/pi-mono/issues/2417), [#2003](https://github.com/badlogic/pi-mono/issues/2003))
41
+ - Fixed footer Git branch refreshes to run asynchronously so branch watcher updates do not block the UI ([#2418](https://github.com/badlogic/pi-mono/issues/2418))
42
+ - Fixed invalid extension provider registrations to surface an extension error without preventing other providers from loading ([#2431](https://github.com/badlogic/pi-mono/issues/2431))
43
+ - Fixed Windows bash execution hanging for commands that spawn detached descendants inheriting stdout/stderr handles, which caused `agent-browser` and similar commands to spin forever ([#2389](https://github.com/badlogic/pi-mono/pull/2389) by [@mrexodia](https://github.com/mrexodia))
44
+ - Fixed `google-vertex` API key resolution to ignore placeholder auth markers like `<authenticated>` and fall back to ADC instead of sending them as literal API keys ([#2335](https://github.com/badlogic/pi-mono/issues/2335))
45
+ - Fixed desktop clipboard text copy to prefer native OS clipboard integration before shell fallbacks, improving reliability on macOS and Windows ([#2347](https://github.com/badlogic/pi-mono/issues/2347))
46
+ - Fixed Bun Bedrock provider registration to survive provider resets and session reloads in compiled binaries ([#2350](https://github.com/badlogic/pi-mono/pull/2350) by [@unexge](https://github.com/unexge))
47
+ - Fixed OpenRouter reasoning requests to use the provider's nested reasoning payload, restoring thinking level support for OpenRouter models and custom compat settings ([#2298](https://github.com/badlogic/pi-mono/pull/2298) by [@PriNova](https://github.com/PriNova))
48
+ - Fixed Bedrock application inference profiles to support prompt caching when `AWS_BEDROCK_FORCE_CACHE=1` is set, covering profile ARNs that do not expose the underlying Claude model name ([#2346](https://github.com/badlogic/pi-mono/pull/2346) by [@haoqixu](https://github.com/haoqixu))
9
49
 
10
50
  ## [0.60.0] - 2026-03-18
11
51
 
@@ -1354,6 +1354,36 @@ Use `promptGuidelines` to add tool-specific bullets to the default system prompt
1354
1354
 
1355
1355
  Note: Some models are idiots and include the @ prefix in tool path arguments. Built-in tools strip a leading @ before resolving paths. If your custom tool accepts a path, normalize a leading @ as well.
1356
1356
 
1357
+ If your custom tool mutates files, use `withFileMutationQueue()` so it participates in the same per-file queue as built-in `edit` and `write`. This matters because tool calls run in parallel by default. Without the queue, two tools can read the same old file contents, compute different updates, and then whichever write lands last overwrites the other.
1358
+
1359
+ Example failure case: your custom tool edits `foo.ts` while built-in `edit` also changes `foo.ts` in the same assistant turn. If your tool does not participate in the queue, both can read the original `foo.ts`, apply separate changes, and one of those changes is lost.
1360
+
1361
+ Pass the real target file path to `withFileMutationQueue()`, not the raw user argument. Resolve it to an absolute path first, relative to `ctx.cwd` or your tool's working directory. For existing files, the helper canonicalizes through `realpath()`, so symlink aliases for the same file share one queue. For new files, it falls back to the resolved absolute path because there is nothing to `realpath()` yet.
1362
+
1363
+ Queue the entire mutation window on that target path. That includes read-modify-write logic, not just the final write.
1364
+
1365
+ ```typescript
1366
+ import { withFileMutationQueue } from "@mariozechner/pi-coding-agent";
1367
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
1368
+ import { dirname, resolve } from "node:path";
1369
+
1370
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
1371
+ const absolutePath = resolve(ctx.cwd, params.path);
1372
+
1373
+ return withFileMutationQueue(absolutePath, async () => {
1374
+ await mkdir(dirname(absolutePath), { recursive: true });
1375
+ const current = await readFile(absolutePath, "utf8");
1376
+ const next = current.replace(params.oldText, params.newText);
1377
+ await writeFile(absolutePath, next, "utf8");
1378
+
1379
+ return {
1380
+ content: [{ type: "text", text: `Updated ${params.path}` }],
1381
+ details: {},
1382
+ };
1383
+ });
1384
+ }
1385
+ ```
1386
+
1357
1387
  ### Tool Definition
1358
1388
 
1359
1389
  ```typescript
@@ -1617,7 +1647,7 @@ renderResult(result, { expanded, isPartial }, theme) {
1617
1647
 
1618
1648
  #### Keybinding Hints
1619
1649
 
1620
- Use `keyHint()` to display keybinding hints that respect user's keybinding configuration:
1650
+ Use `keyHint()` to display keybinding hints that respect the active keybinding configuration:
1621
1651
 
1622
1652
  ```typescript
1623
1653
  import { keyHint } from "@mariozechner/pi-coding-agent";
@@ -1625,18 +1655,25 @@ import { keyHint } from "@mariozechner/pi-coding-agent";
1625
1655
  renderResult(result, { expanded }, theme) {
1626
1656
  let text = theme.fg("success", "✓ Done");
1627
1657
  if (!expanded) {
1628
- text += ` (${keyHint("expandTools", "to expand")})`;
1658
+ text += ` (${keyHint("app.tools.expand", "to expand")})`;
1629
1659
  }
1630
1660
  return new Text(text, 0, 0);
1631
1661
  }
1632
1662
  ```
1633
1663
 
1634
1664
  Available functions:
1635
- - `keyHint(action, description)` - Editor actions (e.g., `"expandTools"`, `"selectConfirm"`)
1636
- - `appKeyHint(keybindings, action, description)` - App actions (requires `KeybindingsManager`)
1637
- - `editorKey(action)` - Get raw key string for editor action
1665
+ - `keyHint(keybinding, description)` - Formats a configured keybinding id such as `"app.tools.expand"` or `"tui.select.confirm"`
1666
+ - `keyText(keybinding)` - Returns the raw configured key text for a keybinding id
1638
1667
  - `rawKeyHint(key, description)` - Format a raw key string
1639
1668
 
1669
+ Use namespaced keybinding ids:
1670
+ - Coding-agent ids use the `app.*` namespace, for example `app.tools.expand`, `app.editor.external`, `app.session.rename`
1671
+ - Shared TUI ids use the `tui.*` namespace, for example `tui.select.confirm`, `tui.select.cancel`, `tui.input.tab`
1672
+
1673
+ For the exhaustive list of keybinding ids and defaults, see [keybindings.md](keybindings.md). `keybindings.json` uses those same namespaced ids.
1674
+
1675
+ Custom editors and `ctx.ui.custom()` components receive `keybindings: KeybindingsManager` as an injected argument. They should use that injected manager directly instead of calling `getKeybindings()` or `setKeybindings()`.
1676
+
1640
1677
  #### Best Practices
1641
1678
 
1642
1679
  - Use `Text` with padding `(0, 0)` - the Box handles padding
@@ -2,6 +2,10 @@
2
2
 
3
3
  All keyboard shortcuts can be customized via `~/.pi/agent/keybindings.json`. Each action can be bound to one or more keys.
4
4
 
5
+ The config file uses the same namespaced keybinding ids that pi uses internally and that extension authors use in `keyHint()` and injected `keybindings` managers.
6
+
7
+ Older configs using pre-namespaced ids such as `cursorUp` or `expandTools` are migrated automatically to the namespaced ids on startup.
8
+
5
9
  After editing `keybindings.json`, run `/reload` in pi to apply the changes without restarting the session.
6
10
 
7
11
  ## Key Format
@@ -18,127 +22,112 @@ Modifier combinations: `ctrl+shift+x`, `alt+ctrl+x`, `ctrl+shift+alt+x`, `ctrl+1
18
22
 
19
23
  ## All Actions
20
24
 
21
- ### Cursor Movement
25
+ ### TUI Editor Cursor Movement
22
26
 
23
- | Action | Default | Description |
27
+ | Keybinding id | Default | Description |
24
28
  |--------|---------|-------------|
25
- | `cursorUp` | `up` | Move cursor up |
26
- | `cursorDown` | `down` | Move cursor down |
27
- | `cursorLeft` | `left`, `ctrl+b` | Move cursor left |
28
- | `cursorRight` | `right`, `ctrl+f` | Move cursor right |
29
- | `cursorWordLeft` | `alt+left`, `ctrl+left`, `alt+b` | Move cursor word left |
30
- | `cursorWordRight` | `alt+right`, `ctrl+right`, `alt+f` | Move cursor word right |
31
- | `cursorLineStart` | `home`, `ctrl+a` | Move to line start |
32
- | `cursorLineEnd` | `end`, `ctrl+e` | Move to line end |
33
- | `jumpForward` | `ctrl+]` | Jump forward to character |
34
- | `jumpBackward` | `ctrl+alt+]` | Jump backward to character |
35
- | `pageUp` | `pageUp` | Scroll up by page |
36
- | `pageDown` | `pageDown` | Scroll down by page |
37
-
38
- ### Deletion
39
-
40
- | Action | Default | Description |
29
+ | `tui.editor.cursorUp` | `up` | Move cursor up |
30
+ | `tui.editor.cursorDown` | `down` | Move cursor down |
31
+ | `tui.editor.cursorLeft` | `left`, `ctrl+b` | Move cursor left |
32
+ | `tui.editor.cursorRight` | `right`, `ctrl+f` | Move cursor right |
33
+ | `tui.editor.cursorWordLeft` | `alt+left`, `ctrl+left`, `alt+b` | Move cursor word left |
34
+ | `tui.editor.cursorWordRight` | `alt+right`, `ctrl+right`, `alt+f` | Move cursor word right |
35
+ | `tui.editor.cursorLineStart` | `home`, `ctrl+a` | Move to line start |
36
+ | `tui.editor.cursorLineEnd` | `end`, `ctrl+e` | Move to line end |
37
+ | `tui.editor.jumpForward` | `ctrl+]` | Jump forward to character |
38
+ | `tui.editor.jumpBackward` | `ctrl+alt+]` | Jump backward to character |
39
+ | `tui.editor.pageUp` | `pageUp` | Scroll up by page |
40
+ | `tui.editor.pageDown` | `pageDown` | Scroll down by page |
41
+
42
+ ### TUI Editor Deletion
43
+
44
+ | Keybinding id | Default | Description |
41
45
  |--------|---------|-------------|
42
- | `deleteCharBackward` | `backspace` | Delete character backward |
43
- | `deleteCharForward` | `delete`, `ctrl+d` | Delete character forward |
44
- | `deleteWordBackward` | `ctrl+w`, `alt+backspace` | Delete word backward |
45
- | `deleteWordForward` | `alt+d`, `alt+delete` | Delete word forward |
46
- | `deleteToLineStart` | `ctrl+u` | Delete to line start |
47
- | `deleteToLineEnd` | `ctrl+k` | Delete to line end |
46
+ | `tui.editor.deleteCharBackward` | `backspace` | Delete character backward |
47
+ | `tui.editor.deleteCharForward` | `delete`, `ctrl+d` | Delete character forward |
48
+ | `tui.editor.deleteWordBackward` | `ctrl+w`, `alt+backspace` | Delete word backward |
49
+ | `tui.editor.deleteWordForward` | `alt+d`, `alt+delete` | Delete word forward |
50
+ | `tui.editor.deleteToLineStart` | `ctrl+u` | Delete to line start |
51
+ | `tui.editor.deleteToLineEnd` | `ctrl+k` | Delete to line end |
48
52
 
49
- ### Text Input
53
+ ### TUI Input
50
54
 
51
- | Action | Default | Description |
55
+ | Keybinding id | Default | Description |
52
56
  |--------|---------|-------------|
53
- | `newLine` | `shift+enter` | Insert new line |
54
- | `submit` | `enter` | Submit input |
55
- | `tab` | `tab` | Tab / autocomplete |
57
+ | `tui.input.newLine` | `shift+enter` | Insert new line |
58
+ | `tui.input.submit` | `enter` | Submit input |
59
+ | `tui.input.tab` | `tab` | Tab / autocomplete |
56
60
 
57
- ### Kill Ring
61
+ ### TUI Kill Ring
58
62
 
59
- | Action | Default | Description |
63
+ | Keybinding id | Default | Description |
60
64
  |--------|---------|-------------|
61
- | `yank` | `ctrl+y` | Paste most recently deleted text |
62
- | `yankPop` | `alt+y` | Cycle through deleted text after yank |
63
- | `undo` | `ctrl+-` | Undo last edit |
65
+ | `tui.editor.yank` | `ctrl+y` | Paste most recently deleted text |
66
+ | `tui.editor.yankPop` | `alt+y` | Cycle through deleted text after yank |
67
+ | `tui.editor.undo` | `ctrl+-` | Undo last edit |
64
68
 
65
- ### Clipboard
69
+ ### TUI Clipboard and Selection
66
70
 
67
- | Action | Default | Description |
71
+ | Keybinding id | Default | Description |
68
72
  |--------|---------|-------------|
69
- | `copy` | `ctrl+c` | Copy selection |
70
- | `pasteImage` | `ctrl+v` | Paste image from clipboard |
73
+ | `tui.input.copy` | `ctrl+c` | Copy selection |
74
+ | `tui.select.up` | `up` | Move selection up |
75
+ | `tui.select.down` | `down` | Move selection down |
76
+ | `tui.select.pageUp` | `pageUp` | Page up in list |
77
+ | `tui.select.pageDown` | `pageDown` | Page down in list |
78
+ | `tui.select.confirm` | `enter` | Confirm selection |
79
+ | `tui.select.cancel` | `escape`, `ctrl+c` | Cancel selection |
71
80
 
72
81
  ### Application
73
82
 
74
- | Action | Default | Description |
83
+ | Keybinding id | Default | Description |
75
84
  |--------|---------|-------------|
76
- | `interrupt` | `escape` | Cancel / abort |
77
- | `clear` | `ctrl+c` | Clear editor |
78
- | `exit` | `ctrl+d` | Exit (when editor empty) |
79
- | `suspend` | `ctrl+z` | Suspend to background |
80
- | `externalEditor` | `ctrl+g` | Open in external editor (`$VISUAL` or `$EDITOR`) |
85
+ | `app.interrupt` | `escape` | Cancel / abort |
86
+ | `app.clear` | `ctrl+c` | Clear editor |
87
+ | `app.exit` | `ctrl+d` | Exit (when editor empty) |
88
+ | `app.suspend` | `ctrl+z` | Suspend to background |
89
+ | `app.editor.external` | `ctrl+g` | Open in external editor (`$VISUAL` or `$EDITOR`) |
90
+ | `app.clipboard.pasteImage` | `ctrl+v` (`alt+v` on Windows) | Paste image from clipboard |
81
91
 
82
- ### Session
92
+ ### Sessions
83
93
 
84
- | Action | Default | Description |
94
+ | Keybinding id | Default | Description |
85
95
  |--------|---------|-------------|
86
- | `newSession` | *(none)* | Start a new session (`/new`) |
87
- | `tree` | *(none)* | Open session tree navigator (`/tree`) |
88
- | `fork` | *(none)* | Fork current session (`/fork`) |
89
- | `resume` | *(none)* | Open session resume picker (`/resume`) |
90
-
91
- ### Models & Thinking
92
-
93
- | Action | Default | Description |
94
- |--------|---------|-------------|
95
- | `selectModel` | `ctrl+l` | Open model selector |
96
- | `cycleModelForward` | `ctrl+p` | Cycle to next model |
97
- | `cycleModelBackward` | `shift+ctrl+p` | Cycle to previous model |
98
- | `cycleThinkingLevel` | `shift+tab` | Cycle thinking level |
99
-
100
- ### Display
101
-
102
- | Action | Default | Description |
96
+ | `app.session.new` | *(none)* | Start a new session (`/new`) |
97
+ | `app.session.tree` | *(none)* | Open session tree navigator (`/tree`) |
98
+ | `app.session.fork` | *(none)* | Fork current session (`/fork`) |
99
+ | `app.session.resume` | *(none)* | Open session resume picker (`/resume`) |
100
+ | `app.session.togglePath` | `ctrl+p` | Toggle path display |
101
+ | `app.session.toggleSort` | `ctrl+s` | Toggle sort mode |
102
+ | `app.session.toggleNamedFilter` | `ctrl+n` | Toggle named-only filter |
103
+ | `app.session.rename` | `ctrl+r` | Rename session |
104
+ | `app.session.delete` | `ctrl+d` | Delete session |
105
+ | `app.session.deleteNoninvasive` | `ctrl+backspace` | Delete session when query is empty |
106
+
107
+ ### Models and Thinking
108
+
109
+ | Keybinding id | Default | Description |
103
110
  |--------|---------|-------------|
104
- | `expandTools` | `ctrl+o` | Collapse/expand tool output |
105
- | `toggleThinking` | `ctrl+t` | Collapse/expand thinking blocks |
111
+ | `app.model.select` | `ctrl+l` | Open model selector |
112
+ | `app.model.cycleForward` | `ctrl+p` | Cycle to next model |
113
+ | `app.model.cycleBackward` | `shift+ctrl+p` | Cycle to previous model |
114
+ | `app.thinking.cycle` | `shift+tab` | Cycle thinking level |
115
+ | `app.thinking.toggle` | `ctrl+t` | Collapse or expand thinking blocks |
106
116
 
107
- ### Message Queue
117
+ ### Display and Message Queue
108
118
 
109
- | Action | Default | Description |
119
+ | Keybinding id | Default | Description |
110
120
  |--------|---------|-------------|
111
- | `followUp` | `alt+enter` | Queue follow-up message |
112
- | `dequeue` | `alt+up` | Restore queued messages to editor |
113
-
114
- ### Selection (Lists, Pickers)
115
-
116
- | Action | Default | Description |
117
- |--------|---------|-------------|
118
- | `selectUp` | `up` | Move selection up |
119
- | `selectDown` | `down` | Move selection down |
120
- | `selectPageUp` | `pageUp` | Page up in list |
121
- | `selectPageDown` | `pageDown` | Page down in list |
122
- | `selectConfirm` | `enter` | Confirm selection |
123
- | `selectCancel` | `escape`, `ctrl+c` | Cancel selection |
121
+ | `app.tools.expand` | `ctrl+o` | Collapse or expand tool output |
122
+ | `app.message.followUp` | `alt+enter` | Queue follow-up message |
123
+ | `app.message.dequeue` | `alt+up` | Restore queued messages to editor |
124
124
 
125
125
  ### Tree Navigation
126
126
 
127
- | Action | Default | Description |
128
- |--------|---------|-------------|
129
- | `treeFoldOrUp` | `ctrl+left`, `alt+left` | Fold current branch segment, or jump to the previous segment start |
130
- | `treeUnfoldOrDown` | `ctrl+right`, `alt+right` | Unfold current branch segment, or jump to the next segment start or branch end |
131
-
132
- ### Session Picker
133
-
134
- | Action | Default | Description |
127
+ | Keybinding id | Default | Description |
135
128
  |--------|---------|-------------|
136
- | `toggleSessionPath` | `ctrl+p` | Toggle path display |
137
- | `toggleSessionSort` | `ctrl+s` | Toggle sort mode |
138
- | `toggleSessionNamedFilter` | `ctrl+n` | Toggle named-only filter |
139
- | `renameSession` | `ctrl+r` | Rename session |
140
- | `deleteSession` | `ctrl+d` | Delete session |
141
- | `deleteSessionNoninvasive` | `ctrl+backspace` | Delete session (when query empty) |
129
+ | `app.tree.foldOrUp` | `ctrl+left`, `alt+left` | Fold current branch segment, or jump to the previous segment start |
130
+ | `app.tree.unfoldOrDown` | `ctrl+right`, `alt+right` | Unfold current branch segment, or jump to the next segment start or branch end |
142
131
 
143
132
  ## Custom Configuration
144
133
 
@@ -146,9 +135,9 @@ Create `~/.pi/agent/keybindings.json`:
146
135
 
147
136
  ```json
148
137
  {
149
- "cursorUp": ["up", "ctrl+p"],
150
- "cursorDown": ["down", "ctrl+n"],
151
- "deleteWordBackward": ["ctrl+w", "alt+backspace"]
138
+ "tui.editor.cursorUp": ["up", "ctrl+p"],
139
+ "tui.editor.cursorDown": ["down", "ctrl+n"],
140
+ "tui.editor.deleteWordBackward": ["ctrl+w", "alt+backspace"]
152
141
  }
153
142
  ```
154
143
 
@@ -158,15 +147,15 @@ Each action can have a single key or an array of keys. User config overrides def
158
147
 
159
148
  ```json
160
149
  {
161
- "cursorUp": ["up", "ctrl+p"],
162
- "cursorDown": ["down", "ctrl+n"],
163
- "cursorLeft": ["left", "ctrl+b"],
164
- "cursorRight": ["right", "ctrl+f"],
165
- "cursorWordLeft": ["alt+left", "alt+b"],
166
- "cursorWordRight": ["alt+right", "alt+f"],
167
- "deleteCharForward": ["delete", "ctrl+d"],
168
- "deleteCharBackward": ["backspace", "ctrl+h"],
169
- "newLine": ["shift+enter", "ctrl+j"]
150
+ "tui.editor.cursorUp": ["up", "ctrl+p"],
151
+ "tui.editor.cursorDown": ["down", "ctrl+n"],
152
+ "tui.editor.cursorLeft": ["left", "ctrl+b"],
153
+ "tui.editor.cursorRight": ["right", "ctrl+f"],
154
+ "tui.editor.cursorWordLeft": ["alt+left", "alt+b"],
155
+ "tui.editor.cursorWordRight": ["alt+right", "alt+f"],
156
+ "tui.editor.deleteCharForward": ["delete", "ctrl+d"],
157
+ "tui.editor.deleteCharBackward": ["backspace", "ctrl+h"],
158
+ "tui.input.newLine": ["shift+enter", "ctrl+j"]
170
159
  }
171
160
  ```
172
161
 
@@ -174,11 +163,11 @@ Each action can have a single key or an array of keys. User config overrides def
174
163
 
175
164
  ```json
176
165
  {
177
- "cursorUp": ["up", "alt+k"],
178
- "cursorDown": ["down", "alt+j"],
179
- "cursorLeft": ["left", "alt+h"],
180
- "cursorRight": ["right", "alt+l"],
181
- "cursorWordLeft": ["alt+left", "alt+b"],
182
- "cursorWordRight": ["alt+right", "alt+w"]
166
+ "tui.editor.cursorUp": ["up", "alt+k"],
167
+ "tui.editor.cursorDown": ["down", "alt+j"],
168
+ "tui.editor.cursorLeft": ["left", "alt+h"],
169
+ "tui.editor.cursorRight": ["right", "alt+l"],
170
+ "tui.editor.cursorWordLeft": ["alt+left", "alt+b"],
171
+ "tui.editor.cursorWordRight": ["alt+right", "alt+w"]
183
172
  }
184
173
  ```
@@ -30,7 +30,7 @@ import { existsSync, readFileSync } from "node:fs";
30
30
  import { mkdir, writeFile } from "node:fs/promises";
31
31
  import { join } from "node:path";
32
32
  import { StringEnum } from "@mariozechner/pi-ai";
33
- import { type ExtensionAPI, getAgentDir } from "@mariozechner/pi-coding-agent";
33
+ import { type ExtensionAPI, getAgentDir, withFileMutationQueue } from "@mariozechner/pi-coding-agent";
34
34
  import { type Static, Type } from "@sinclair/typebox";
35
35
 
36
36
  const PROVIDER = "google-antigravity";
@@ -228,12 +228,14 @@ function imageExtension(mimeType: string): string {
228
228
  }
229
229
 
230
230
  async function saveImage(base64Data: string, mimeType: string, outputDir: string): Promise<string> {
231
- await mkdir(outputDir, { recursive: true });
232
231
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
233
232
  const ext = imageExtension(mimeType);
234
233
  const filename = `image-${timestamp}-${randomUUID().slice(0, 8)}.${ext}`;
235
234
  const filePath = join(outputDir, filename);
236
- await writeFile(filePath, Buffer.from(base64Data, "base64"));
235
+ await withFileMutationQueue(filePath, async () => {
236
+ await mkdir(outputDir, { recursive: true });
237
+ await writeFile(filePath, Buffer.from(base64Data, "base64"));
238
+ });
237
239
  return filePath;
238
240
  }
239
241
 
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider",
3
- "version": "1.11.0",
3
+ "version": "1.12.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-custom-provider",
9
- "version": "1.11.0",
9
+ "version": "1.12.1",
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.11.0",
4
+ "version": "1.12.1",
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.11.0",
4
+ "version": "1.12.1",
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.10.0",
4
+ "version": "1.11.1",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -19,7 +19,7 @@ import * as path from "node:path";
19
19
  import type { AgentToolResult } from "@mariozechner/pi-agent-core";
20
20
  import type { Message } from "@mariozechner/pi-ai";
21
21
  import { StringEnum } from "@mariozechner/pi-ai";
22
- import { type ExtensionAPI, getMarkdownTheme } from "@mariozechner/pi-coding-agent";
22
+ import { type ExtensionAPI, getMarkdownTheme, withFileMutationQueue } from "@mariozechner/pi-coding-agent";
23
23
  import { Container, Markdown, Spacer, Text } from "@mariozechner/pi-tui";
24
24
  import { Type } from "@sinclair/typebox";
25
25
  import { type AgentConfig, type AgentScope, discoverAgents } from "./agents.js";
@@ -207,11 +207,13 @@ async function mapWithConcurrencyLimit<TIn, TOut>(
207
207
  return results;
208
208
  }
209
209
 
210
- function writePromptToTempFile(agentName: string, prompt: string): { dir: string; filePath: string } {
211
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-"));
210
+ async function writePromptToTempFile(agentName: string, prompt: string): Promise<{ dir: string; filePath: string }> {
211
+ const tmpDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "pi-subagent-"));
212
212
  const safeName = agentName.replace(/[^\w.-]+/g, "_");
213
213
  const filePath = path.join(tmpDir, `prompt-${safeName}.md`);
214
- fs.writeFileSync(filePath, prompt, { encoding: "utf-8", mode: 0o600 });
214
+ await withFileMutationQueue(filePath, async () => {
215
+ await fs.promises.writeFile(filePath, prompt, { encoding: "utf-8", mode: 0o600 });
216
+ });
215
217
  return { dir: tmpDir, filePath };
216
218
  }
217
219
 
@@ -274,7 +276,7 @@ async function runSingleAgent(
274
276
 
275
277
  try {
276
278
  if (agent.systemPrompt.trim()) {
277
- const tmp = writePromptToTempFile(agent.name, agent.systemPrompt);
279
+ const tmp = await writePromptToTempFile(agent.name, agent.systemPrompt);
278
280
  tmpPromptDir = tmp.dir;
279
281
  tmpPromptPath = tmp.filePath;
280
282
  args.push("--append-system-prompt", tmpPromptPath);
@@ -21,10 +21,10 @@
21
21
  */
22
22
 
23
23
  import type { TextContent } from "@mariozechner/pi-ai";
24
- import { type ExtensionAPI, getAgentDir } from "@mariozechner/pi-coding-agent";
24
+ import { type ExtensionAPI, getAgentDir, withFileMutationQueue } from "@mariozechner/pi-coding-agent";
25
25
  import { Type } from "@sinclair/typebox";
26
- import { appendFileSync, constants, readFileSync } from "fs";
27
- import { access, readFile } from "fs/promises";
26
+ import { constants, readFileSync } from "fs";
27
+ import { access, appendFile, readFile } from "fs/promises";
28
28
  import { join, resolve } from "path";
29
29
 
30
30
  const LOG_FILE = join(getAgentDir(), "read-access.log");
@@ -44,14 +44,16 @@ function isBlockedPath(path: string): boolean {
44
44
  return BLOCKED_PATTERNS.some((pattern) => pattern.test(path));
45
45
  }
46
46
 
47
- function logAccess(path: string, allowed: boolean, reason?: string) {
47
+ async function logAccess(path: string, allowed: boolean, reason?: string) {
48
48
  const timestamp = new Date().toISOString();
49
49
  const status = allowed ? "ALLOWED" : "BLOCKED";
50
50
  const msg = reason ? ` (${reason})` : "";
51
51
  const line = `[${timestamp}] ${status}: ${path}${msg}\n`;
52
52
 
53
53
  try {
54
- appendFileSync(LOG_FILE, line);
54
+ await withFileMutationQueue(LOG_FILE, async () => {
55
+ await appendFile(LOG_FILE, line);
56
+ });
55
57
  } catch {
56
58
  // Ignore logging errors
57
59
  }
@@ -77,7 +79,7 @@ export default function (pi: ExtensionAPI) {
77
79
 
78
80
  // Check if path is blocked
79
81
  if (isBlockedPath(absolutePath)) {
80
- logAccess(absolutePath, false, "matches blocked pattern");
82
+ await logAccess(absolutePath, false, "matches blocked pattern");
81
83
  return {
82
84
  content: [
83
85
  {
@@ -90,7 +92,7 @@ export default function (pi: ExtensionAPI) {
90
92
  }
91
93
 
92
94
  // Log allowed access
93
- logAccess(absolutePath, true);
95
+ await logAccess(absolutePath, true);
94
96
 
95
97
  // Perform the actual read (simplified implementation)
96
98
  try {
@@ -14,6 +14,7 @@
14
14
  * built-in `grep` tool in src/core/tools/grep.ts for a more complete implementation.
15
15
  */
16
16
 
17
+ import { mkdtemp, writeFile } from "node:fs/promises";
17
18
  import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
18
19
  import {
19
20
  DEFAULT_MAX_BYTES,
@@ -21,11 +22,11 @@ import {
21
22
  formatSize,
22
23
  type TruncationResult,
23
24
  truncateHead,
25
+ withFileMutationQueue,
24
26
  } from "@mariozechner/pi-coding-agent";
25
27
  import { Text } from "@mariozechner/pi-tui";
26
28
  import { Type } from "@sinclair/typebox";
27
29
  import { execSync } from "child_process";
28
- import { mkdtempSync, writeFileSync } from "fs";
29
30
  import { tmpdir } from "os";
30
31
  import { join } from "path";
31
32
 
@@ -108,9 +109,11 @@ export default function (pi: ExtensionAPI) {
108
109
 
109
110
  if (truncation.truncated) {
110
111
  // Save full output to a temp file so LLM can access it if needed
111
- const tempDir = mkdtempSync(join(tmpdir(), "pi-rg-"));
112
+ const tempDir = await mkdtemp(join(tmpdir(), "pi-rg-"));
112
113
  const tempFile = join(tempDir, "output.txt");
113
- writeFileSync(tempFile, output);
114
+ await withFileMutationQueue(tempFile, async () => {
115
+ await writeFile(tempFile, output, "utf8");
116
+ });
114
117
 
115
118
  details.truncation = truncation;
116
119
  details.fullOutputPath = tempFile;
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pi-extension-with-deps",
3
- "version": "1.24.0",
3
+ "version": "1.25.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-with-deps",
9
- "version": "1.24.0",
9
+ "version": "1.25.1",
10
10
  "dependencies": {
11
11
  "ms": "^2.1.3"
12
12
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-with-deps",
3
3
  "private": true,
4
- "version": "1.24.0",
4
+ "version": "1.25.1",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperspaceng/neural-coding-agent",
3
- "version": "0.60.0",
3
+ "version": "0.61.2",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "piConfig": {
@@ -40,9 +40,9 @@
40
40
  },
41
41
  "dependencies": {
42
42
  "@mariozechner/jiti": "^2.6.2",
43
- "@hyperspaceng/neural-agent-core": "^0.60.0",
44
- "@hyperspaceng/neural-ai": "^0.60.0",
45
- "@hyperspaceng/neural-tui": "^0.60.0",
43
+ "@mariozechner/pi-agent-core": "^0.61.1",
44
+ "@mariozechner/pi-ai": "^0.61.1",
45
+ "@mariozechner/pi-tui": "^0.61.1",
46
46
  "@silvia-odwyer/photon-node": "^0.3.4",
47
47
  "chalk": "^5.5.0",
48
48
  "cli-highlight": "^2.1.11",
@@ -66,7 +66,7 @@
66
66
  }
67
67
  },
68
68
  "optionalDependencies": {
69
- "@hyperspaceng/neural-clipboard": "^0.3.2"
69
+ "@mariozechner/clipboard": "^0.3.2"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@types/diff": "^7.0.2",
@@ -90,7 +90,7 @@
90
90
  "license": "MIT",
91
91
  "repository": {
92
92
  "type": "git",
93
- "url": "git+https://github.com/DrOlu/pi-mono.git",
93
+ "url": "git+https://github.com/badlogic/pi-mono.git",
94
94
  "directory": "packages/coding-agent"
95
95
  },
96
96
  "engines": {