Wire the derived UnreadPresence into dot indicators (no counts) across the
navigation panel: workspace, project, and machine rows (machine-switcher and
machine-list) now show a static accent dot whenever a session beneath them is
unread, including offline machines with stale-but-present state. Presence
flows PiWebApp -> AppNavigationPanel -> leaf id sets, mirroring the
unreadSessionIds chain, and is covered by happy-dom component tests per list
plus panel- and app-level wiring tests. Adds changesets for the mark-as-read
actions and the bubble-up indicators.
Call WorkspaceController.refreshSelectedProjectTopology() from the existing
browser-resume refresh and the plugin-facing refreshAppData path, so worktrees
created or removed outside PI WEB become visible with no user action. No new
timer, watcher, process, or push channel; the resume path is already debounced
per animation frame and collapses concurrent requests.
Document the resume-scoped detection and the hiding of gone checkouts in the
FAQ, and add the changeset for the user-visible behavior.
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.
The global provider bootstrap froze all three ModelRuntime mutation
methods after startup, so a provider extension that fetched an updated
model catalog had that work silently discarded.
registerProvider is now applied when the provider ID is already in the
frozen baseline and the incoming config equals the recorded baseline in
every field except `models`. Refreshing extensions re-send a complete
provider config rather than a models-only delta, so the test is
"equal except models", not "contains only models".
Everything else stays a logged no-op: unknown provider IDs, any change
to name/baseUrl/apiKey/api/streamSimple/headers/authHeader/oauth/
refreshModels, native registration, and unregistration. Function-valued
fields compare by reference and so always read as a mismatch, which is
the intended conservative direction.
An accepted update rebases the stored baseline from Pi's merged record,
so repeat refreshes work and an unchanged replay is correctly ignored
rather than re-applied on every session start. The accept path stays
synchronous and never awaits or networks; Pi's own trailing
fire-and-forget local refresh is untouched.
Wrap chat markdown tables in a focusable scroll region and let the table
keep its natural width so narrow screens can scroll instead of squeezing
columns into the chat width.
Finding 6: serialize createOfflineModelRuntime so overlapping calls cannot
interleave their PI_OFFLINE save/restore pairs and leave the process offline,
and name the process-wide visibility of that window in the docblock.
Finding 7: assert the offline construction through the public refresh seam via
reloadConfig() — the request path that regressed — instead of reading upstream's
private modelNetworkEnabled field.
Finding 8.4/8.5: document the background provider-catalog refresh in
docs/config.md and docs/config.html (cadence, timeout, single retry, offline
opt-out via PI_WEB_OFFLINE / PI_OFFLINE only), and update the changeset to
match the behavior after the earlier fixes.
The shared ModelRuntime was constructed with network refreshes enabled, so
reloadConfig()/login()/logout() — called on the model picker, session model
changes, and auth dialogs — performed unbounded provider-catalog fetches.
A single stalled fetch blocked those requests for minutes and, through pi's
coalesced per-provider refresh, dragged session creation along with it.
Construct the runtime with PI_OFFLINE forced so every runtime-driven refresh
stays local, and add ModelCatalogRefresher as the single deliberate network
path: bounded by an abort timeout, serialized through one in-flight run,
scheduled in the background, and triggered after provider auth changes.
Update the single quiet-session-warnings changeset entry to cover the
final shipped behavior: status-bar expand/collapse toggle, the in-pane
minimise chevron restored in 2bdfd48, per-session remembered state, and
SVG warning icons. Frontmatter (patch) unchanged. No new changeset.
Relay restore-warning-chevron leg 2 (final).
Relaxes the provider policy from 'global config only' to 'global sources':
providers registered by agent-dir (global) extensions are learned once at
daemon startup and allowed on the shared runtime; project-extension
registrations are still rejected with a session warning. Global extensions
load identically for every session, so their providers are daemon-consistent
and cannot leak project state (#76).
- Shim now allows allowlisted ids through and also covers Pi 0.81's native
provider path (registerNativeProvider), closing a bypass.
- Startup learning step loads only global extensions against a scratch cwd
and diffs the runtime's registered provider ids.
- Bumps @earendil-works/* dev/peer ranges to >=0.81.1 <0.82; adapts to the
Agent.streamFn -> streamFunction rename.
- Docs, changeset, unit and acceptance tests updated (global-extension allow
path, late re-registration a la pi-tensorx, native provider rule).
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.
Extract the Git panel into a dedicated `workspace-git-panel` Lit component
(mirroring the Files panel) and add a segmented List/Tree toggle next to
Refresh.
Tree view builds an in-memory, collapsible directory tree from the changed
files, starts fully collapsed, and offers a single expand-all/collapse-all
button (visible only in tree view). List view keeps the existing flat,
full-path rows. The selected view mode persists in localStorage under
`pi-web.gitFileView`; per-directory expand state is intentionally ephemeral.
- gitFileViewPreference.ts: localStorage-backed view preference (+ test)
- gitFileTree.ts: pure flat-paths -> nested-tree builder (+ test)
- WorkspaceGitPanel.ts: the panel component with toggle + tree state
- Only increment visual branch depth after forks so long linear session
histories stay in one lane instead of scrolling off-screen; lower the
max visual depth cap to match.
- Reset to the no-summary default when leaving an invalid custom summary
choice so Navigate is never permanently disabled by a stale invalid entry.
Retain client-owned machine affinity for each interactive auth flow and use it for prompt responses, polling, cancellation, and completion refreshes. This prevents a later machine selection from forwarding secrets to a different remote.\n\nRefs #74
Best-effort cancel a running auth flow when its start response arrives after the browser operation was closed or superseded, so sessiond does not retain orphaned provider polling or callback listeners.\n\nRefs #72
Relay issue-62-authstorage leg 7 (final): add patch changeset for the
session-daemon crash fix on Pi >=0.80.8, re-verify green, confirm goal
criteria and cleanup. Relay complete.
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.