Commit Graph
749 Commits
Author SHA1 Message Date
Federico Jaramillo Martinez e12e5d16c6 docs(relay): record worktree-autodetect leg 1 2026-07-26 22:25:59 +02:00
Federico Jaramillo Martinez 4eca5fe6f2 fix(workspaces): hide worktrees whose checkout directory is gone
git worktree list keeps reporting a linked worktree after its directory is deleted
outside PI WEB, marking it prunable. The workspace list showed those as normal
selectable workspaces. The porcelain parser now reads the prunable and locked keys,
and WorkspaceService filters prunable linked worktrees out while always keeping the
project's own path. Listing stays read-only; git worktree prune is never run.
2026-07-26 22:25:59 +02:00
Federico Jaramillo Martinez 8a3fe0daa6 docs(relay): add worktree-autodetect relay packet
Assessment of auto-detecting worktrees created outside PI WEB. Worktree
discovery is already derived per request with no cache and no registry, so
the gap is that the browser never re-lists. Recommends the reduced scope:
filter prunable worktrees, add a non-disruptive topology refresh, and call
it from the existing browser-resume path. No watchers, timers, processes, or
push channels.

Packet is repo-only and outside the published files allowlist, so no
changeset is needed.
2026-07-26 22:25:59 +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 2a05d67436 test(sessions): pin the baseline rebase and invalid catalog refresh
Two behaviors of the narrowed provider freeze were unprotected: deleting
the baseline rebase, or swallowing Pi's validation error on the accept
path, both left the suite green.

Add a replay test asserting one applied update and one de-duplicated
ignored entry across four identical registrations, which is what a
per-session session_start handler produces. This fails if the rebase is
removed, because every replay then differs from the original catalog and
is accepted forever.

Add a test for a provider whose models carry their own api/baseUrl, so a
refreshed catalog omitting them fails validation. It pins that the error
reaches the extension rather than being silently swallowed, and that the
recorded baseline and previously registered models survive.

Note that a poisoned-baseline reordering is deliberately not asserted:
recording the incoming config early only corrupts `models`, and models
are expected to differ, so no later comparison can observe it.
2026-07-26 17:32:42 +02:00
Federico Jaramillo Martinez 67be9fb523 fix(sessions): attribute session startup progress by id before workspace
Startup progress resolved its target row by workspace path first and only
fell back to a known session id, which let one row be shown another row's
phase. While a create is pending in a workspace, an existing session in that
same workspace can also be opened -- by selecting another row, by another
tab, or by a subsession open -- and that open publishes the same cwd. The
cwd-first order rewrote such an event onto the pending create row, so a user
watching a session being created could be told a phase that belonged to a
different session. That is exactly the dishonest attribution this work set
out to avoid.

A known session id is the strongest available proof of the target, so it is
now checked first; workspace routing is used only when the id is unknown,
which is precisely the pre-session case it exists for. No wording changed and
no event changed; only which row an event is applied to.

Two tests were added where behavior was asserted but not proved. The
controller test fails against the previous order, so the misattribution is
now pinned. The service test covers a startup whose extension binding
rejects, proving the window still ends with an idle report rather than
leaving a waiting row labelled with a phase the service has left.
2026-07-26 17:05:56 +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
Federico Jaramillo Martinez 36d67262ec docs(config): describe the model list refresh exception
The Pi extension provider baseline section stated that every later
provider registration is a no-op, naming session_start as an example.
That is now the exact case that is allowed, so the shipped configuration
reference contradicted the behavior. docs/config.md is in the package
files allowlist, so this text reaches users.

Describe what stays frozen, the two conditions under which a known
provider's model list refresh is applied, and why function-valued fields
are always rejected. Record the accepted trade-offs: catalogs are shared
daemon-wide state with last-registration-wins, and a model entry's own
baseUrl/headers take precedence over the provider-level values. Split
the log behavior out and note that ignored mutations are de-duplicated
per provider while applied refreshes are always logged.

Also correct the reload-behavior bullet: a restart is needed to add or
remove a provider or change its connection settings, but not for a known
provider refreshing only its model list.
2026-07-26 14:14:30 +02:00
Federico Jaramillo Martinez 531ccf7b2a feat(sessions): let a known provider refresh its own model list
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.
2026-07-26 14:14:07 +02:00
Federico Jaramillo Martinez 111db63f4a fix(chat): scroll wide markdown tables horizontally
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.
2026-07-26 10:32:42 +02:00
Federico Jaramillo Martinez 2fb626204c chore(release): v1.202607.2 2026-07-25 23:51:01 +02:00
Federico Jaramillo Martinez 7431d1b6d9 Merge pull request #101 from jmfederico/chore/require-pi-0.82
chore(deps): require pi 0.82.1
2026-07-25 23:22:03 +02:00
Federico Jaramillo Martinez dfa6647402 test(sessions): cover the frozen provider baseline across runtime refreshes 2026-07-25 22:20:24 +02:00
Federico Jaramillo Martinez e7eb5383b2 docs: correct the model catalog refresh retry claim
The five-minute retry recovers from timeouts and unreachable providers,
but not from a provider that answers with an HTTP error status: pi
re-stamps checkedAt on that path, so the unforced retry lands inside a
fresh freshness window. Tracked in #100.
2026-07-25 22:12:53 +02:00
Federico Jaramillo Martinez 92854489cf docs: require pi 0.82.1 in user-facing requirements 2026-07-25 22:10:42 +02:00
Federico Jaramillo Martinez 20b65cf5b9 chore(deps): require pi 0.82.1 and port the removed reloadConfig()
Bump @earendil-works/pi-coding-agent, pi-ai, and pi-agent-core to 0.82.1
together and raise the peer range to >=0.82.1 <0.83. All three must move in
lockstep: bumping only two leaves a duplicate pi-ai copy in the tree, which
surfaces as misleading type-identity errors rather than real API breaks.

Pi 0.82 removed ModelRuntime.reloadConfig() and merged it into refresh(),
which now does ModelConfig.load, configureRadiusProviders, and rebuildProviders
before refreshing. Port the five production call sites literally, passing no
options so refresh() keeps defaulting allowNetwork to modelNetworkEnabled --
which the shared runtime pins to false by constructing under PI_OFFLINE. No
call site passes allowNetwork: true.

The auth tests lose reloadConfig() as an observation seam, so the offline
regression cases now drive removeRuntimeApiKey(), the surviving public mutation
that still forwards the construction-time network flag to refresh().
2026-07-25 19:09:18 +02:00
Federico Jaramillo Martinez b445ddae04 Merge pull request #97 from jmfederico/fix/model-catalog-refresh-stalls
fix(sessions): move provider catalog network refreshes off request paths
2026-07-25 18:39:30 +02:00
Federico Jaramillo Martinez 5d632ef11d test(sessions): make the offline-runtime regression test discriminating
The reworked assertion ran under the file-level PI_OFFLINE=1 stub, so the
runtime was offline whether or not createOfflineModelRuntime forced it and
the test passed with the fix fully removed. Clear the stub for that case,
and rewrap a docblock line.
2026-07-25 13:32:21 +02:00
Federico Jaramillo Martinez 90dd7ce401 docs(sessions): document background catalog refresh and contain offline env window
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.
2026-07-25 13:07:02 +02:00
Federico Jaramillo Martinez c5af390ab9 fix(sessions): abort in-flight catalog refresh on dispose
`dispose()` only cleared timers, so a refresh already in flight kept its
provider fetch alive for the rest of the timeout budget and could delay
daemon shutdown, which is exactly when sessiond disposes the refresher.
A refresher-lifetime AbortController is now combined with the per-run
timeout via `AbortSignal.any`, and `dispose()` aborts it. A
dispose-triggered abort logs as expected shutdown info rather than a
timeout warning or an error, whether the runtime resolves as aborted or
rejects.

`start()` is now idempotent: a second call previously overwrote both
timer handles and leaked the first pair, which kept firing.

Also replaces the `then().catch()` bookkeeping chain in `queueRefresh()`
with an awaited private `runCycle()`, keeping the coalescing, retry, and
dispose semantics unchanged.
2026-07-25 12:56:31 +02:00
Federico Jaramillo Martinez 3d3538c76b fix(sessions): size catalog refresh cadence, force, and retries
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()`.
2026-07-25 12:48:15 +02:00
Federico Jaramillo Martinez acda1cc0be fix(sessions): honor offline settings in background catalog refresher
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.
2026-07-25 12:40:33 +02:00
Federico Jaramillo Martinez ed9c2f65bb fix(sessions): move provider catalog network refreshes off request paths
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.
2026-07-24 23:44:35 +02:00
Federico Jaramillo Martinez 83e9014a8a Merge pull request #92 from lizhuoran1019/feat/git-tree-submodule-navigation
feat(git): add tree view and submodule file navigation
2026-07-24 22:09:02 +02:00
Federico Jaramillo Martinez 095adfe550 test(ui): add WorkspaceGitPanel component-boundary tests 2026-07-24 12:30:56 +02:00
Federico Jaramillo Martinez 6db065e984 refactor(ui): dedupe git file helpers and memoize git view model
- 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.
2026-07-24 12:09:55 +02:00
Federico Jaramillo Martinez 10ee3daa0c fix(ui): keep git view toggle stationary when expand-all appears
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.
2026-07-24 12:06:33 +02:00
Federico Jaramillo Martinez 65a20b59fe fix(git): render newly staged submodule pointer as new → <sha>
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).
2026-07-24 11:56:54 +02:00
Federico Jaramillo Martinez 842160f964 perf(git): parallelize submodule expansion and skip impossible submodule lookups
- 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).
2026-07-24 11:55:56 +02:00
Federico Jaramillo Martinez 95102b8d78 fix(git): show staged submodule pointer moves and support spaced submodule paths
- 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.
2026-07-24 11:43:58 +02:00
Federico Jaramillo Martinez 63b85fc246 Merge branch 'pr-92-review' into test/pr-92 2026-07-24 10:21:29 +02:00
Federico Jaramillo Martinez 165928a543 docs(changeset): describe in-pane chevron in quiet-session-warnings
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).
2026-07-23 12:03:28 +02:00
Federico Jaramillo Martinez 8f6688e76e fix(ui): restore in-pane minimise chevron for session warnings
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.
2026-07-23 12:03:28 +02:00
Federico Jaramillo Martinez 10f49f349c Merge pull request #91 from jmfederico/agent/global-provider-policy
fix: freeze extension providers to the global startup baseline
2026-07-23 12:01:01 +02:00
Federico Jaramillo Martinez e8d418a71a docs: distinguish Pi extensions from PI WEB plugins 2026-07-22 23:53:30 +02:00
Federico Jaramillo Martinez ced3261651 test(sessions): make unread checks portable 2026-07-22 23:36:28 +02:00
Federico Jaramillo Martinez e70b3d6bb9 fix(sessions): keep ignored provider mutations inert 2026-07-22 20:58:56 +02:00
Federico Jaramillo Martinez ee8d9e5359 docs: document immutable provider bootstrap 2026-07-22 20:46:41 +02:00
Federico Jaramillo Martinez 66f0ea44ce test(sessions): cover immutable provider baseline 2026-07-22 20:39:43 +02:00
Federico Jaramillo Martinez 04d8134ba4 fix(sessions): freeze providers after global bootstrap 2026-07-22 20:31:08 +02:00
Federico Jaramillo Martinez c2bf595999 feat(sessions): allow global-extension providers, require Pi 0.81
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).
2026-07-22 17:13:57 +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
lzr 8a5aaf93a1 feat(git): add list/tree toggle for changed files in the Git panel
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
2026-07-22 21:34:54 +08:00
Federico Jaramillo Martinez 242911331a docs: document global provider policy and extension registration rejection 2026-07-22 09:36:32 +02:00
Federico Jaramillo Martinez fb4ceb5d04 test(sessions): cover the global provider policy
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.
2026-07-22 09:30:40 +02:00
Federico Jaramillo Martinez 20d424c48f feat(sessions): reject extension-scoped provider registrations
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).
2026-07-22 08:57:05 +02:00
Federico Jaramillo Martinez 24a3d3611e fix(ui): compact session tree indentation and allow default navigation after invalid custom input
- 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.
2026-07-21 11:11:10 +02:00
Federico Jaramillo Martinez a13778c97b fix(ui): keep warning toggle visible 2026-07-21 10:43:13 +02:00
Federico Jaramillo Martinez 115d74e79a feat(sessions): persist shared unread state 2026-07-20 19:36:16 +02:00
Federico Jaramillo Martinez a20a8c8c09 fix: preserve minimised warnings across session navigation 2026-07-20 19:35:45 +02:00