Tick the background catalog refresher hourly instead of every four hours:
pi stamps `checkedAt` after a fetch completes, so a tick at exactly its 4h
freshness window always landed a few seconds short and only fetched on
every other tick (~8h effective). Scheduled runs stay unforced, so the
extra ticks are nearly free and pi's gate keeps deciding when to fetch.
Auth-triggered refreshes now pass `force: true` so a re-login of a
provider refreshed within the last four hours actually reaches the
network. A request queued behind an in-flight run keeps the strongest
mode asked for, so a forced request is never downgraded.
Raise the whole-cycle timeout to 60s, since one run covers every
refreshable provider and a background job has no startup budget, and give
a timed-out or errored run exactly one bounded retry. Retries never earn
retries, are superseded by any fresh request, and are cleared by
`dispose()`.
The background model catalog refresher always requested a network refresh,
so sessiond fetched provider catalogs on a schedule even when the operator
set PI_OFFLINE or PI_WEB_OFFLINE. Before the refresher existed, those
settings made every runtime refresh local-only.
Add `offlineModeEnabled()` to the config module and inject the resulting
flag from sessiond's frozen daemon environment, so the refresher schedules
nothing and ignores auth-triggered requests in offline mode. The narrower
PI_SKIP_VERSION_CHECK / PI_WEB_SKIP_VERSION_CHECK keys are deliberately not
included: they only suppress release lookups.
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.
- N1: extract the copy-pasted pointerName/segmentName helpers from
gitFileList.ts and gitFileTree.ts into gitFileShared.ts.
- N3: drop the dead conditional "tree" class (no CSS rule exists).
- N4 (P3): memoize computeViewState on (status, view) identity so renders
from expand/collapse or diff selection skip the full model rebuild;
expand state is read live at render time, never cached.
The right-anchored .toolbar-actions group rendered the view toggle left of
the conditional expand/collapse-all button, so the toggle jumped left
whenever the button appeared. Render expand/collapse-all first (leftmost)
so only the space to its left changes; the toggle and Refresh stay put.
A staged submodule add records an all-zero head OID, which rendered as
0000000 → <sha>. Display the zero OID as "new" instead; the client
pointer label needs no change (N4).
- expandSubmodules now fans out with Promise.all over the dirty
submodules and concatenates results in input order, so the polled
status endpoint no longer pays serial git status/rev-parse spawns
(P1).
- submoduleForPath bails out before spawning git config when the path
contains no '/' or the repo has no .gitmodules, removing a spawn
from every diff call in plain repos (P2).
- Rename submodulePaths() to configuredSubmodulePaths() and the
expandSubmodules local to dirtySubmodulePaths to disambiguate the
two concepts (N2).
- parseStatus: detect staged submodule pointer moves by comparing the
recorded HEAD/index OIDs (porcelain reports S... for a staged move, so
the c flag never fires); staged moves previously vanished from the
status response (PR #92 review finding B1).
- parseStatus: keep deleted gitlinks (index or working tree) as plain
deletion rows instead of deferring them as submodules. Unstaged
deletions vanished entirely, and staged deletions would render as a
bogus pointer move to the zero OID. The finding assumed N... porcelain;
git 2.54 actually emits .D/D. S... (finding S3's stated outcome).
- submodulePaths: parse 'git config -z' records so submodule paths with
spaces survive .gitmodules key parsing instead of splitting lines at
the first space (finding S1).
- tests: strip inherited GIT_* env vars in the fixture helper so the
suite also passes when run from a git hook (pre-commit sets GIT_DIR).
Adds real-git fixture tests for staged moves, staged+dirty combos,
deleted submodules, inner renames, and spaced submodule/file paths.
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).
Re-add the minimise chevron to the expanded session-warnings pane in
ChatView, wired to the existing unified onToggleWarnings (toggle ≡
collapse in the expanded state). The status-bar warning toggle from
a13778c is retained unchanged; both controls share the single
sessionWarningVisibility mutation, so they cannot desync.
- ChatView: onToggleWarnings prop + handleToggleWarnings; chevron
rendered inline via html (no svg re-import), guarded by
onToggleWarnings === undefined.
- PiWebApp: renderChatView <chat-view> passes .onToggleWarnings.
- shared.ts: restore .session-warnings-controls / -collapse / icon CSS.
- ChatView.test.ts: restore the chevron-wiring test against
onToggleWarnings via the session-warnings-collapse marker.
Relay restore-warning-chevron leg 1.
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
Unit tests for the policy shim (swallowed registrations, no-op
unregister, untouched global providers, rejection wording) and
acceptance tests wired as sessiond wires production: load-time
rejections surface as session warnings while extension tools and
commands keep working, late registrations are broadcast to active
sessions' notification inboxes, colliding provider ids across
workspaces cannot affect each other, and a project-level models.json
does not alter the shared runtime.
PI WEB only supports globally configured providers (Pi built-ins,
agent-dir models.json, environment credentials). A daemon-wide shim on
the shared ModelRuntime swallows extension registerProvider calls and
makes unregisterProvider a no-op, so one workspace's extensions can no
longer corrupt the provider set of concurrent sessions (issue #76).
Rejections during a services load surface as session warnings through
the existing diagnostics pipeline; late registrations from session
event handlers broadcast a notification to active sessions. Everything
else extensions register keeps working.
Requires manual restart of pi-web-sessiond.service (daemon wiring changed).
- 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.
Skip opportunistic status requests when the flow's originating machine is no longer selected, and discard in-flight status results after the machine or session selection changes.\n\nRefs #74
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