@mariozechner/pi-coding-agent 0.31.1 → 0.32.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.
- package/CHANGELOG.md +44 -0
- package/README.md +56 -5
- package/dist/cli/file-processor.d.ts +5 -1
- package/dist/cli/file-processor.d.ts.map +1 -1
- package/dist/cli/file-processor.js +28 -8
- package/dist/cli/file-processor.js.map +1 -1
- package/dist/core/agent-session.d.ts +41 -16
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +90 -41
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts +6 -1
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +16 -1
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/custom-tools/types.d.ts +1 -1
- package/dist/core/custom-tools/types.d.ts.map +1 -1
- package/dist/core/custom-tools/types.js.map +1 -1
- package/dist/core/hooks/loader.d.ts +4 -1
- package/dist/core/hooks/loader.d.ts.map +1 -1
- package/dist/core/hooks/loader.js +2 -2
- package/dist/core/hooks/loader.js.map +1 -1
- package/dist/core/hooks/runner.d.ts +2 -2
- package/dist/core/hooks/runner.d.ts.map +1 -1
- package/dist/core/hooks/runner.js +3 -3
- package/dist/core/hooks/runner.js.map +1 -1
- package/dist/core/hooks/types.d.ts +10 -4
- package/dist/core/hooks/types.d.ts.map +1 -1
- package/dist/core/hooks/types.js.map +1 -1
- package/dist/core/model-registry.d.ts +5 -2
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +85 -49
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +1 -0
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +9 -6
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/settings-manager.d.ts +17 -3
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +41 -6
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/tools/index.d.ts +9 -4
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +6 -6
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/read.d.ts +5 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +22 -5
- package/dist/core/tools/read.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +5 -5
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/custom-editor.d.ts +1 -0
- package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-editor.js +7 -1
- package/dist/modes/interactive/components/custom-editor.js.map +1 -1
- package/dist/modes/interactive/components/hook-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/hook-editor.js +3 -3
- package/dist/modes/interactive/components/hook-editor.js.map +1 -1
- package/dist/modes/interactive/components/hook-input.d.ts.map +1 -1
- package/dist/modes/interactive/components/hook-input.js +3 -3
- package/dist/modes/interactive/components/hook-input.js.map +1 -1
- package/dist/modes/interactive/components/hook-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/hook-selector.js +3 -3
- package/dist/modes/interactive/components/hook-selector.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +3 -3
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.js +3 -3
- package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +8 -2
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +37 -6
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +66 -19
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +3 -3
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +12 -4
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +18 -6
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +21 -12
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +25 -6
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/utils/image-resize.d.ts +29 -0
- package/dist/utils/image-resize.d.ts.map +1 -0
- package/dist/utils/image-resize.js +111 -0
- package/dist/utils/image-resize.js.map +1 -0
- package/docs/hooks.md +16 -9
- package/examples/README.md +6 -0
- package/examples/custom-tools/README.md +2 -0
- package/examples/hooks/README.md +1 -0
- package/examples/hooks/file-trigger.ts +1 -1
- package/examples/hooks/todo/index.ts +134 -0
- package/package.json +6 -5
- package/dist/modes/interactive/components/queue-mode-selector.d.ts +0 -10
- package/dist/modes/interactive/components/queue-mode-selector.d.ts.map +0 -1
- package/dist/modes/interactive/components/queue-mode-selector.js +0 -42
- package/dist/modes/interactive/components/queue-mode-selector.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.32.0] - 2026-01-03
|
|
4
|
+
|
|
5
|
+
### Breaking Changes
|
|
6
|
+
|
|
7
|
+
- **Queue API replaced with steer/followUp**: The `queueMessage()` method has been split into two methods with different delivery semantics ([#403](https://github.com/badlogic/pi-mono/issues/403)):
|
|
8
|
+
- `steer(text)`: Interrupts the agent mid-run (Enter while streaming). Delivered after current tool execution.
|
|
9
|
+
- `followUp(text)`: Waits until the agent finishes (Alt+Enter while streaming). Delivered only when agent stops.
|
|
10
|
+
- **Settings renamed**: `queueMode` setting renamed to `steeringMode`. Added new `followUpMode` setting. Old settings.json files are migrated automatically.
|
|
11
|
+
- **AgentSession methods renamed**:
|
|
12
|
+
- `queueMessage()` → `steer()` and `followUp()`
|
|
13
|
+
- `queueMode` getter → `steeringMode` and `followUpMode` getters
|
|
14
|
+
- `setQueueMode()` → `setSteeringMode()` and `setFollowUpMode()`
|
|
15
|
+
- `queuedMessageCount` → `pendingMessageCount`
|
|
16
|
+
- `getQueuedMessages()` → `getSteeringMessages()` and `getFollowUpMessages()`
|
|
17
|
+
- `clearQueue()` now returns `{ steering: string[], followUp: string[] }`
|
|
18
|
+
- `hasQueuedMessages()` → `hasPendingMessages()`
|
|
19
|
+
- **Hook API signature changed**: `pi.sendMessage()` second parameter changed from `triggerTurn?: boolean` to `options?: { triggerTurn?, deliverAs? }`. Use `deliverAs: "followUp"` for follow-up delivery. Affects both hooks and internal `sendHookMessage()` method.
|
|
20
|
+
- **RPC API changes**:
|
|
21
|
+
- `queue_message` command → `steer` and `follow_up` commands
|
|
22
|
+
- `set_queue_mode` command → `set_steering_mode` and `set_follow_up_mode` commands
|
|
23
|
+
- `RpcSessionState.queueMode` → `steeringMode` and `followUpMode`
|
|
24
|
+
- **Settings UI**: "Queue mode" setting split into "Steering mode" and "Follow-up mode"
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- Configurable double-escape action: choose whether double-escape with empty editor opens `/tree` (default) or `/branch`. Configure via `/settings` or `doubleEscapeAction` in settings.json ([#404](https://github.com/badlogic/pi-mono/issues/404))
|
|
29
|
+
- Vertex AI provider (`google-vertex`): access Gemini models via Google Cloud Vertex AI using Application Default Credentials ([#300](https://github.com/badlogic/pi-mono/pull/300) by [@default-anton](https://github.com/default-anton))
|
|
30
|
+
- Built-in provider overrides in `models.json`: override just `baseUrl` to route a built-in provider through a proxy while keeping all its models, or define `models` to fully replace the provider ([#406](https://github.com/badlogic/pi-mono/pull/406) by [@yevhen](https://github.com/yevhen))
|
|
31
|
+
- Automatic image resizing: images larger than 2000x2000 are resized for better model compatibility. Original dimensions are injected into the prompt. Controlled via `/settings` or `images.autoResize` in settings.json. ([#402](https://github.com/badlogic/pi-mono/pull/402) by [@mitsuhiko](https://github.com/mitsuhiko))
|
|
32
|
+
- Alt+Enter keybind to queue follow-up messages while agent is streaming
|
|
33
|
+
- `Theme` and `ThemeColor` types now exported for hooks using `ctx.ui.custom()`
|
|
34
|
+
- Terminal window title now displays "pi - dirname" to identify which project session you're in ([#407](https://github.com/badlogic/pi-mono/pull/407) by [@kaofelix](https://github.com/kaofelix))
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
|
|
38
|
+
- Editor component now uses word wrapping instead of character-level wrapping for better readability ([#382](https://github.com/badlogic/pi-mono/pull/382) by [@nickseelert](https://github.com/nickseelert))
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
|
|
42
|
+
- `/model` selector now opens instantly instead of waiting for OAuth token refresh. Token refresh is deferred until a model is actually used.
|
|
43
|
+
- Shift+Space, Shift+Backspace, and Shift+Delete now work correctly in Kitty-protocol terminals (Kitty, WezTerm, etc.) instead of being silently ignored ([#411](https://github.com/badlogic/pi-mono/pull/411) by [@nathyong](https://github.com/nathyong))
|
|
44
|
+
- `AgentSession.prompt()` now throws if called while the agent is already streaming, preventing race conditions. Use `steer()` or `followUp()` to queue messages during streaming.
|
|
45
|
+
- Ctrl+C now works like Escape in selector components, so mashing Ctrl+C will eventually close the program ([#400](https://github.com/badlogic/pi-mono/pull/400) by [@mitsuhiko](https://github.com/mitsuhiko))
|
|
46
|
+
|
|
3
47
|
## [0.31.1] - 2026-01-02
|
|
4
48
|
|
|
5
49
|
### Fixed
|
package/README.md
CHANGED
|
@@ -188,7 +188,7 @@ The agent reads, writes, and edits files, and executes commands via bash.
|
|
|
188
188
|
|
|
189
189
|
| Command | Description |
|
|
190
190
|
|---------|-------------|
|
|
191
|
-
| `/settings` | Open settings menu (thinking, theme,
|
|
191
|
+
| `/settings` | Open settings menu (thinking, theme, message delivery modes, toggles) |
|
|
192
192
|
| `/model` | Switch models mid-session (fuzzy search, arrow keys, Enter to select) |
|
|
193
193
|
| `/export [file]` | Export session to self-contained HTML |
|
|
194
194
|
| `/share` | Upload session as secret GitHub gist, get shareable URL (requires `gh` CLI) |
|
|
@@ -214,7 +214,11 @@ The agent reads, writes, and edits files, and executes commands via bash.
|
|
|
214
214
|
|
|
215
215
|
**Multi-line paste:** Pasted content is collapsed to `[paste #N <lines> lines]` but sent in full.
|
|
216
216
|
|
|
217
|
-
**Message queuing:** Submit messages while the agent is working
|
|
217
|
+
**Message queuing:** Submit messages while the agent is working:
|
|
218
|
+
- **Enter** queues a *steering* message, delivered after current tool execution (interrupts remaining tools)
|
|
219
|
+
- **Alt+Enter** queues a *follow-up* message, delivered only after the agent finishes all work
|
|
220
|
+
|
|
221
|
+
Both modes are configurable via `/settings`: "one-at-a-time" delivers messages one by one waiting for responses, "all" delivers all queued messages at once. Press Escape to abort and restore queued messages to editor.
|
|
218
222
|
|
|
219
223
|
### Keyboard Shortcuts
|
|
220
224
|
|
|
@@ -286,6 +290,8 @@ You: What's in this screenshot? /path/to/image.png
|
|
|
286
290
|
|
|
287
291
|
Supported formats: `.jpg`, `.jpeg`, `.png`, `.gif`, `.webp`
|
|
288
292
|
|
|
293
|
+
**Auto-resize:** Images larger than 2000x2000 pixels are automatically resized to fit within this limit for better compatibility with Anthropic models. The original dimensions are noted in the context so the model can map coordinates back if needed. Disable via `images.autoResize: false` in settings.
|
|
294
|
+
|
|
289
295
|
**Inline rendering:** On terminals that support the Kitty graphics protocol (Kitty, Ghostty, WezTerm) or iTerm2 inline images, images in tool output are rendered inline. On unsupported terminals, a text placeholder is shown instead.
|
|
290
296
|
|
|
291
297
|
Toggle inline images via `/settings` or set `terminal.showImages: false` in settings.
|
|
@@ -459,6 +465,37 @@ Add custom models (Ollama, vLLM, LM Studio, etc.) via `~/.pi/agent/models.json`:
|
|
|
459
465
|
}
|
|
460
466
|
```
|
|
461
467
|
|
|
468
|
+
**Overriding built-in providers:**
|
|
469
|
+
|
|
470
|
+
To route a built-in provider (anthropic, openai, google, etc.) through a proxy without redefining all models, just specify the `baseUrl`:
|
|
471
|
+
|
|
472
|
+
```json
|
|
473
|
+
{
|
|
474
|
+
"providers": {
|
|
475
|
+
"anthropic": {
|
|
476
|
+
"baseUrl": "https://my-proxy.example.com/v1"
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
All built-in Anthropic models remain available with the new endpoint. Existing OAuth or API key auth continues to work.
|
|
483
|
+
|
|
484
|
+
To fully replace a built-in provider with custom models, include the `models` array:
|
|
485
|
+
|
|
486
|
+
```json
|
|
487
|
+
{
|
|
488
|
+
"providers": {
|
|
489
|
+
"anthropic": {
|
|
490
|
+
"baseUrl": "https://my-proxy.example.com/v1",
|
|
491
|
+
"apiKey": "ANTHROPIC_API_KEY",
|
|
492
|
+
"api": "anthropic-messages",
|
|
493
|
+
"models": [...]
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
```
|
|
498
|
+
|
|
462
499
|
**Authorization header:** Set `authHeader: true` to add `Authorization: Bearer <apiKey>` automatically.
|
|
463
500
|
|
|
464
501
|
**OpenAI compatibility (`compat` field):**
|
|
@@ -499,7 +536,8 @@ Global `~/.pi/agent/settings.json` stores persistent preferences:
|
|
|
499
536
|
"defaultModel": "claude-sonnet-4-20250514",
|
|
500
537
|
"defaultThinkingLevel": "medium",
|
|
501
538
|
"enabledModels": ["anthropic/*", "*gpt*", "gemini-2.5-pro:high"],
|
|
502
|
-
"
|
|
539
|
+
"steeringMode": "one-at-a-time",
|
|
540
|
+
"followUpMode": "one-at-a-time",
|
|
503
541
|
"shellPath": "C:\\path\\to\\bash.exe",
|
|
504
542
|
"hideThinkingBlock": false,
|
|
505
543
|
"collapseChangelog": false,
|
|
@@ -519,6 +557,9 @@ Global `~/.pi/agent/settings.json` stores persistent preferences:
|
|
|
519
557
|
"terminal": {
|
|
520
558
|
"showImages": true
|
|
521
559
|
},
|
|
560
|
+
"images": {
|
|
561
|
+
"autoResize": true
|
|
562
|
+
},
|
|
522
563
|
"hooks": ["/path/to/hook.ts"],
|
|
523
564
|
"customTools": ["/path/to/tool.ts"]
|
|
524
565
|
}
|
|
@@ -531,7 +572,8 @@ Global `~/.pi/agent/settings.json` stores persistent preferences:
|
|
|
531
572
|
| `defaultModel` | Default model ID | - |
|
|
532
573
|
| `defaultThinkingLevel` | Thinking level: `off`, `minimal`, `low`, `medium`, `high`, `xhigh` | - |
|
|
533
574
|
| `enabledModels` | Model patterns for cycling. Supports glob patterns (`github-copilot/*`, `*sonnet*`) and fuzzy matching. Same as `--models` CLI flag | - |
|
|
534
|
-
| `
|
|
575
|
+
| `steeringMode` | Steering message delivery: `all` or `one-at-a-time` | `one-at-a-time` |
|
|
576
|
+
| `followUpMode` | Follow-up message delivery: `all` or `one-at-a-time` | `one-at-a-time` |
|
|
535
577
|
| `shellPath` | Custom bash path (Windows) | auto-detected |
|
|
536
578
|
| `hideThinkingBlock` | Hide thinking blocks in output (Ctrl+T to toggle) | `false` |
|
|
537
579
|
| `collapseChangelog` | Show condensed changelog after update | `false` |
|
|
@@ -543,6 +585,9 @@ Global `~/.pi/agent/settings.json` stores persistent preferences:
|
|
|
543
585
|
| `retry.maxRetries` | Maximum retry attempts | `3` |
|
|
544
586
|
| `retry.baseDelayMs` | Base delay for exponential backoff | `2000` |
|
|
545
587
|
| `terminal.showImages` | Render images inline (supported terminals) | `true` |
|
|
588
|
+
| `images.autoResize` | Auto-resize images to 2000x2000 max for better model compatibility | `true` |
|
|
589
|
+
|
|
590
|
+
| `doubleEscapeAction` | Action for double-escape with empty editor: `tree` or `branch` | `tree` |
|
|
546
591
|
| `hooks` | Additional hook file paths | `[]` |
|
|
547
592
|
| `customTools` | Additional custom tool file paths | `[]` |
|
|
548
593
|
|
|
@@ -689,7 +734,13 @@ export default function (pi: HookAPI) {
|
|
|
689
734
|
|
|
690
735
|
**Sending messages from hooks:**
|
|
691
736
|
|
|
692
|
-
Use `pi.sendMessage(message,
|
|
737
|
+
Use `pi.sendMessage(message, options?)` to inject messages into the session. Messages are persisted as `CustomMessageEntry` and sent to the LLM.
|
|
738
|
+
|
|
739
|
+
Options:
|
|
740
|
+
- `triggerTurn`: If true and agent is idle, starts a new agent turn. Default: false.
|
|
741
|
+
- `deliverAs`: When agent is streaming, controls delivery timing:
|
|
742
|
+
- `"steer"` (default): Delivered after current tool execution, interrupts remaining tools.
|
|
743
|
+
- `"followUp"`: Delivered only after agent finishes all work.
|
|
693
744
|
|
|
694
745
|
```typescript
|
|
695
746
|
import * as fs from "node:fs";
|
|
@@ -6,6 +6,10 @@ export interface ProcessedFiles {
|
|
|
6
6
|
text: string;
|
|
7
7
|
images: ImageContent[];
|
|
8
8
|
}
|
|
9
|
+
export interface ProcessFileOptions {
|
|
10
|
+
/** Whether to auto-resize images to 2000x2000 max. Default: true */
|
|
11
|
+
autoResizeImages?: boolean;
|
|
12
|
+
}
|
|
9
13
|
/** Process @file arguments into text content and image attachments */
|
|
10
|
-
export declare function processFileArguments(fileArgs: string[]): Promise<ProcessedFiles>;
|
|
14
|
+
export declare function processFileArguments(fileArgs: string[], options?: ProcessFileOptions): Promise<ProcessedFiles>;
|
|
11
15
|
//# sourceMappingURL=file-processor.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-processor.d.ts","sourceRoot":"","sources":["../../src/cli/file-processor.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"file-processor.d.ts","sourceRoot":"","sources":["../../src/cli/file-processor.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAOxD,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,YAAY,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,kBAAkB;IAClC,oEAAoE;IACpE,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,sEAAsE;AACtE,wBAAsB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,cAAc,CAAC,CAwEpH","sourcesContent":["/**\n * Process @file CLI arguments into text content and image attachments\n */\n\nimport { access, readFile, stat } from \"node:fs/promises\";\nimport type { ImageContent } from \"@mariozechner/pi-ai\";\nimport chalk from \"chalk\";\nimport { resolve } from \"path\";\nimport { resolveReadPath } from \"../core/tools/path-utils.js\";\nimport { formatDimensionNote, resizeImage } from \"../utils/image-resize.js\";\nimport { detectSupportedImageMimeTypeFromFile } from \"../utils/mime.js\";\n\nexport interface ProcessedFiles {\n\ttext: string;\n\timages: ImageContent[];\n}\n\nexport interface ProcessFileOptions {\n\t/** Whether to auto-resize images to 2000x2000 max. Default: true */\n\tautoResizeImages?: boolean;\n}\n\n/** Process @file arguments into text content and image attachments */\nexport async function processFileArguments(fileArgs: string[], options?: ProcessFileOptions): Promise<ProcessedFiles> {\n\tconst autoResizeImages = options?.autoResizeImages ?? true;\n\tlet text = \"\";\n\tconst images: ImageContent[] = [];\n\n\tfor (const fileArg of fileArgs) {\n\t\t// Expand and resolve path (handles ~ expansion and macOS screenshot Unicode spaces)\n\t\tconst absolutePath = resolve(resolveReadPath(fileArg, process.cwd()));\n\n\t\t// Check if file exists\n\t\ttry {\n\t\t\tawait access(absolutePath);\n\t\t} catch {\n\t\t\tconsole.error(chalk.red(`Error: File not found: ${absolutePath}`));\n\t\t\tprocess.exit(1);\n\t\t}\n\n\t\t// Check if file is empty\n\t\tconst stats = await stat(absolutePath);\n\t\tif (stats.size === 0) {\n\t\t\t// Skip empty files\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst mimeType = await detectSupportedImageMimeTypeFromFile(absolutePath);\n\n\t\tif (mimeType) {\n\t\t\t// Handle image file\n\t\t\tconst content = await readFile(absolutePath);\n\t\t\tconst base64Content = content.toString(\"base64\");\n\n\t\t\tlet attachment: ImageContent;\n\t\t\tlet dimensionNote: string | undefined;\n\n\t\t\tif (autoResizeImages) {\n\t\t\t\tconst resized = await resizeImage({ type: \"image\", data: base64Content, mimeType });\n\t\t\t\tdimensionNote = formatDimensionNote(resized);\n\t\t\t\tattachment = {\n\t\t\t\t\ttype: \"image\",\n\t\t\t\t\tmimeType: resized.mimeType,\n\t\t\t\t\tdata: resized.data,\n\t\t\t\t};\n\t\t\t} else {\n\t\t\t\tattachment = {\n\t\t\t\t\ttype: \"image\",\n\t\t\t\t\tmimeType,\n\t\t\t\t\tdata: base64Content,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\timages.push(attachment);\n\n\t\t\t// Add text reference to image with optional dimension note\n\t\t\tif (dimensionNote) {\n\t\t\t\ttext += `<file name=\"${absolutePath}\">${dimensionNote}</file>\\n`;\n\t\t\t} else {\n\t\t\t\ttext += `<file name=\"${absolutePath}\"></file>\\n`;\n\t\t\t}\n\t\t} else {\n\t\t\t// Handle text file\n\t\t\ttry {\n\t\t\t\tconst content = await readFile(absolutePath, \"utf-8\");\n\t\t\t\ttext += `<file name=\"${absolutePath}\">\\n${content}\\n</file>\\n`;\n\t\t\t} catch (error: unknown) {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tconsole.error(chalk.red(`Error: Could not read file ${absolutePath}: ${message}`));\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { text, images };\n}\n"]}
|
|
@@ -5,9 +5,11 @@ import { access, readFile, stat } from "node:fs/promises";
|
|
|
5
5
|
import chalk from "chalk";
|
|
6
6
|
import { resolve } from "path";
|
|
7
7
|
import { resolveReadPath } from "../core/tools/path-utils.js";
|
|
8
|
+
import { formatDimensionNote, resizeImage } from "../utils/image-resize.js";
|
|
8
9
|
import { detectSupportedImageMimeTypeFromFile } from "../utils/mime.js";
|
|
9
10
|
/** Process @file arguments into text content and image attachments */
|
|
10
|
-
export async function processFileArguments(fileArgs) {
|
|
11
|
+
export async function processFileArguments(fileArgs, options) {
|
|
12
|
+
const autoResizeImages = options?.autoResizeImages ?? true;
|
|
11
13
|
let text = "";
|
|
12
14
|
const images = [];
|
|
13
15
|
for (const fileArg of fileArgs) {
|
|
@@ -32,14 +34,32 @@ export async function processFileArguments(fileArgs) {
|
|
|
32
34
|
// Handle image file
|
|
33
35
|
const content = await readFile(absolutePath);
|
|
34
36
|
const base64Content = content.toString("base64");
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
data: base64Content,
|
|
39
|
-
|
|
37
|
+
let attachment;
|
|
38
|
+
let dimensionNote;
|
|
39
|
+
if (autoResizeImages) {
|
|
40
|
+
const resized = await resizeImage({ type: "image", data: base64Content, mimeType });
|
|
41
|
+
dimensionNote = formatDimensionNote(resized);
|
|
42
|
+
attachment = {
|
|
43
|
+
type: "image",
|
|
44
|
+
mimeType: resized.mimeType,
|
|
45
|
+
data: resized.data,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
attachment = {
|
|
50
|
+
type: "image",
|
|
51
|
+
mimeType,
|
|
52
|
+
data: base64Content,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
40
55
|
images.push(attachment);
|
|
41
|
-
// Add text reference to image
|
|
42
|
-
|
|
56
|
+
// Add text reference to image with optional dimension note
|
|
57
|
+
if (dimensionNote) {
|
|
58
|
+
text += `<file name="${absolutePath}">${dimensionNote}</file>\n`;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
text += `<file name="${absolutePath}"></file>\n`;
|
|
62
|
+
}
|
|
43
63
|
}
|
|
44
64
|
else {
|
|
45
65
|
// Handle text file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-processor.js","sourceRoot":"","sources":["../../src/cli/file-processor.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAE1D,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAC/B,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAE,oCAAoC,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"file-processor.js","sourceRoot":"","sources":["../../src/cli/file-processor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Process @file CLI arguments into text content and image attachments\n */\n\nimport { access, readFile, stat } from \"node:fs/promises\";\nimport type { ImageContent } from \"@mariozechner/pi-ai\";\nimport chalk from \"chalk\";\nimport { resolve } from \"path\";\nimport { resolveReadPath } from \"../core/tools/path-utils.js\";\nimport { formatDimensionNote, resizeImage } from \"../utils/image-resize.js\";\nimport { detectSupportedImageMimeTypeFromFile } from \"../utils/mime.js\";\n\nexport interface ProcessedFiles {\n\ttext: string;\n\timages: ImageContent[];\n}\n\nexport interface ProcessFileOptions {\n\t/** Whether to auto-resize images to 2000x2000 max. Default: true */\n\tautoResizeImages?: boolean;\n}\n\n/** Process @file arguments into text content and image attachments */\nexport async function processFileArguments(fileArgs: string[], options?: ProcessFileOptions): Promise<ProcessedFiles> {\n\tconst autoResizeImages = options?.autoResizeImages ?? true;\n\tlet text = \"\";\n\tconst images: ImageContent[] = [];\n\n\tfor (const fileArg of fileArgs) {\n\t\t// Expand and resolve path (handles ~ expansion and macOS screenshot Unicode spaces)\n\t\tconst absolutePath = resolve(resolveReadPath(fileArg, process.cwd()));\n\n\t\t// Check if file exists\n\t\ttry {\n\t\t\tawait access(absolutePath);\n\t\t} catch {\n\t\t\tconsole.error(chalk.red(`Error: File not found: ${absolutePath}`));\n\t\t\tprocess.exit(1);\n\t\t}\n\n\t\t// Check if file is empty\n\t\tconst stats = await stat(absolutePath);\n\t\tif (stats.size === 0) {\n\t\t\t// Skip empty files\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst mimeType = await detectSupportedImageMimeTypeFromFile(absolutePath);\n\n\t\tif (mimeType) {\n\t\t\t// Handle image file\n\t\t\tconst content = await readFile(absolutePath);\n\t\t\tconst base64Content = content.toString(\"base64\");\n\n\t\t\tlet attachment: ImageContent;\n\t\t\tlet dimensionNote: string | undefined;\n\n\t\t\tif (autoResizeImages) {\n\t\t\t\tconst resized = await resizeImage({ type: \"image\", data: base64Content, mimeType });\n\t\t\t\tdimensionNote = formatDimensionNote(resized);\n\t\t\t\tattachment = {\n\t\t\t\t\ttype: \"image\",\n\t\t\t\t\tmimeType: resized.mimeType,\n\t\t\t\t\tdata: resized.data,\n\t\t\t\t};\n\t\t\t} else {\n\t\t\t\tattachment = {\n\t\t\t\t\ttype: \"image\",\n\t\t\t\t\tmimeType,\n\t\t\t\t\tdata: base64Content,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\timages.push(attachment);\n\n\t\t\t// Add text reference to image with optional dimension note\n\t\t\tif (dimensionNote) {\n\t\t\t\ttext += `<file name=\"${absolutePath}\">${dimensionNote}</file>\\n`;\n\t\t\t} else {\n\t\t\t\ttext += `<file name=\"${absolutePath}\"></file>\\n`;\n\t\t\t}\n\t\t} else {\n\t\t\t// Handle text file\n\t\t\ttry {\n\t\t\t\tconst content = await readFile(absolutePath, \"utf-8\");\n\t\t\t\ttext += `<file name=\"${absolutePath}\">\\n${content}\\n</file>\\n`;\n\t\t\t} catch (error: unknown) {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tconsole.error(chalk.red(`Error: Could not read file ${absolutePath}: ${message}`));\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { text, images };\n}\n"]}
|
|
@@ -105,7 +105,10 @@ export declare class AgentSession {
|
|
|
105
105
|
private _fileCommands;
|
|
106
106
|
private _unsubscribeAgent?;
|
|
107
107
|
private _eventListeners;
|
|
108
|
-
|
|
108
|
+
/** Tracks pending steering messages for UI display. Removed when delivered. */
|
|
109
|
+
private _steeringMessages;
|
|
110
|
+
/** Tracks pending follow-up messages for UI display. Removed when delivered. */
|
|
111
|
+
private _followUpMessages;
|
|
109
112
|
private _compactionAbortController;
|
|
110
113
|
private _autoCompactionAbortController;
|
|
111
114
|
private _branchSummaryAbortController;
|
|
@@ -169,8 +172,10 @@ export declare class AgentSession {
|
|
|
169
172
|
get isCompacting(): boolean;
|
|
170
173
|
/** All messages including custom types like BashExecutionMessage */
|
|
171
174
|
get messages(): AgentMessage[];
|
|
172
|
-
/** Current
|
|
173
|
-
get
|
|
175
|
+
/** Current steering mode */
|
|
176
|
+
get steeringMode(): "all" | "one-at-a-time";
|
|
177
|
+
/** Current follow-up mode */
|
|
178
|
+
get followUpMode(): "all" | "one-at-a-time";
|
|
174
179
|
/** Current session file path, or undefined if sessions are disabled */
|
|
175
180
|
get sessionFile(): string | undefined;
|
|
176
181
|
/** Current session ID */
|
|
@@ -192,10 +197,15 @@ export declare class AgentSession {
|
|
|
192
197
|
prompt(text: string, options?: PromptOptions): Promise<void>;
|
|
193
198
|
private _tryExecuteHookCommand;
|
|
194
199
|
/**
|
|
195
|
-
* Queue a message to
|
|
196
|
-
*
|
|
200
|
+
* Queue a steering message to interrupt the agent mid-run.
|
|
201
|
+
* Delivered after current tool execution, skips remaining tools.
|
|
197
202
|
*/
|
|
198
|
-
|
|
203
|
+
steer(text: string): Promise<void>;
|
|
204
|
+
/**
|
|
205
|
+
* Queue a follow-up message to be processed after the agent finishes.
|
|
206
|
+
* Delivered only when agent has no more tool calls or steering messages.
|
|
207
|
+
*/
|
|
208
|
+
followUp(text: string): Promise<void>;
|
|
199
209
|
/**
|
|
200
210
|
* Send a hook message to the session. Creates a CustomMessageEntry.
|
|
201
211
|
*
|
|
@@ -205,18 +215,28 @@ export declare class AgentSession {
|
|
|
205
215
|
* - Not streaming + no trigger: appends to state/session, no turn
|
|
206
216
|
*
|
|
207
217
|
* @param message Hook message with customType, content, display, details
|
|
208
|
-
* @param triggerTurn If true and not streaming, triggers a new LLM turn
|
|
218
|
+
* @param options.triggerTurn If true and not streaming, triggers a new LLM turn
|
|
219
|
+
* @param options.deliverAs When streaming, use "steer" (default) for immediate or "followUp" to wait
|
|
209
220
|
*/
|
|
210
|
-
sendHookMessage<T = unknown>(message: Pick<HookMessage<T>, "customType" | "content" | "display" | "details">,
|
|
221
|
+
sendHookMessage<T = unknown>(message: Pick<HookMessage<T>, "customType" | "content" | "display" | "details">, options?: {
|
|
222
|
+
triggerTurn?: boolean;
|
|
223
|
+
deliverAs?: "steer" | "followUp";
|
|
224
|
+
}): Promise<void>;
|
|
211
225
|
/**
|
|
212
|
-
* Clear queued messages and return them.
|
|
226
|
+
* Clear all queued messages and return them.
|
|
213
227
|
* Useful for restoring to editor when user aborts.
|
|
228
|
+
* @returns Object with steering and followUp arrays
|
|
214
229
|
*/
|
|
215
|
-
clearQueue():
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
230
|
+
clearQueue(): {
|
|
231
|
+
steering: string[];
|
|
232
|
+
followUp: string[];
|
|
233
|
+
};
|
|
234
|
+
/** Number of pending messages (includes both steering and follow-up) */
|
|
235
|
+
get pendingMessageCount(): number;
|
|
236
|
+
/** Get pending steering messages (read-only) */
|
|
237
|
+
getSteeringMessages(): readonly string[];
|
|
238
|
+
/** Get pending follow-up messages (read-only) */
|
|
239
|
+
getFollowUpMessages(): readonly string[];
|
|
220
240
|
get skillsSettings(): Required<SkillsSettings> | undefined;
|
|
221
241
|
/**
|
|
222
242
|
* Abort current operation and wait for agent to become idle.
|
|
@@ -273,10 +293,15 @@ export declare class AgentSession {
|
|
|
273
293
|
*/
|
|
274
294
|
supportsThinking(): boolean;
|
|
275
295
|
/**
|
|
276
|
-
* Set message
|
|
296
|
+
* Set steering message mode.
|
|
297
|
+
* Saves to settings.
|
|
298
|
+
*/
|
|
299
|
+
setSteeringMode(mode: "all" | "one-at-a-time"): void;
|
|
300
|
+
/**
|
|
301
|
+
* Set follow-up message mode.
|
|
277
302
|
* Saves to settings.
|
|
278
303
|
*/
|
|
279
|
-
|
|
304
|
+
setFollowUpMode(mode: "all" | "one-at-a-time"): void;
|
|
280
305
|
/**
|
|
281
306
|
* Manually compact the session context.
|
|
282
307
|
* Aborts current agent operation first.
|