Rewrite sessionNameGenerator.ts to consume a StreamFn-shaped dependency
(sourced from PiAgentSession.agent.streamFn, wired in leg B) instead of
ModelRegistry plus a dynamic @earendil-works/pi-ai/compat import.
streamFn resolves auth/headers/retry internally, so the explicit
modelRegistry.getApiKeyAndHeaders(model) call and apiKey/headers stream
options are no longer needed.
Deletes now-dead compat-loading machinery: getPiAiProviderRegistryModule,
loadPiAiProviderRegistryModule, importOptionalPiAiModule,
isModuleUnavailableError, hasGetApiProvider, PI_AI_COMPAT_MODULE,
ModuleImporter, SessionNameApiProvider, PiAiProviderRegistryModule, and
the module-level provider registry cache.
Updates the maybeGenerateSessionName call site in piSessionService.ts to
pass session.agent.streamFn instead of this.modelRegistry, and updates
both test files: sessionNameGenerator.test.ts gains coverage for the new
streamFn-driven generateShortSessionName signature (success and error
paths), and piSessionService.test.ts gains an end-to-end test proving a
first prompt generates a session name through the wired agent.streamFn
fake.
sessionNameGenerator.ts no longer imports @earendil-works/pi-ai/compat
or references getApiProvider anywhere.
Add a narrow `agent: { streamFn: StreamFn }` field to the PiAgentSession
interface, sourced structurally from the real AgentSession.agent (Agent,
from the stable @earendil-works/pi-agent-core package). This is the
resolved-auth/headers/retry "call this model" function pi's own
compaction/branch-summarization code uses internally, and will let
sessionNameGenerator.ts drop its dependency on the deprecated pi-ai
/compat provider registry in a follow-up leg.
No adapter object was needed: AgentSession's real .agent field already
structurally satisfies the new narrow shape, so defaultCreateAgentRuntime
needed no changes.
Adds a unit test proving the field is wired end-to-end via the injected
runtime creator. sessionNameGenerator.ts itself is untouched (leg C).
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 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.
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.
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.
- WorkspaceFiles: writeFile, deleteFile, moveFile with path safety
- writeFile: text/binary, auto-create dirs, overwrite option
- deleteFile: idempotent, uses lstat (removes symlinks not targets)
- moveFile: unix mv semantics, overwrite defaults to false
- All mutations auto-refreshFiles() in File Explorer
- Symlink escape prevention via realpath(dirname) check
- PluginPromptEditor: insertText, getText, getSelection, onPaste, onKeyDown, focus
- Uses CM6 EditorView.domEventHandlers() via Compartment (not raw DOM)
- Handlers registered before mount are preserved and applied on mount
- First-to-consume-wins ordering for multi-plugin scenarios
- insertText replaces selection (not inserts after)
- PluginAttachments: insertFileReference, getAttachedFiles, removeFileReference
- insertFileReference validates file exists before inserting @path
- Does not auto-focus editor (unlike prompt.insertText)
- @file regex requires file extension to avoid matching emails
- Server endpoints: PUT /file, DELETE /file, POST /file/move
- All work for local and federated machines
- Tests: 31 unit tests, 9 integration tests, 5 client tests
- Docs: 3 new sections in plugins.md
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.
Sessiond caches the in-memory SessionManager and never re-reads the
session file. When the same session is also being edited by another
process (e.g. the pi CLI), new entries on disk are invisible to the
web UI \u2014 the tail of the conversation gets cut.
Add a manual Reload action in the session three-dot menu:
- Server: PiSessionService.reload(sessionId) closes the active
session and re-opens it from disk, then publishes a fresh status.
Exposed as POST /api/.../sessions/:sessionId/reload.
- Client: api.reloadSession, SessionController.reloadSession which
discards the cached transcript and re-runs selectSession so the
history page is re-fetched.
- ChatTranscriptStore gains discard(sessionId) and the history
cache adapter gains optional remove(sessionId).
- SessionList shows a Reload entry for non-archived, non-cached
sessions; plumbed through AppNavigationPanel and PiWebApp.
Note: pi-web-sessiond.service must be restarted manually after this
change since the session daemon code path is affected.
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.
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).
Use viewport-constrained positioning for sidebar action menus and keep the auth warning test isolated from local models.json config so pre-commit verification is deterministic.