Raises the minimum supported Pi version from 0.78 to 0.80. Required
for the upcoming removal of pi-web's dependency on pi-ai's deprecated
compat entrypoint (@earendil-works/pi-ai/compat).
Coalesce session status/activity updates into one render per animation
frame instead of one per token, ignore prompt-editor status changes that
do not affect what it displays, and stop per-keystroke draft state from
re-rendering the surrounding template. This prevents streaming-driven
re-renders from interrupting in-progress touch gestures such as the iOS
long-press paste/edit callout.
Split subsession inspection into two tools: check_subsession gives a quick
glance (status + latest assistant output), while read_subsession reads through
a child's transcript with role/content filters, full-content substring search
(including tool-call args), optional per-value maxChars truncation that flags
clipped parts, includeToolArgs, and pagination.
Filtering and search run on full untruncated content; truncation is an explicit,
caller-owned final projection (no default) so a narrow read never silently hides
a match. Empty page-windows are distinguished from zero-match results.
Add the Relay method skill (execute a long plan as a chain of independent
sessions handing off via spawn_session) under a top-level skills/ folder that
follows the open agent-skills convention, installable with the skills CLI.
Also clarify the changeset-changelog skill: no changeset is needed for changes
outside the npm release allowlist (package.json files), such as repo-only
agent skills under .agents/ and skills/.
Add spawn_subsession / list_subsessions / read_subsession tools that let an
agent start child sessions it stays attached to: the child records its parent
in the session tree, the parent is notified (as a system-authored custom
message that wakes an idle parent and queues behind in-flight work) when the
child stops working, and the parent can inspect children's status and result.
Gated behind a beta flag, off by default, mirroring spawnSessions: enable via
PI_WEB_SUBSESSIONS, the subsessions config key, or the Settings toggle. Also
requires spawnSessions.
Also fix the release skill so the version step resyncs package-lock.json
(npm install --package-lock-only) and the commit step refuses a release where
package.json and package-lock.json versions disagree.
Attachments can be pasted, dropped, or uploaded, so 'paste' was too
narrow. Rename the default folder and filename prefix accordingly and
update the changeset reference.
The stream catch-up mode was represented by two fields that could drift:
the private catchupStreamSessionId guard (which suppresses live transcript
events while the in-flight message prefix is missing) and the public
isReceivingPartialStream flag (which drives the badge). The socket reconnect
path (refreshSelectedSession) set the flag from status without updating the
guard, so a later idle status.update was skipped by the guard check and the
badge never cleared.
Route every mutation of the mode through a single setStreamCatchup helper so
the guard and badge can't disagree, and clear the mode on any non-streaming
status for the selected session.
A slash command sent as the first (or any idle) message showed the raw
command text twice until reload: once from the client's optimistic insert
and once from the server's message.append echo, neither of which converges
with the agent's canonical expanded message (e.g. a /skill:* block).
Make commands obey the same source-of-truth contract as prompts:
- client no longer inserts the raw command text optimistically; it shows
the existing per-session sending indicator instead
- forwarded runtime/skill commands return a bare done result rather than a
synthetic "Accepted ..." line
- server suppresses the raw message.append echo for command-forwarded
prompts (threaded through the compaction queue too)
Result: pre-reload state matches reload, with no transient duplicate.
The session.created global event (added with spawn_session) could arrive
in the initiating tab before the start request's HTTP response resolved.
applyCreatedSession inserted a plain session row (archive/reload actions)
and then startSession unconditionally prepended the cached version (delete
action), leaving two badges with the same id.
Make the optimistic insert idempotent by filtering out any existing entry
with the same id before prepending the cached session, so the locally
cached session always wins regardless of event ordering.
Drop the task-count badge so the Tasks tab no longer draws attention
during normal use. Keep the '!' badge for when the tasks config exists
but cannot be loaded, which is the only state that warrants attention.
Add a project-scoped spawn_session tool so agents can dispatch new,
independent sessions (ralph loops, long-plan chaining). Spawned sessions
are constrained to a workspace/worktree of the same registered project,
appear in the session list immediately via a new session.created event,
and the capability is on by default with a Settings -> Session daemon
toggle (spawnSessions / PI_WEB_SPAWN_SESSIONS).
Note: adds a session daemon code path, so pi-web-sessiond.service must be
restarted manually for the server side to take effect.
Builds on marcus's plugin-api-completeness work. Narrows the new plugin
surface to capabilities that expose real, otherwise-unreachable pi-web
functionality, and drops invented/duplicative surfaces:
Kept:
- files.writeFile / deleteFile / moveFile (genuine workspace mutation,
federated, path-safe)
- prompt.insertText / getText / getSelection (editor state access)
Dropped:
- attachments.* (insertFileReference/getAttachedFiles/removeFileReference):
getAttachedFiles invented a structured-attachment notion pi-web does not
have and duplicated prompt.getText() + a regex with a false email-safety
claim; insert/removeFileReference were thin sugar over readFile +
insertText that plugins can compose themselves.
- prompt.onPaste / onKeyDown: an incomplete two-event hook system shaped
around a single use case, overlapping the editor's native image-paste
handling. Deferred until a real editor event/hook surface is designed.
- prompt.focus: redundant and buggier duplicate of the existing
focusPrompt() (silently no-ops when not on the chat view). Focus stays
as focusPrompt().
Security fix:
- deleteWorkspaceFile now resolves the parent via realpath + ensureInside
before lstat/unlink, closing a symlinked-parent-directory escape that
allowed deleting files outside the workspace (write/move already did
this). Final path component is still not resolved, so deleting a symlink
removes the link, not its target. Adds a regression test.
Docs and the registry test mock updated to match the trimmed surface.
Drop pi-web-plugin.test.ts, which relied on a walk() hack to assert
rendered template content and click wiring. Real decision logic is
already covered by updatesLogic.test.ts; the remaining runCommand glue
is trivial inline code.
Move the Updates plugin's pure decision logic (recommended/additional
commands, panel visibility, installation labels, version formatting)
into a sibling updatesLogic.ts module so it can be unit tested directly,
matching the workspace-tasks multi-file plugin layout. The plugin file
is now thin rendering glue that imports those helpers.
Add unit tests for the extracted logic and render smoke tests that
exercise the panel through its public contribution API, including that
Run actions wire to the terminal with the "pi.plugin": "updates"
metadata. No behavior change; the beta label stays.
Add a "Reload Session" core action so reload is keyboard-accessible and can be
assigned a custom shortcut, gated by the same guards as the menu item (writable
session, sessions.reload capability, not currently busy). Disable the Reload
menu entry while the session has active work, mirroring the server guard and the
archived-delete control, so users get a clear reason instead of an error toast.
Co-authored-by: Claude <[email protected]>
Build on the original Reload action with the fixes raised in review:
- Server reload() now refuses to run on archived (read-only) sessions
and when the session has work in progress, mirroring archive(), so a
reload can no longer silently abort an in-flight agent run.
- Add a sessions.reload runtime capability; the client gates both the
reloadSession call and the Reload menu entry on it so the action only
appears for machines whose Pi-Web runtime supports it.
- reloadSession ignores cached-new and archived sessions.
- Add server (PiSessionService + routes) and client (SessionController)
tests covering reload success, the active-work guard, archived
rejection, route forwarding, capability gating, and error mapping.
- Restore alphabetical parser import ordering in clients.ts.
- Add a changeset documenting the feature and the sessiond restart note.
Note: touches a session daemon code path, so pi-web-sessiond.service
must be restarted manually for the server side to take effect.
Depend on @earendil-works/pi-agent-core so the ThinkingLevel union has a single source of truth (re-exported via shared/thinkingLevels). Wire/data fields use string and the parser is lenient, so an unknown level from a newer pi runtime is still listed, selectable, and rendered gracefully instead of throwing. The composer gauge now derives its bar count from the levels available for the current model and fills by rank. Adds compile-time drift guards (satisfies + Exclude check) and unit tests so a changed pi level set fails fast in development.
Replace Send/Queue/Steer/Stop text buttons with compact icons, move Attach into the message box, and show the thinking level as a fill gauge. Keeps mobile layouts uncrowded while preserving accessible labels and the readable model selector.
Add per-role left accent stripes and color-coded header labels across
all themes. Lighten the dark theme user-message background, decouple it
from the generic hover color, and brighten the user border so user and
assistant turns are clearly distinguishable.
Add a dedicated "sending" activity indicator kind (warning color) so a
session uploading attachments is visually distinct from server activity.
Server activity propagates up to workspace/machine rows; client-side
sending does not, and the different color signals that to users.
Also extract sessionRowActivityKind as a pure, exported helper so the
"sending takes precedence, archived/cached-new never show" logic is unit
tested without rendering.
isSendingPrompt was a single global flag, so an in-flight upload showed
the "Sending…" dock on whatever session/machine the user switched to.
Replace it with sendingPrompts, a Record<sessionId, true> keyed like
sessionStatuses/sessionActivities. The controller sets/clears the entry
for the originating session (captured before the await), the chat dock
reads only the selected session's entry, and the session list now shows
the activity dot for a session that is uploading so progress is visible
after switching away. Machine switches clear the record like other
per-session state; deselecting a session no longer cancels its indicator.
The paste/drop/file capture logic (supported-type filtering, unnamed-file
extension fallback, per-file error collection) lived inside PromptEditor,
mixing browser side effects with branching that had no tests.
Move it into a pure promptAttachmentCapture module with the byte reader
injected, so the FileReader side effect stays at the component boundary
and the orchestration is unit-tested. Also removes a duplicated mime->ext
fallback in favour of the shared extensionForImageMimeType helper.
Replace the composer-local "Sending…" indicator with a single, consistent
state surfaced by the existing chat activity dock. The session controller
now owns the full attachment send lifecycle (including the folder-mode
upload + reference rewrite) and toggles an isSendingPrompt flag around it,
which the chat dock renders as "Sending your message…" until the real
server activity supersedes it.
This covers the pre-receipt dead zone (upload, server-side image resize,
first-session open) with one indicator instead of two, and keeps plain
text sends fire-and-forget.
Messages with image attachments could take a moment to appear (large
base64 upload, server-side resize, first-session open) while the composer
cleared instantly, making it look like nothing happened.
Await the send for attachment messages and surface a "Sending…" button
label plus a "Sending your files…" / "Saving your files…" hint, disabling
the composer until the message lands. Plain text messages stay
fire-and-forget so the input frees up instantly.
Support pasting (Ctrl/Cmd+V), drag-and-drop, and an Attach button to add
PNG/JPEG/GIF/WebP images to a message, with thumbnail previews and
multi-image support.
Attachments are delivered to the session using pi's native ImageContent
format and are run through pi's own resizeImage so they match pi's inline
image limits exactly. Image content now renders inline in the transcript.
A per-message delivery toggle also lets users save attachments into the
workspace `.pi-web/paste` folder and reference them so the agent reads
them with its own tools.
The accepted HTTP upload size is configurable via PI_WEB_MAX_UPLOAD_BYTES
or the maxUploadBytes config value (default 64 MB).
Closes#13
Run Linux restart commands inside a detached transient systemd user
service (systemd-run --user) so the restart survives the launching
terminal being killed and its logs can be inspected via journalctl.
Make the Updates panel actionable: every command has Copy and Run,
a single recommended all-in-one command is shown at the top, and the
rest are grouped as optional additional commands.
Drive-qualify resolved paths in fixtures and assert against normalized cwds
so route tests match the values normalizeRequestCwd produces. Skip the
POSIX-shell TerminalService suite on native Windows, where the terminal
feature is unsupported.
Sessions outside the server's launch directory were invisible and returned
404 on open, leaving the model picker empty. List without the SDK's
process-cwd filter and normalize working directories at the API boundary and
when reading stored session data, tolerating separator/normalization
differences (including Windows backslash vs forward slash). Requires Pi
coding agent SDK 0.78.0 or newer.
Running the suggested restart command from a PI WEB terminal kills the
command when sessiond restarts, so services listed after sessiond were
never restarted. Restart web/UI first in both the updates plugin's
suggested command and `pi-web restart` (systemd and launchd).