@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 +41 -1
- package/docs/extensions.md +42 -5
- package/docs/keybindings.md +101 -112
- package/examples/extensions/antigravity-image-gen.ts +5 -3
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/custom-provider-qwen-cli/package.json +1 -1
- package/examples/extensions/subagent/index.ts +7 -5
- package/examples/extensions/tool-override.ts +9 -7
- package/examples/extensions/truncated-tool.ts +6 -3
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +6 -6
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
|
|
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
|
|
package/docs/extensions.md
CHANGED
|
@@ -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
|
|
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("
|
|
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(
|
|
1636
|
-
- `
|
|
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
|
package/docs/keybindings.md
CHANGED
|
@@ -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
|
-
|
|
|
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
|
-
|
|
|
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
|
-
###
|
|
53
|
+
### TUI Input
|
|
50
54
|
|
|
51
|
-
|
|
|
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
|
-
|
|
|
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
|
-
|
|
|
71
|
+
| Keybinding id | Default | Description |
|
|
68
72
|
|--------|---------|-------------|
|
|
69
|
-
| `copy` | `ctrl+c` | Copy selection |
|
|
70
|
-
| `
|
|
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
|
-
|
|
|
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
|
-
| `
|
|
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
|
-
###
|
|
92
|
+
### Sessions
|
|
83
93
|
|
|
84
|
-
|
|
|
94
|
+
| Keybinding id | Default | Description |
|
|
85
95
|
|--------|---------|-------------|
|
|
86
|
-
| `
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
|
94
|
-
|
|
95
|
-
| `
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
| `
|
|
105
|
-
| `
|
|
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
|
-
|
|
|
119
|
+
| Keybinding id | Default | Description |
|
|
110
120
|
|--------|---------|-------------|
|
|
111
|
-
| `
|
|
112
|
-
| `
|
|
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
|
-
|
|
|
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
|
-
| `
|
|
137
|
-
| `
|
|
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
|
|
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.
|
|
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.
|
|
9
|
+
"version": "1.12.1",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@anthropic-ai/sdk": "^0.52.0"
|
|
12
12
|
}
|
|
@@ -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.
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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 =
|
|
112
|
+
const tempDir = await mkdtemp(join(tmpdir(), "pi-rg-"));
|
|
112
113
|
const tempFile = join(tempDir, "output.txt");
|
|
113
|
-
|
|
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.
|
|
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.
|
|
9
|
+
"version": "1.25.1",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"ms": "^2.1.3"
|
|
12
12
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperspaceng/neural-coding-agent",
|
|
3
|
-
"version": "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
|
-
"@
|
|
44
|
-
"@
|
|
45
|
-
"@
|
|
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
|
-
"@
|
|
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/
|
|
93
|
+
"url": "git+https://github.com/badlogic/pi-mono.git",
|
|
94
94
|
"directory": "packages/coding-agent"
|
|
95
95
|
},
|
|
96
96
|
"engines": {
|