Commit Graph
270 Commits
Author SHA1 Message Date
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 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 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 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 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 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
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 115d74e79a feat(sessions): persist shared unread state 2026-07-20 19:36:16 +02:00
Federico Jaramillo Martinez a884773357 fix(sessions): provide plain-text extension theme 2026-07-20 14:01:47 +02:00
Federico Jaramillo Martinez b48b147b5b fix(install): allow node-pty scripts with npm 12 2026-07-20 11:57:39 +02:00
Federico Jaramillo Martinez 4ca4a1d096 feat: add hierarchical session tree navigator 2026-07-20 10:40:17 +02:00
Federico Jaramillo Martinez a77c83b309 fix(sessions): clarify delegation tool guidance 2026-07-20 00:48:08 +02:00
Federico Jaramillo Martinez 793b492153 fix(sessions): canonicalize notification route cwd 2026-07-19 02:24:38 +02:00
Federico Jaramillo Martinez 71fd091e0e fix(sessions): preserve notification inbox authority 2026-07-19 02:17:59 +02:00
Federico Jaramillo Martinez 6e09df8329 feat(sessions): add daemon notification inbox protocol 2026-07-19 01:28:45 +02:00
Federico Jaramillo Martinez d29fbb98b2 Merge pull request #70 from jmfederico/agent/issue-67
fix(terminals): load interactive login profiles
2026-07-18 21:46:57 +02:00
Federico Jaramillo Martinez d72a0012c7 fix(sessions): surface extension command notifications 2026-07-18 20:49:41 +02:00
Federico Jaramillo Martinez 2b1507ba35 fix(terminals): load interactive login profiles 2026-07-18 20:47:07 +02:00
Federico Jaramillo Martinez d2e2d512cd Merge pull request #64 from jmfederico/fix/issue-62-authstorage
fix: migrate auth/model plumbing to ModelRuntime (fixes #62)
2026-07-18 18:20:03 +02:00
Federico Jaramillo Martinez 65350fd1b5 fix(realtime): terminate failed sockets 2026-07-18 08:33:51 +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 3c3741b565 fix(auth): reconcile committed OAuth cancellation 2026-07-18 07:48:27 +02:00
Federico Jaramillo Martinez aca168a311 fix(runtime): align supported requirements and release hygiene 2026-07-18 00:10:47 +02:00
Federico Jaramillo Martinez 1f13bab58a fix(realtime): isolate notification failures 2026-07-17 23:58:56 +02:00
Federico Jaramillo Martinez 45f068ef05 fix(runtime): reload model config at service boundaries 2026-07-17 23:48:32 +02:00
Federico Jaramillo Martinez 3a208e648e fix(auth): preserve OAuth interaction semantics 2026-07-17 23:34:58 +02:00
Federico Jaramillo Martinez a39cf49f3a fix(auth): prevent API key reuse across login prompts 2026-07-17 23:18:58 +02:00
Federico Jaramillo Martinez 68d8bd1788 test: stabilize fileTreeService truncation test on slow filesystems 2026-07-17 23:03:13 +02:00
Federico Jaramillo Martinez 910c6b5ae0 refactor: derive auth-provider login options from SDK data instead of hardcoded provider lists 2026-07-17 22:57:33 +02:00
Federico Jaramillo Martinez 28f328f475 refactor(test): use deterministic waits instead of sleeps in spawnSubsession test 2026-07-17 22:10:25 +02:00
Federico Jaramillo Martinez d0cc55cce3 Migrate test doubles + testSupport to ModelRuntime/InMemoryCredentialStore (slice 5)
Replace AuthStorage.inMemory / ModelRegistry.create|inMemory across all test
and support code with the pi-ai InMemoryCredentialStore + async
ModelRuntime.create({ credentials }). Add shared test-runtime seams
(createTestModelRuntime, testModelRuntime, seedCredential) in testSupport.ts
and thread modelRuntime into fakeRuntime and every PiSessionService
construction (now a required dependency). Rework the anthropic subscription
warning tests onto a temp auth.json seam read via readStoredCredential, and
the auth-loss warning test onto a live credential store + runtime refresh.
Make getLoginProviderOptions synchronous and fix associated await/lint sites.

npm run verify green (typecheck + lint + knip + 1390 tests).
2026-07-17 21:56:53 +02:00
Federico Jaramillo Martinez 0c04c9b19c refactor(test): use vi.waitFor instead of sleeps in promptQueue test 2026-07-17 21:54:29 +02:00
Federico Jaramillo Martinez 4ccd4f81fc Migrate piSessionService to ModelRuntime (slice 4)
Pass modelRuntime to createAgentSessionServices instead of authStorage +
modelRegistry; carry ModelRuntime on PiAgentSession; make modelRuntime a
required PiSessionService dependency (sessiond already injects auth.runtime).
Switch anthropicSubscriptionWarning to readStoredCredential, and rederive
model reads (availableModels/setModel/syncCurrentModelAuthWarning) via the
runtime (getAvailableSnapshot/getModel/hasConfiguredAuth). sessiond.ts and
piSessionService.ts now typecheck; only slice-5 test/support files remain.
2026-07-17 21:32:02 +02:00
Federico Jaramillo Martinez 1c3d6db109 Migrate oauthLoginFlowService to pi-ai AuthInteraction contract
Reimplement OAuthLoginFlowService against the pi-ai AuthInteraction
({ signal?, prompt(AuthPrompt), notify(AuthEvent) }) contract instead of
the removed OAuthLoginCallbacks shape, and drive login via
runtime.login(providerId, "oauth", interaction). start() now takes a
ModelRuntime instead of authStorage, resolving the authService.ts line-83
error. AuthPrompt text/secret/manual_code/select map onto the existing
web-UI prompt/select flow state; auth_url/device_code map onto the auth
field; info/progress append to progress. Per-prompt AuthPrompt.signal now
cancels just that pending request without ending the flow.

Slice 3 of the issue-62 authStorage migration relay.
2026-07-17 21:09:39 +02:00
Federico Jaramillo Martinez d09d7cc1ff Migrate authProviderOptions to ModelRuntime API
Rederive login/logout provider options from runtime.getProviders() +
listCredentials() + getProviderAuthStatus() instead of the removed
authStorage.getOAuthProviders()/list()/get() + getAll()/
getProviderDisplayName() surface (Pi 0.80.8+).

- Replace the AuthProviderModelRegistry structural interface with a
  runtime-shaped AuthProviderRuntime (getProviders/listCredentials/
  getProviderAuthStatus); a real ModelRuntime satisfies it.
- Make getLoginProviderOptions/getLogoutProviderOptions async to match
  the await call sites already in authService.ts.
- OAuth-capable providers = auth.oauth present; api-key providers =
  auth.apiKey present, preserving OAUTH_ONLY_PROVIDERS /
  isApiKeyLoginProvider logic. Display names from Provider.name.
- Update the test double to the new runtime shape.

Slice 2 of the authStorage migration relay. tsc: 31 -> 28 errors
(remaining are cross-slice: slices 3/4/5).
2026-07-17 20:58:46 +02:00
Federico Jaramillo Martinez e37148c193 Migrate authService to async ModelRuntime API (slice 1)
Move AuthService off the removed AuthStorage / ModelRegistry.create surface
onto the async ModelRuntime API:

- AuthService.create({ agentDir | runtime }) async factory wrapping
  ModelRuntime.create({ authPath, modelsPath }); createModelRuntimeForAgentDir
  replaces createModelRegistryForAgentDir.
- saveApiKey -> runtime.login(providerId, "api_key", nonInteractive) so the
  key is persisted through the runtime credential store.
- logoutProvider -> runtime.logout; refreshAuthState -> await runtime.refresh().
- startOAuthLogin now passes the runtime into OAuthLoginFlowService.start.
- authProviders/requireOAuthLoginProvider became async around getLogin/Logout
  provider options.
- sessiond.ts: async createRuntime, AuthService.create, pass modelRuntime to
  PiSessionService; sessionDaemonStartup awaits createRuntime.

Cross-slice: authProviderOptions (2), oauthLoginFlowService (3), and
piSessionService (4) still expose the old ModelRegistry shape, so the tree does
not fully typecheck yet. Session-daemon path changed -> manual sessiond restart
needed once the migration lands.
2026-07-17 20:53:42 +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