Commit Graph
78 Commits
Author SHA1 Message Date
Federico Jaramillo Martinez 69b125b001 feat(sessions): surface cross-worktree parent and child sessions
A session spawned into another worktree recorded a parent that no
listing contained, so the row showed only "parent unavailable" and its
parent's row looked childless. Both facts were accurate and useless:
neither said where the related session actually was.

Report both directions from the session store instead. A missing
parent's cwd and id come from its own file header, so one 4 KB read per
distinct missing parent resolves it without listing other workspaces;
children are counted by listing sibling workspaces and matching the
parent path they already recorded, needing no header reads. Reads are
memoized per path because Pi writes headers once, and the cache is
released on dispose. Both directions are best-effort: an unreadable
header or an unlistable worktree leaves a session unannotated rather
than failing the listing.

In the browser, an orphan child keeps the same child marker as a nested
one, dimmed, so it no longer renders as a root; whereabouts are stated
once on the meta line ("parent in feature/foo", "2 children
elsewhere"), where a clamped title cannot hide them. A "Go to parent
session" action switches to the owning workspace and selects the
parent. Live session.created events keep child counts current instead
of leaving them stale until the next listing.

Session and workspace paths reach the browser from two producers: store
enumeration for a listing, and the live runtime for a broadcast. They
are now compared through one normalizing helper, so tree nesting and
child counts cannot silently miss a link when only a trailing separator
differs.

Extract the shared "workspaces of the project containing this cwd"
lookup out of ProjectScopedSpawnTargetResolver so spawn targeting and
child counting share one implementation, and register it regardless of
whether spawning is enabled: children can predate a config change, and
the tree should stay honest about them either way.
2026-07-28 11:56:21 +02:00
Federico Jaramillo Martinez 0e83146315 fix(ui): streamline ask_user question forms 2026-07-27 10:55:49 +02:00
Federico Jaramillo Martinez 4e47c23f23 Merge remote-tracking branch 'origin/main' into feat/model-questions-ux
# Conflicts:
#	src/server/sessiond.ts
#	src/server/sessions/sessionRoutes.test.ts
2026-07-27 09:44:40 +02:00
Federico Jaramillo Martinez 4940eda352 fix(sessions): stop counting session startup as work in progress
Startup progress rides the per-session activity channel with an "active"
phase, because a startup phase really is in progress. But isSessionActive()
treated any active activity as work, so a session that was merely *opening*
enabled "Stop Active Work", disabled "Reload from disk" with the misleading
"Stop current session activity before reloading" tooltip, showed the row's
active-work indicator, and — for any caller that hands a startup activity to
WorkspaceActivityService — reported the whole workspace as busy. Selecting an
archived, read-only session reported active work while it opened.

Starting is not working. publishStartupProgress now marks its reports with a
new optional SessionActivity.startup field, and isSessionActive() does not
count a marked activity. Every affected consumer — the session list, the core
actions, the app's activity-transition handling, and the server's workspace
aggregation — reads that one helper, so the correction lands in all of them at
once.

The marker is a new field rather than a new phase on purpose: six readers test
phase === "active" directly, including the pending row's "creating · " prefix,
the chat dock's active styling, and the daemon's own heartbeat re-publication.
It can only ever remove the activity-phase reason for being active, so
streaming, bash, compaction, and queued prompts still report as active through
the status even while a startup report is the latest activity. The chat dock
still shows the startup text; this changes what counts as work, not what is
shown.

The browser's own pending-create row keeps its previous appearance: it borrows
only the daemon's phase text and drops the marker, since that row stands for a
create the user is waiting on rather than a session the daemon is opening.
2026-07-26 22:54:27 +02:00
Federico Jaramillo Martinez 51ebfe4c00 feat(sessions): deliver ask_user answers
Integrate the pending-ask store into the session service so an open ask is
visible, observable, and closable.

- `statusFromSession` projects `pendingAsk`, so a browser rehydrates an open
  ask from `GET /sessions/:sessionId/status` after reload or a web/API restart.
- `openAsk` publishes `ask.opened`, and publishes `ask.closed` first when the
  new ask supersedes an unanswered one.
- `submitAsk` / `cancelAsk` close the ask and hand the outcome to the model as
  a `pi-web.ask.answers` follow-up custom message (`triggerTurn`,
  `deliverAs: "followUp"`), the same delivery subsession notices use. A stale
  ask id is reported, not thrown: losing the race against a supersede or
  another browser is ordinary. Cancel still reports every question as
  unanswered so the model is not left waiting for a promised message.
- The open ask is forgotten when its runtime closes; nothing is left to
  receive the answers.
- `POST /sessions/:sessionId/ask/{submit,cancel}` behind the existing
  `/api/sessions/*` daemon proxy, allowlisted for machine federation.
2026-07-26 22:38:15 +02:00
Federico Jaramillo Martinez bd4a891b95 fix(sessions): correlate startup progress by token instead of workspace
Startup progress could still be shown on the wrong session's row. Routing by
known session id first closed the case where the browser knew the other
session, but left open the case where it does not -- which the browser is
designed to produce. While a create is pending for a workspace,
applyCreatedSession deliberately withholds a session.created event for that
workspace and stashes it, to avoid a duplicate row. So during exactly the
window this feature exists for, a session created by an agent's spawn or by
another tab is intentionally absent from the session list. Its startup events
carried an unrecognised id and a matching cwd, and were routed onto the user's
pending create row, showing a phase and a label belonging to another session.

Workspace path was never evidence of identity; it was the only key both sides
happened to share. Give them a real one. The browser already invents a
temporary row id for a pending create, so it now sends that id with the create
request as an opaque startupToken; the daemon carries it through construction,
echoes it on the startup events it publishes for that construction, and the
browser matches it exactly. The token is a throwaway label the daemon never
interprets. It never becomes the session id: activity.sessionId still carries
Pi's SessionManager id, which remains how an open of an already-known session
is routed.

With exact identity available, the guessing is deleted rather than gated.
startupProgressPendingStart goes entirely, and with it the selected-machine
comparison, the cwd filter, and the single-match ambiguity rule: a second
concurrent create carries a different token, and a foreign workspace or
non-selected machine carries no token this browser is waiting on, so those
cases stop existing rather than needing detection. One Map lookup replaces a
filtered scan. cwd comes off the event, since it existed only as the routing
key and nothing else read it.

No compatibility path is needed. session.startup is unreleased -- checked
against the published tarball, not only git tags -- so no deployed daemon
emits these events and no deployed browser parses them. An older daemon
ignores the extra request field; a newer daemon talking to an older browser
degrades to the pre-existing generic wording, as does any unmatched token.

One silent behaviour change to state plainly: startupProgress guarded on
`sessionId === "" || cwd === ""`. Removing cwd from the event removes the
meaningful half of that guard, and that half had no test. The session-id half
is kept, which is the half that actually protects honest reporting.

The replaced ambiguity test is rewritten rather than dropped, so the same three
scenarios still pin the user-visible guarantee -- no match means the generic
wording stays -- now including the reproduced foreign-session case, which fails
against the previous code. Session creation ordering, semantics, and queueing
are unchanged; the token is a passthrough label read only to build an event.
2026-07-26 22:10:58 +02:00
Federico Jaramillo Martinez 49393e1cb9 feat(sessions): add the pending ask store
Own the one-open-ask-per-session lifecycle in daemon-side domain logic: validate
model-authored question sets, validate submitted answers against them, and
compute the answered-versus-unanswered outcome both the model-facing follow-up
message and the browser record are rendered from.

Also lands the answer half of the shared ask contract alongside its first
consumer.
2026-07-26 21:45:51 +02:00
Federico Jaramillo Martinez 6fa57b524b feat(config): add askUser capability and shared ask contract
Introduce the shared contract for the upcoming ask_user tool: question and
pending-ask types in the API contract, the pendingAsk field on
SessionStatus, ask.opened/ask.closed session UI events, the askUser global
config key with a PI_WEB_ASK_USER env override, and the sessions.askUser
capability requiring both the web and session daemon runtimes.

askUser defaults to true: the questions land in the session the user is
already watching and nothing happens until they act, unlike the beta-off
subsessions flag.
2026-07-26 21:26:23 +02:00
Federico Jaramillo Martinez 49e7c390f3 feat(sessions): tell the user what a session start is waiting on
Creating or opening a session could stall for reasons the daemon knew
about and never shared. The browser invented the whole message it showed
while waiting -- "Creating session: Waiting for the backend session to be
ready" -- which says that we are waiting but never what for. A shared
ModelRuntime read during startup can be handed a network refresh that is
already in flight, and extensions may do their own network I/O while
loading, so the wait is real and previously unattributable.

The pre-session gap turned out to be a missing shared key rather than a
missing channel: publishActivity needs the PiAgentSession being built, but
the session id and cwd are both known before the first await. So create()
now publishes a new global session.startup event carrying an ordinary
SessionActivity, routed by cwd -- the one identity a browser row waiting
for a session id can match, since the client-invented pending id is
unknown to the daemon and the daemon's id is unknown to the browser.

Two phases are reported, each published before the await it describes so
the label changes during the wait rather than after it: "Starting the Pi
session" and "Loading session extensions". Both are facts, because the
service awaits exactly one call for each. A concurrent background catalog
refresh is appended as a note ("provider model lists are refreshing"),
never as the cause: the refresher can prove a refresh is running but not
that this startup joined it. ModelCatalogRefresher gains only a read-only
isRefreshInFlight() getter; cadence, timeout, and coalescing are untouched.

Reporting is event-only and synchronous. It writes no activities entry, no
workspace activity, and no unread state, so a failed creation leaves
nothing stranded, no await is added, and creation ordering and semantics
are unchanged. The window-ending idle report is skipped when a real
activity was published during startup, so an extension error survives.

The browser applies startup progress only when it can prove the target:
one non-discarded pending start in that cwd on the selected machine, or a
session whose id it already knows. A foreign workspace, another machine,
or two concurrent starts in one workspace keep today's generic wording
rather than showing one row the phase of another. An idle report restores
that generic wording, including the queued-messages variant.

docs/config.md said nothing a request triggers waits on a catalog fetch.
That is not strictly true for a refresh already in flight, so both it and
the generated docs/config.html now state the exception and say PI WEB
reports it while it happens.
2026-07-26 16:19:05 +02:00
lzr dd435cb1b0 feat(git): view submodule working-tree changes in the Git panel
Recurse into dirty submodules when building the Git status so their
modified and untracked files appear as full-path entries, and add a
commit-pointer entry (with short SHAs) only when the recorded commit
actually moved. Route diffs whose path falls inside a submodule to run
in that submodule's working tree so real per-file diffs are shown.

The changed-file list groups these under the submodule: tree view keeps
the nested structure and marks the submodule root with a badge, list
view flattens them into one expandable group pinned above the ordinary
files. Depth 1 only; ignored files are excluded; the panel stays
read-only.

Covered by client tree/list-grouping tests, parser tests, and a
server test that drives a real temporary repository and submodule.
2026-07-22 21:35:06 +08:00
Federico Jaramillo Martinez 115d74e79a feat(sessions): persist shared unread state 2026-07-20 19:36:16 +02:00
Federico Jaramillo Martinez 4ca4a1d096 feat: add hierarchical session tree navigator 2026-07-20 10:40:17 +02:00
Federico Jaramillo Martinez 6e09df8329 feat(sessions): add daemon notification inbox protocol 2026-07-19 01:28:45 +02:00
Federico Jaramillo Martinez c569a03f54 fix(auth): make API-key setup and status truthful 2026-07-18 08:28:03 +02:00
Federico Jaramillo Martinez 3a208e648e fix(auth): preserve OAuth interaction semantics 2026-07-17 23:34:58 +02:00
Federico Jaramillo Martinez aedcbf885e feat(sessions): surface live session startup warnings in the web UI
Show a pinned banner at the top of the session view with resource and
runtime diagnostics (skills, prompts, themes, extension load errors) plus
the Anthropic subscription-auth billing notice, recomputed live from the
current runtime so they stay accurate across browser reloads.

Warnings carry an optional dismiss capability; the Anthropic notice is
dismissable and durably suppressed through pi's own anthropicExtraUsage
warning setting. Also fixes the testing-guide skill frontmatter so it
loads.
2026-07-17 19:16:58 +02:00
Federico Jaramillo Martinez 2b17145291 feat(sessions): stream in-flight partial when joining a mid-turn session
Seed the in-flight partial assistant message (text, thinking, and
in-progress tool calls) when opening or reconnecting to a session that is
mid-stream, then continue streaming live deltas on top of it. Replaces the
blocking "Catching up..." placeholder and the end-of-turn transcript reload.

Server stamps every per-session UI event with a monotonic seq at the
SessionEventHub publish choke point and exposes
GET /sessions/:sessionId/stream-snapshot returning { seq, partial }. The
client fetches the snapshot on join, seeds the normalized partial into the
in-memory transcript (never the history cache), and applies buffered/live
events using the seq watermark for exactly-once delivery.

The snapshot is a progressive enhancement: a 404 from an older remote
pi-web or a not-yet-restarted session daemon falls back to an empty seed
(seq 0, drops nothing), so sessions still open and stream normally. The
stream-snapshot route is registered in the federation allowlist for
remote-machine proxying.
2026-07-17 14:49:56 +02:00
Federico Jaramillo Martinez ca07e48c0a docs: clarify Pi-compatible agent profiles 2026-07-14 00:30:09 +02:00
Federico Jaramillo Martinez a1f749cdb6 feat: add server session queue clearing 2026-07-14 00:24:14 +02:00
Federico Jaramillo Martinez 8b5ccc2fd9 feat: apply agent profile settings atomically 2026-07-14 00:11:39 +02:00
Federico Jaramillo Martinez 141cda93c8 feat: expose daemon-owned active agent profile 2026-07-13 22:50:50 +02:00
Federico Jaramillo Martinez cfa9f5a2fa Merge remote-tracking branch 'origin/main' into pr-36-generic-agent-config
# Conflicts:
#	docs/config.html
#	docs/config.md
#	src/cli.test.ts
#	src/cli.ts
#	src/client/src/components/settings/SettingsSessiondPanel.ts
#	src/client/src/components/settings/settingsConfigDraft.test.ts
#	src/client/src/components/settings/settingsConfigDraft.ts
#	src/server/app.test.ts
#	src/server/app.ts
#	src/server/configRoutes.test.ts
#	src/server/configRoutes.ts
#	src/server/piWebPluginService.test.ts
#	src/server/piWebPluginService.ts
#	src/server/piWebStatus.test.ts
#	src/server/piWebStatus.ts
#	src/server/piWebStatusCache.ts
#	src/server/sessions/authService.test.ts
#	src/server/sessions/piSessionService.ts
#	src/server/sessions/sessionRoutes.test.ts
2026-07-13 20:30:08 +02:00
Federico Jaramillo Martinez eb1727688f fix: preserve legacy federated session actions 2026-07-04 22:39:55 +02:00
Pi Web Agent a2bfd3657d Merge remote-tracking branch 'origin/main' into feat/docker-runtime-host-admin
# Conflicts:
#	src/client/src/api.ts
#	src/client/src/api/parsers.test.ts
#	src/shared/piWebStatusParsing.test.ts
2026-07-02 20:52:50 +00:00
Federico Jaramillo Martinez 2665d1e4bc fix: handle session start persistence 2026-07-02 18:46:44 +02:00
Federico Jaramillo Martinez 64b2b32705 feat: target settings to selected machine 2026-07-02 13:28:03 +02:00
Federico Jaramillo Martinez 332db5faeb feat: expose package management capability 2026-07-01 22:13:02 +02:00
Federico Jaramillo Martinez 8ade238228 feat: add Pi package management settings 2026-07-01 15:34:34 +02:00
Federico Jaramillo Martinez d165d69f73 feat: add bulk session mutations 2026-06-29 13:34:03 +02:00
Pi Web Agent bd28c93cfe feat: unify Docker entrypoint 2026-06-29 11:27:47 +00:00
Federico Jaramillo Martinez 75e2377756 feat: generalize agent runtime config 2026-06-28 16:36:40 +02:00
Jeff Scott Ward 84a485d62e feat: add OMP runtime support 2026-06-26 12:49:58 -04:00
Federico Jaramillo Martinez cb13af4b88 feat: add manual session cleanup 2026-06-26 15:57:12 +02:00
Federico Jaramillo Martinez 7e812aa7f5 feat: support general chat file attachments 2026-06-25 21:32:07 +02:00
Federico Jaramillo Martinez e46d9ecbf8 feat: add safe manual workspace uploads 2026-06-25 15:16:44 +02:00
Federico Jaramillo Martinez a0dd9e1cd7 Merge branch 'main' into cleanup/plugin-api-scope 2026-06-24 08:49:49 +02:00
Federico Jaramillo Martinez 9cc20d65fb feat: add external path access allowlist 2026-06-23 12:30:30 +02:00
Federico Jaramillo Martinez 997b821717 feat: add check_subsession and an exploratory read_subsession transcript reader
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.
2026-06-19 23:48:12 +02:00
Federico Jaramillo Martinez 355ebe8cf8 feat(sessions): add tracked subsessions behind a beta flag
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.
2026-06-17 12:13:23 +02:00
Federico Jaramillo Martinez 95c151233a feat: let agents start new sessions via spawn_session tool
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.
2026-06-16 14:53:37 +02:00
marcus 27a3b2b5ed feat: Plugin API Completeness — file mutations, prompt editor, and attachment APIs
- 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
2026-06-14 15:03:23 +02:00
Federico Jaramillo Martinez 82db15f894 fix(sessions): guard, gate, and test session reload
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.
2026-06-14 14:17:51 +02:00
Federico Jaramillo Martinez ca30c970a7 refactor: source thinking levels from pi and make the gauge dynamic
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.
2026-06-14 13:47:44 +02:00
Federico Jaramillo Martinez edae57b836 Clean up remaining dead code and dependencies
- Replace unused 'codemirror' barrel dep with explicit @codemirror/{state,
  view,commands,language} sub-packages that were imported but unlisted
- Remove unused exports: writeNamespacedQuery, targetWorkspacePathForRun,
  isWorkspaceDeletionRun, piWebConfigDir, duplicate parsePiWebRuntimeResponse
- Remove unused types: WorkspacePanelFiles, WorkspacePanelHost,
  GetActiveSession, SaveAttachmentsResponse, QueryValues
- Drop now-unused re-exports/imports cascading from the above
- Add knip config + 'npm run knip' script for ongoing dead-code detection
2026-06-13 21:57:51 +02:00
Federico Jaramillo Martinez d17050e144 feat: add image attachments to the chat composer
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
2026-06-13 13:49:39 +02:00
Federico Jaramillo Martinez 71510444c4 Merge remote-tracking branch 'origin/main' into investigate/issue-12-session-dir
# Conflicts:
#	src/client/src/api.ts
#	src/client/src/api/clients.ts
#	src/client/src/api/federatedRouteContract.test.ts
#	src/server/sessions/piSessionService.ts
#	src/server/sessions/sessionRoutes.ts
2026-06-10 20:43:10 +02:00
Federico Jaramillo Martinez 06052ea5ec fix: respect Pi session directories by cwd 2026-06-10 20:26:51 +02:00
Federico Jaramillo Martinez c57f24dfa5 feat: support machine-specific plugins 2026-06-09 15:10:32 +02:00
Federico Jaramillo Martinez fc20b95fed feat: gate session cleanup by runtime capabilities 2026-06-09 11:40:17 +02:00
Federico Jaramillo Martinez 4eb95fec7f Merge remote-tracking branch 'origin/main' into review/pr-5-machine-federation-fixes
# Conflicts:
#	src/client/src/api.ts
#	src/client/src/api/clients.ts
#	src/client/src/api/parsers.ts
#	src/client/src/components/PiWebApp.ts
#	src/server/app.ts
#	src/shared/apiTypes.ts
2026-06-04 10:31:23 +02:00