@narumitw/pi-webui 0.21.0 → 0.22.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/README.md CHANGED
@@ -13,7 +13,7 @@ This package is intentionally different from the broader, separately maintained
13
13
  - Preserves open tool/thinking disclosures during keyed streaming updates and offers **Jump to latest** when new activity arrives while you read earlier messages.
14
14
  - Sends immediately while Pi is idle and automatically queues **Queue next** as a follow-up while Pi is busy.
15
15
  - Provides a separate **Steer** action while Pi is working; steering is never the default submit action.
16
- - Accepts pasted, dropped, or selected PNG, JPEG, WebP, and GIF images, strips metadata server-side, applies Pi-compatible size limits, and provides contained thumbnails plus an enlarged preview.
16
+ - Accepts pasted, dropped, or selected PNG, JPEG, WebP, GIF, BMP, TIFF, HEIC/HEIF, and AVIF images, strips metadata server-side, applies Pi-compatible size limits, and provides ordered thumbnails plus an enlarged preview.
17
17
  - Reconnects from an ordered event cursor and replaces state from an authoritative snapshot after a gap.
18
18
  - Keeps a failed browser draft and prevents rapid duplicate submission with request IDs.
19
19
  - Uses no frontend framework, build step, browser storage, remote service, or automatically launched browser.
@@ -68,15 +68,33 @@ The normal default path is `~/.pi/agent/pi-webui.json`. Pi installations that us
68
68
 
69
69
  ```json
70
70
  {
71
- "startOnSessionStart": false
71
+ "startOnSessionStart": false,
72
+ "retainSentImages": false,
73
+ "maxRetainedImages": 32,
74
+ "maxRetainedBytes": 134217728,
75
+ "maxImages": 8,
76
+ "maxImageBytes": 10485760,
77
+ "maxBatchBytes": 41943040,
78
+ "maxImagePixels": 50000000
72
79
  }
73
80
  ```
74
81
 
75
82
  | Setting | Default | Behavior |
76
83
  | --- | --- | --- |
77
84
  | `startOnSessionStart` | `false` | Start WebUI and display a fresh one-time link after every Pi session initialization, including startup, reload, new, resume, and fork. It never opens a browser. |
85
+ | `retainSentImages` | `false` | Opt in to bounded, session-only retention of sanitized images after Pi accepts their browser message. |
86
+ | `maxRetainedImages` | `32` | FIFO retained-image count ceiling. Positive integers up to the hard ceiling of 128 are accepted. |
87
+ | `maxRetainedBytes` | `134217728` (128 MiB) | FIFO retained provider-ready byte ceiling. Positive integers up to the hard ceiling of 536870912 (512 MiB) are accepted. |
88
+ | `maxImages` | `8` | Images in one active draft; hard ceiling 32. |
89
+ | `maxImageBytes` | `10485760` (10 MiB) | Source bytes per image; hard ceiling 52428800 (50 MiB). Must not exceed `maxBatchBytes`. |
90
+ | `maxBatchBytes` | `41943040` (40 MiB) | Combined source/processed draft bytes; hard ceiling 209715200 (200 MiB). |
91
+ | `maxImagePixels` | `50000000` | Decoded pixels per image; hard ceiling 100000000. Animated-image frame area participates in this limit. |
78
92
 
79
- A missing file uses defaults. The file must contain a top-level JSON object, and `startOnSessionStart` must be a boolean when present. Malformed JSON or an invalid recognized value causes the file to be ignored with a warning and leaves it untouched. Unknown fields are accepted and preserved by the settings screen for forward compatibility.
93
+ A missing file uses defaults. The file must contain a top-level JSON object; recognized booleans and positive integer limits must have the documented types and remain within their hard ceilings. Malformed JSON or an invalid recognized value causes the file to be ignored with a warning and leaves it untouched. Unknown fields are accepted and preserved by the settings screen for forward compatibility. The settings screen exposes only the frequent startup toggle; there is not yet user-testing evidence that `maxImages` is adjusted often enough to justify another routine row. Retention and image-limit fields remain in Advanced JSON at the reported path.
94
+
95
+ ### Advanced image limits
96
+
97
+ Omitting all four image-limit fields exactly reproduces the original 8 image / 10 MiB per image / 40 MiB batch / 50 megapixel behavior. Values are byte counts, not Base64 character counts. Raising any image limit above its safe default emits one concise session-start warning and can materially increase Pi-process memory, decoder work, and denial-of-service exposure; use the smallest value that solves the current workflow. Any non-integer, non-positive, above-ceiling, or cross-field-invalid recognized value rejects the whole file and safely restores all defaults without rewriting it. `/webui status` reports the effective limits and whether they came from defaults or the settings file.
80
98
 
81
99
  Settings are reloaded on every `session_start`. Changes made in `/webui settings` are saved atomically and update the in-memory preference immediately, but they intentionally do not start or stop the server in the current session; they take effect at the next session initialization or `/reload`. `/webui init` creates formatted defaults once and refuses to overwrite valid or invalid existing content.
82
100
 
@@ -86,7 +104,7 @@ In print, JSON, and RPC modes, `/webui settings` does not open custom TUI or wri
86
104
 
87
105
  WebUI mirrors Pi's semantic session events, not terminal pixels. It displays conversation content, streaming assistant state, tool calls/results, errors, and activity using browser-native presentation. It does not reproduce ANSI colors, terminal wrapping, footer/widgets, built-in dialogs, arbitrary custom TUI components, or unsent terminal editor text.
88
106
 
89
- The initial transcript comes from the active session branch. Browser refresh does not create a second transcript or alter Pi's session tree.
107
+ The initial transcript comes from the active session branch. Unsent browser message text and ordered attachment references are authoritative in the live Pi process, so refresh, reconnect, and active-tab takeover restore the same draft without creating a second transcript or altering Pi's session tree. Text edits are revisioned and saved with bounded, deduplicated mutations; stale or delayed responses cannot overwrite newer typing.
90
108
 
91
109
  ## Images
92
110
 
@@ -95,11 +113,21 @@ The initial transcript comes from the active session branch. Browser refresh doe
95
113
  | PNG | PNG |
96
114
  | JPEG | JPEG |
97
115
  | WebP | WebP |
98
- | GIF | GIF |
116
+ | GIF, including animation | GIF |
117
+ | BMP | PNG |
118
+ | TIFF | PNG |
119
+ | HEIC/HEIF | PNG |
120
+ | AVIF | PNG |
121
+
122
+ The server checks file signatures instead of trusting browser MIME types or filename extensions. It rejects corrupt/unknown formats and applies the effective `maxImages`, `maxImageBytes`, `maxBatchBytes`, and `maxImagePixels` settings at browser admission, streamed upload, processing, draft accounting, send preflight, and Attach again. Pi's approximately 4.5 MB inline Base64 constraint and 2,000-pixel provider-ready dimension constraint remain fixed and cannot be raised in WebUI settings. Images over 2,000 pixels on either side are resized when Pi's `images.autoResize` setting is enabled and rejected when it is disabled.
123
+
124
+ Choosing, pasting, or dropping images first reserves an ordered server-side batch, uploads each source as a bounded raw request, and processes it before Send becomes available. Each thumbnail reports Uploading, Processing, Ready, or Needs attention; upload progress is shown when the browser reports a byte total. A failed item can be retried without reselecting successful siblings, and every item can be removed independently. Refreshing the active tab restores the authoritative staged batch, while another tab taking the editing lease cancels in-flight work safely.
125
+
126
+ When a draft has two or more images, each card shows its order and can be dragged into place; keyboard users can focus a card and press `Alt+↑` or `Alt+↓`. A single image omits meaningless ordering interaction. Remove images individually with each card's trash button, or use the confirmed **Clear attachments** action when a draft contains several images. The collection summary shows the attached count against the effective maximum and states the metadata-removal guarantee once, while each card keeps its own upload/processing/ready/error state and any conversion or resize summary. Processing applies image orientation, re-encodes provider-ready output, strips EXIF and other private metadata, preserves ICC color profiles and animated GIF timing where supported, and releases each source after successful sanitation. Failed processing retains only that bounded source for Retry; Ready provider bytes remain in the Pi process until accepted send, removal, clear, lease/session teardown, or failed-send retry. Image bytes are never embedded in the message JSON protocol.
99
127
 
100
- The server checks file signatures instead of trusting browser MIME types. It rejects corrupt/unknown formats, more than 8 images, individual source images over 10 MiB, combined image input over 40 MiB, images over 50 megapixels, and provider-ready Base64 content over Pi's approximately 4.5 MB inline limit. Images over 2,000 pixels on either side are resized when Pi's `images.autoResize` setting is enabled and rejected when it is disabled.
128
+ Pi's effective global and trusted-project `images.autoResize` and `images.blockImages` settings plus the current model's image capability and authentication are checked again at send time. A failed send leaves the exact Ready batch available for an idempotent retry. BMP and HEIC use bounded portable decoders because the prebuilt `sharp`/libvips distribution does not decode those inputs consistently across supported platforms.
101
129
 
102
- Processing re-encodes images with `sharp`, strips EXIF and other metadata, and does not retain browser source bytes after the message request is accepted. Pi's effective global and trusted-project `images.autoResize` and `images.blockImages` settings plus the current model's image capability are checked at send time.
130
+ When `retainSentImages` is enabled, only provider-ready sanitized bytes transfer into a separate session-memory store after Pi accepts the matching browser message. Content-identical sanitized images share one opaque session reference. Oldest entries are evicted first when either retention ceiling is exceeded. WebUI also reconciles retained, current-draft, and conservative in-flight processing bytes against one aggregate resident-image budget (the larger of the configured retention byte ceiling and the staging store's maximum working set), evicting sent entries before that aggregate can grow. Eligible transcript image chips offer **Attach again** and **Forget**; evicted or forgotten references read **Expired**, and terminal-origin images never gain those actions. Attach again clones the retained bytes into the current authoritative draft, reuses normal count/byte admission, and never mutates the earlier message. Refresh and active-tab takeover recover eligibility from Pi-process state; session replacement, reload, shutdown, or process exit releases all retained bytes.
103
131
 
104
132
  ## Security and privacy
105
133
 
@@ -107,7 +135,7 @@ Processing re-encodes images with `sharp`, strips EXIF and other metadata, and d
107
135
  - A rotating bootstrap token is exchanged once for a per-server HttpOnly, `SameSite=Strict` cookie and removed from the URL.
108
136
  - Every endpoint requires the cookie. Mutations also require exact Host and Origin values plus the active browser-tab lease.
109
137
  - Responses use no-store, no-referrer, MIME-sniffing, frame-denial, same-origin resource, and restrictive Content Security Policy headers.
110
- - Transcript projection retains at most the newest 500 messages and 500 tool records, event replay keeps 256 updates, and request-id records keep 128 sends. The page uses no localStorage, sessionStorage, IndexedDB, cookies of its own, or image/transcript cache.
138
+ - Transcript projection retains at most the newest 500 messages and 500 tool records, event replay keeps 256 updates, and request-id records keep 128 sends. Unsent message text, ordered attachment references, staged image bytes, and opt-in sanitized sent-image bytes live only in bounded Pi-process memory under their documented lifecycles; the page uses no localStorage, sessionStorage, IndexedDB, script-readable cookies, or image/transcript cache.
111
139
  - Tool arguments/results, paths, images, and model thinking can be sensitive. Thinking is collapsed by default; only open a link issued by a Pi process you trust.
112
140
  - Reload, session replacement/fork, or Pi shutdown closes sockets, invalidates old callbacks, ends the page, and releases in-memory state.
113
141
 
@@ -115,13 +143,13 @@ A loopback page is local to the operating-system network namespace. WebUI does n
115
143
 
116
144
  ## Accessibility and browsers
117
145
 
118
- The page uses semantic headings, native disclosure/dialog controls, concise status/alert live regions, text labels, visible keyboard focus, at least 44 px controls, keyboard image preview/removal, `Ctrl/Command+Enter` submission, reduced-motion handling, dark mode, and responsive reflow. It targets current stable desktop Chrome, Edge, Firefox, and Safari.
146
+ The page uses semantic headings, native disclosure/dialog controls, concise status/alert live regions, accessible labels, visible keyboard focus, at least 44 px controls, keyboard image preview/removal/reordering, `Ctrl/Command+Enter` submission, reduced-motion handling, dark mode, and responsive reflow. It targets current stable desktop Chrome, Edge, Firefox, and Safari.
119
147
 
120
148
  ## Limitations
121
149
 
122
150
  - One active Pi session and one active browser editing tab only.
123
- - No persistent browser transcript, sent-image history, remote access, PTY/terminal control, model/settings controls, or session switching.
124
- - No SVG, HEIC/HEIF, TIFF, BMP, AVIF, remote image URL, OCR, annotation, or directory upload support.
151
+ - No persistent browser transcript, permanent sent-image gallery, cross-session image history, remote access, PTY/terminal control, model/settings controls, or session switching.
152
+ - No SVG, remote image URL, OCR, annotation, or directory upload support.
125
153
  - Browser acknowledgment means Pi accepted or queued the message. Provider failures follow Pi's normal session/retry behavior.
126
154
  - Built-in TUI commands and dialogs are not reimplemented in the browser.
127
155
 
@@ -132,7 +160,11 @@ src/webui.ts Pi extension entrypoint
132
160
  src/runtime.ts Pi lifecycle, commands, event projection, and browser message routing
133
161
  src/settings.ts global WebUI settings validation and atomic persistence
134
162
  src/conversation.ts bounded transcript snapshot and ordered event replay
135
- src/server.ts authenticated loopback HTTP/SSE server
163
+ src/drafts.ts authoritative in-memory text and attachment-reference revisions
164
+ src/attachments.ts revisioned staged-image state, processing queue, and byte ownership
165
+ src/sent-images.ts opt-in bounded sanitized sent-image retention
166
+ src/server.ts authenticated loopback HTTP/SSE server and raw attachment protocol
167
+ src/image-limits.ts shared configurable defaults, ceilings, and provider constraints
136
168
  src/images.ts bounded provider-ready image processing
137
169
  src/pi-settings.ts effective Pi image settings reader
138
170
  src/web/ framework-free browser page
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@narumitw/pi-webui",
3
- "version": "0.21.0",
3
+ "version": "0.22.0",
4
4
  "description": "Lightweight local web companion for the current Pi terminal session.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -29,6 +29,8 @@
29
29
  "typecheck": "tsc --noEmit"
30
30
  },
31
31
  "dependencies": {
32
+ "bmp-js": "^0.1.0",
33
+ "heic-decode": "^2.1.0",
32
34
  "sharp": "^0.35.3"
33
35
  },
34
36
  "peerDependencies": {