Archived
fix(runtime): align supported requirements and release hygiene
This commit is contained in:
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@jmfederico/pi-web": patch
|
||||
---
|
||||
|
||||
Offer API-key and OAuth login options for every provider the agent backend supports each method for, instead of a curated hardcoded list. Providers that support both methods (such as Anthropic and GitHub Copilot) now surface both an API-key and an OAuth login option, driven purely by what the backend reports.
|
||||
@@ -2,4 +2,4 @@
|
||||
"@jmfederico/pi-web": patch
|
||||
---
|
||||
|
||||
Fix the session daemon crashing on startup with Pi (`@earendil-works/pi-coding-agent`) 0.80.8 and newer. Pi removed the `AuthStorage` API in 0.80.8, which caused Pi Web to fail at module load. Authentication, OAuth login, API-key save/logout, provider listing, and the Anthropic subscription warning now run on Pi's new `ModelRuntime` credential APIs. Pi Web now requires Pi `>=0.80.8`.
|
||||
Restore session-daemon startup and authentication on supported Pi `>=0.80.8 <0.81` releases by migrating model and credential handling to `ModelRuntime`. Login options now follow each provider's interactive API-key and OAuth capabilities, OAuth prompts retain their input, selection, and device-code semantics, and unsupported multi-step API-key setup fails safely instead of storing malformed credentials. PI WEB now requires Node.js `>=22.19.0`.
|
||||
|
||||
@@ -1,391 +0,0 @@
|
||||
# Assessment — Issue #62: `AuthStorage` export removed in Pi 0.80.8
|
||||
|
||||
## 1. Summary
|
||||
|
||||
Pi Web's session daemon crashes at ESM module initialization after
|
||||
`@earendil-works/pi-coding-agent` is resolved at **0.80.8 or later**:
|
||||
|
||||
```
|
||||
SyntaxError: The requested module '@earendil-works/pi-coding-agent'
|
||||
does not provide an export named 'AuthStorage'
|
||||
```
|
||||
|
||||
The crash is a hard, load-time failure (a static `import { AuthStorage } ...`
|
||||
that no longer resolves), so Pi Web is completely unusable with any Pi in the
|
||||
0.80.8+ line. The permissive peer/dev range `>=0.80.0 <1` lets npm resolve the
|
||||
incompatible release.
|
||||
|
||||
**Root cause:** Pi 0.80.8 is a **major architectural refactor** of model/auth
|
||||
plumbing ("Unified model runtime and provider authentication"), explicitly
|
||||
listed under **Breaking Changes** in the upstream CHANGELOG. `AuthStorage` (and
|
||||
its storage backends `FileAuthStorageBackend`, `InMemoryAuthStorageBackend`,
|
||||
and the credential type exports) were **removed from the package's public
|
||||
exports**. The class still exists internally but is no longer exported; the new
|
||||
public surface is `ModelRuntime` (async) plus a synchronous compatibility
|
||||
`ModelRegistry` facade with a different shape, and `readStoredCredential()` for
|
||||
one-off reads.
|
||||
|
||||
**Recommendation (see §5):** Do **not** attempt a dual-API compatibility shim.
|
||||
The change is a deep semantic refactor (sync → async, credential store contract
|
||||
change, removal of `authStorage` from services, `ModelRegistry` constructor and
|
||||
method-signature changes). A clean migration to the `ModelRuntime` API,
|
||||
combined with pinning the supported Pi range to `>=0.80.8 <0.81`, is the correct
|
||||
fix and warrants a Pi Web version bump via a changeset.
|
||||
|
||||
---
|
||||
|
||||
## 2. Where and how `AuthStorage` / `ModelRegistry` are used in `src/`
|
||||
|
||||
All usage is under `src/server/sessions/`. Production files (3) and test/support
|
||||
files (5).
|
||||
|
||||
### Production code
|
||||
|
||||
**`authService.ts`** — the central auth wiring.
|
||||
- `import { AuthStorage, ModelRegistry } from "@earendil-works/pi-coding-agent"`.
|
||||
- `type ModelRegistryInstance = ReturnType<typeof ModelRegistry.create>`.
|
||||
- `createModelRegistryForAgentDir(agentDir)`:
|
||||
`AuthStorage.create(join(agentDir, "auth.json"))` then
|
||||
`ModelRegistry.create(authStorage, join(agentDir, "models.json"))`.
|
||||
- Constructor fallback: `ModelRegistry.create(AuthStorage.create())`.
|
||||
- Reads/writes credentials through `this.modelRegistry.authStorage`:
|
||||
- `.set(providerId, { type: "api_key", key })` (saveApiKey)
|
||||
- `.logout(providerId)` (logoutProvider)
|
||||
- `.reload()` (refreshAuthState)
|
||||
- passes `this.modelRegistry.authStorage` into the OAuth login flow.
|
||||
- Uses `this.modelRegistry.refresh()` (currently synchronous `void`).
|
||||
|
||||
**`oauthLoginFlowService.ts`** — OAuth login orchestration for the web UI.
|
||||
- `import type { AuthStorage } from "@earendil-works/pi-coding-agent"`.
|
||||
- `type OAuthLoginStorage = Pick<AuthStorage, "login">`.
|
||||
- Calls `authStorage.login(providerId, callbacks)` where `callbacks` is the
|
||||
old `OAuthLoginCallbacks` shape: `signal`, `onAuth`, `onDeviceCode`,
|
||||
`onPrompt`, `onManualCodeInput`, `onSelect`, `onProgress`.
|
||||
|
||||
**`piSessionService.ts`** — session runtime factory + warnings.
|
||||
- `import { AuthStorage, ..., ModelRegistry, ... }`.
|
||||
- `type ModelRegistryInstance = ReturnType<typeof ModelRegistry.create>`.
|
||||
- `createDefaultRuntimeFactory(authStorage: AuthStorage, modelRegistry, ...)`
|
||||
calls `createAgentSessionServices({ cwd, agentDir, authStorage, modelRegistry })`.
|
||||
- Uses `createModelRegistryForAgentDir` fallback; passes
|
||||
`this.modelRegistry.authStorage` and `this.modelRegistry` into the runtime
|
||||
factory (around lines 605–612).
|
||||
- `anthropicSubscriptionWarning()` reads
|
||||
`session.modelRegistry.authStorage.get("anthropic")` and inspects
|
||||
`credential.type` / `credential.key`.
|
||||
- `PiAgentSession.modelRegistry: ModelRegistryInstance` is part of the internal
|
||||
session interface.
|
||||
|
||||
**`authProviderOptions.ts`** — provider enumeration (no direct SDK import; uses a
|
||||
structural `AuthProviderModelRegistry` interface). Depends on the current
|
||||
`ModelRegistry`/`AuthStorage` shape:
|
||||
- `modelRegistry.authStorage.getOAuthProviders()` → `{ id, name }[]`
|
||||
- `modelRegistry.authStorage.list()` → `string[]`
|
||||
- `modelRegistry.authStorage.get(provider)` → `{ type } | undefined`
|
||||
- `modelRegistry.getAll()` → `{ provider }[]`
|
||||
- `modelRegistry.getProviderDisplayName(provider)`
|
||||
- `modelRegistry.getProviderAuthStatus(provider)`
|
||||
|
||||
### Test / support code
|
||||
- `authService.test.ts` — `AuthStorage.inMemory(...)`, `ModelRegistry.create(...)`,
|
||||
asserts `startOptions.authStorage`.
|
||||
- `piSessionService.testSupport.ts` — `ModelRegistry.inMemory(AuthStorage.inMemory())`,
|
||||
`ModelRegistry.create(AuthStorage.inMemory())`.
|
||||
- `piSessionService.promptQueue.test.ts` — `AuthStorage.inMemory({ anthropic: {...} })`,
|
||||
`ModelRegistry.inMemory(authStorage)`.
|
||||
- `piSessionService.warnings.test.ts` — `AuthStorage.inMemory()`,
|
||||
`ModelRegistry.inMemory/create`, builds anthropic credentials via
|
||||
`authStorage.set(...)`.
|
||||
- `oauthLoginFlowService.test.ts` — `Pick<AuthStorage, "login">` fake.
|
||||
- `authProviderOptions.test.ts` — structural `AuthProviderModelRegistry` fake
|
||||
(no SDK import; must track whatever `authProviderOptions.ts` requires).
|
||||
|
||||
### Other pi-coding-agent imports (unaffected — still exported in 0.80.8)
|
||||
`DefaultPackageManager`, `SettingsManager` (piPackageService, piWebPluginService,
|
||||
piWebStatus), `createAgentSessionServices`, `createAgentSessionFromServices`,
|
||||
`createAgentSessionRuntime`, `AgentSessionRuntimeDiagnostic`, `ResourceDiagnostic`.
|
||||
These remain present; only the auth/model-registry construction path is broken.
|
||||
|
||||
---
|
||||
|
||||
## 3. What Pi 0.80.8 actually changed (verified against real tarballs)
|
||||
|
||||
Method: downloaded and extracted the real npm tarballs for
|
||||
`@earendil-works/pi-coding-agent` 0.80.7, 0.80.8, 0.80.10 and
|
||||
`@earendil-works/pi-ai` 0.80.7, 0.80.8 (into `/srv/dev/pi-inspect`) and diffed
|
||||
the `.d.ts` surface. (Local `node_modules` was not installed in this worktree;
|
||||
the last globally installed copy elsewhere is 0.80.6.)
|
||||
|
||||
### 3.1 Public export diff — `pi-coding-agent` index.d.ts (0.80.7 → 0.80.8)
|
||||
|
||||
Removed:
|
||||
```
|
||||
export { type ApiKeyCredential, type AuthCredential, type AuthStatus,
|
||||
AuthStorage, type AuthStorageBackend, FileAuthStorageBackend,
|
||||
InMemoryAuthStorageBackend, type OAuthCredential } from "./core/auth-storage.ts";
|
||||
```
|
||||
Added:
|
||||
```
|
||||
export { readStoredCredential } from "./core/auth-storage.ts";
|
||||
export { type CreateModelRuntimeOptions, ModelRuntime,
|
||||
type ModelRuntimeAuthOverrides } from "./core/model-runtime.ts";
|
||||
```
|
||||
`ModelRegistry` is still exported, but its class shape changed (see §3.3).
|
||||
0.80.10 (current `latest`) is **byte-identical** to 0.80.8 for `index.d.ts`,
|
||||
`auth-storage.d.ts`, and `model-runtime.d.ts` — the new surface is stable.
|
||||
|
||||
### 3.2 Upstream CHANGELOG (0.80.8) — Breaking Changes (verbatim highlights)
|
||||
|
||||
- "Replaced the SDK's `CreateAgentSessionOptions.authStorage` and
|
||||
`modelRegistry` options with the async `modelRuntime` option. `AuthStorage`
|
||||
and its storage backends are no longer exported; use `ModelRuntime` (or a
|
||||
custom pi-ai `CredentialStore`), or `readStoredCredential()` for one-off
|
||||
reads of auth.json."
|
||||
- "Replaced SDK request-auth assembly through
|
||||
`ModelRegistry.getApiKeyAndHeaders()` with `ModelRuntime.getAuth()`."
|
||||
- "Changed extension-facing `ModelRegistry.refresh()` from synchronous `void`
|
||||
to `Promise<void>` because `models.json` loading is asynchronous. Extensions
|
||||
must await it before making synchronous registry reads."
|
||||
- "Moved canonical dynamic catalog refresh to async `ModelRuntime.refresh()`."
|
||||
|
||||
### 3.3 The new API shape
|
||||
|
||||
**`ModelRuntime`** (`core/model-runtime.d.ts`, new) — the canonical async facade:
|
||||
- `static create(options?: CreateModelRuntimeOptions): Promise<ModelRuntime>`
|
||||
where options include `credentials?: CredentialStore`, `authPath?`,
|
||||
`modelsPath?`, `modelsStore?`, `allowModelNetwork?`, etc.
|
||||
- Provider/model reads: `getProviders()`, `getProvider(id)`, `getModels()`,
|
||||
`getModel()`, `getAvailable()` (async) / `getAvailableSnapshot()` (sync).
|
||||
- Auth: `getAuth(providerId|model, overrides?)`, `checkAuth(providerId)`,
|
||||
`hasConfiguredAuth(providerId)`, `isUsingOAuth(providerId)`,
|
||||
`getProviderAuthStatus(providerId)`, `listCredentials()`,
|
||||
`setRuntimeApiKey`, `removeRuntimeApiKey`.
|
||||
- Login/logout: `login(providerId, type, interaction): Promise<Credential>`,
|
||||
`logout(providerId): Promise<void>`.
|
||||
- `refresh(): Promise<...>`, `registerProvider`/`unregisterProvider`.
|
||||
- Implements pi-ai `Models`.
|
||||
|
||||
**`ModelRegistry`** (`core/model-registry.d.ts`, changed) — now a thin sync
|
||||
compatibility facade **for extensions**, constructed from a `ModelRuntime`:
|
||||
- `constructor(runtime: ModelRuntime)` — **no more `ModelRegistry.create(authStorage, ...)`
|
||||
and no more `ModelRegistry.inMemory(...)`**.
|
||||
- **No `authStorage` property.** (This breaks `authProviderOptions.ts`,
|
||||
`authService.ts`, and `anthropicSubscriptionWarning`.)
|
||||
- `refresh(): Promise<void>` (was sync `void`).
|
||||
- Keeps `getAll`, `getAvailable`, `find`, `getProviderAuthStatus`,
|
||||
`getProviderDisplayName`, `getApiKeyForProvider`, `isUsingOAuth`,
|
||||
`hasConfiguredAuth`, `getApiKeyAndHeaders`, `registerProvider`, etc.
|
||||
- **Dropped:** the whole `authStorage`-centric credential API
|
||||
(`get/set/list/logout/reload/getOAuthProviders`).
|
||||
|
||||
**`AuthStorage`** (`core/auth-storage.d.ts`, still exists internally, NOT
|
||||
exported): now `implements CredentialStore` with an entirely different,
|
||||
**async** method set — `read()`, `modify()`, `delete()`, `list()` returning
|
||||
`Promise`s of pi-ai `Credential`/`CredentialInfo`. The old
|
||||
`get/set/remove/has/login/logout/getApiKey/getOAuthProviders/setRuntimeApiKey`
|
||||
synchronous methods are gone. `static create/inMemory/fromStorage` remain but
|
||||
the class is unexported.
|
||||
|
||||
**`readStoredCredential(providerId, authPath?)`** — new synchronous one-off read
|
||||
returning a pi-ai `Credential | undefined` (`{ type: "api_key", key?, env? }` or
|
||||
`{ type: "oauth", ... }`). Useful for `anthropicSubscriptionWarning`.
|
||||
|
||||
**pi-ai 0.80.8 auth model** (`@earendil-works/pi-ai`, `auth/types.d.ts`,
|
||||
`auth/credential-store.d.ts`):
|
||||
- `CredentialStore` interface: `read`, `list`, `modify`, `delete` — all async.
|
||||
- `Credential = ApiKeyCredential | OAuthCredential`; `CredentialInfo`.
|
||||
- `InMemoryCredentialStore` class exported — the test seam that replaces
|
||||
`AuthStorage.inMemory(...)`.
|
||||
- `AuthInteraction` interface replaces the old `OAuthLoginCallbacks`:
|
||||
`{ signal?, prompt(prompt: AuthPrompt): Promise<string>, notify(event: AuthEvent): void }`.
|
||||
`AuthPrompt` is a discriminated union (`text`/`secret`/`select`/`manual_code`);
|
||||
`AuthEvent` is `info`/`auth_url`/`device_code`/`progress`. This is a **complete
|
||||
reshaping** of the OAuth login callback contract used by
|
||||
`oauthLoginFlowService.ts`.
|
||||
- `login(providerId, type, interaction)` now lives on `ModelRuntime`, not on a
|
||||
credential store, and returns a `Credential`.
|
||||
- `Provider` objects (`getProviders()`) carry `{ id, name, auth: { apiKey?, oauth? } }`
|
||||
— this is the new source of truth for enumerating login providers, replacing
|
||||
`authStorage.getOAuthProviders()`.
|
||||
|
||||
### 3.4 Session services wiring change
|
||||
|
||||
`createAgentSessionServices` options and `AgentSessionServices`:
|
||||
- 0.80.7: `{ cwd, agentDir?, authStorage?, settingsManager?, modelRegistry?, ... }`
|
||||
→ services expose `authStorage` + `modelRegistry`.
|
||||
- 0.80.8: `{ cwd, agentDir?, settingsManager?, modelRuntime?, ... }`
|
||||
→ services expose `modelRuntime` (no `authStorage`, no `modelRegistry`).
|
||||
|
||||
So `piSessionService.ts`'s `createDefaultRuntimeFactory` must pass `modelRuntime`
|
||||
instead of `authStorage` + `modelRegistry`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Backwards-compatibility analysis (0.80.0–0.80.7 vs 0.80.8+)
|
||||
|
||||
A shim would need to bridge, simultaneously:
|
||||
|
||||
1. **Construction:** `ModelRegistry.create(authStorage, modelsPath)` /
|
||||
`ModelRegistry.inMemory(authStorage)` (old) vs
|
||||
`await ModelRuntime.create({ credentials, authPath, modelsPath })` then
|
||||
`new ModelRegistry(runtime)` (new). Old is sync; new is async. This alone
|
||||
forces `AuthService` / `PiSessionService` construction to become async or to
|
||||
pre-resolve a runtime, changing call sites either way.
|
||||
2. **Credential access:** synchronous `authStorage.get/set/list/logout/reload/
|
||||
getOAuthProviders` (old) vs async `CredentialStore.read/modify/delete/list`
|
||||
+ `ModelRuntime.getProviders()/login/logout/getProviderAuthStatus` (new).
|
||||
Sync→async cannot be shimmed transparently.
|
||||
3. **OAuth login:** `authStorage.login(providerId, OAuthLoginCallbacks)` (old,
|
||||
rich callback object) vs `modelRuntime.login(providerId, type,
|
||||
AuthInteraction)` (new, `prompt`/`notify` contract). The
|
||||
`oauthLoginFlowService` maps SDK callbacks onto web-UI flow state; the two
|
||||
callback contracts are structurally different and would each need a distinct
|
||||
adapter.
|
||||
4. **`refresh()`** sync vs async.
|
||||
5. **Provider enumeration** (`authProviderOptions.ts`) built on
|
||||
`authStorage.getOAuthProviders()/list()/get()` — none of which exist in the
|
||||
new surface; must be rederived from `getProviders()` + `listCredentials()`.
|
||||
|
||||
A dual shim would therefore reimplement two full auth stacks behind a lowest-
|
||||
common-denominator async interface, plus runtime detection of which export
|
||||
exists — high complexity, high risk, and permanently carrying dead code for the
|
||||
already-broken 0.80.0–0.80.7 line. This fails the "easy/clean" bar in the task.
|
||||
|
||||
**Conclusion:** backwards compatibility with 0.80.0–0.80.7 is **not easy** and
|
||||
not worth it. Pi Web should target the new (0.80.8+) API and drop support for
|
||||
0.80.0–0.80.7.
|
||||
|
||||
---
|
||||
|
||||
## 5. Recommendation
|
||||
|
||||
**Clean migration to the `ModelRuntime` API + range correction + version bump.**
|
||||
|
||||
Rationale:
|
||||
- 0.80.8 is an explicit upstream breaking change; the export removal is
|
||||
intentional and permanent (confirmed identical in 0.80.10 `latest`).
|
||||
- The old 0.80.0–0.80.7 surface and the new 0.80.8+ surface differ across
|
||||
construction, sync/async, credential access, OAuth login, and session
|
||||
services — there is no small adapter that spans both cleanly.
|
||||
- Pinning down to a still-working old version is a dead end: users installing
|
||||
Pi Web get whatever Pi they have, and `latest` is already 0.80.10.
|
||||
|
||||
### Concrete migration shape (to be executed by the relay, not now)
|
||||
|
||||
1. **`authService.ts`**: hold a `ModelRuntime` (created via
|
||||
`ModelRuntime.create({ authPath, modelsPath })`), optionally expose a
|
||||
`ModelRegistry` wrapper for extension-facing reads. Replace credential
|
||||
operations:
|
||||
- `saveApiKey` → `runtime` credential `modify(providerId, async () => ({ type:"api_key", key }))`
|
||||
(via the runtime's credential store / `setRuntimeApiKey` is for ephemeral;
|
||||
persistence uses the `CredentialStore.modify` path).
|
||||
- `logoutProvider` → `runtime.logout(providerId)`.
|
||||
- `startOAuthLogin` → `runtime.login(providerId, "oauth", interaction)`.
|
||||
- refresh → `await runtime.refresh()`.
|
||||
- Construction becomes async (factory function returning a Promise, or an
|
||||
`init()` step) — propagate to `sessiond.ts`.
|
||||
2. **`authProviderOptions.ts`**: rederive login/logout options from
|
||||
`runtime.getProviders()` (auth.apiKey / auth.oauth presence + names) and
|
||||
`runtime.listCredentials()` / `getProviderAuthStatus()`. Update the
|
||||
structural `AuthProviderModelRegistry`/`AuthProviderRuntime` interface and
|
||||
its test double.
|
||||
3. **`oauthLoginFlowService.ts`**: reimplement against `AuthInteraction`
|
||||
(`prompt(AuthPrompt)` + `notify(AuthEvent)`) instead of `OAuthLoginCallbacks`.
|
||||
Map `AuthPrompt` kinds (`text`/`secret`/`manual_code`/`select`) to the web
|
||||
UI prompt/select shapes, and `AuthEvent` (`auth_url`/`device_code`/`progress`)
|
||||
to the existing flow-state fields. This is the largest single slice.
|
||||
4. **`piSessionService.ts`**:
|
||||
- `createDefaultRuntimeFactory` passes `modelRuntime` to
|
||||
`createAgentSessionServices` instead of `authStorage` + `modelRegistry`.
|
||||
- `PiAgentSession` internal type: carry `modelRuntime` (or an adapted
|
||||
registry) instead of the old `modelRegistry.authStorage`.
|
||||
- `anthropicSubscriptionWarning`: replace
|
||||
`modelRegistry.authStorage.get("anthropic")` with
|
||||
`readStoredCredential("anthropic", authPath)` (sync, no `authStorage`
|
||||
needed) — cleanest fit for this synchronous check.
|
||||
5. **`sessiond.ts`**: adapt to async auth construction (create the runtime,
|
||||
`await` init, then pass into `PiSessionService`). **This is session-daemon
|
||||
code → requires a manual `pi-web-web-sessiond.service` restart after the fix
|
||||
lands.**
|
||||
6. **Tests / testSupport**: replace `AuthStorage.inMemory(...)` with pi-ai
|
||||
`InMemoryCredentialStore` (+ `await ModelRuntime.create({ credentials })`),
|
||||
and `ModelRegistry.create/inMemory(...)` accordingly. Update
|
||||
`authService.test.ts`, `piSessionService.testSupport.ts`,
|
||||
`piSessionService.promptQueue.test.ts`, `piSessionService.warnings.test.ts`,
|
||||
`oauthLoginFlowService.test.ts`, `authProviderOptions.test.ts`. Follow the
|
||||
testing-guide skill (esp. async construction, no over-mocking of SDK).
|
||||
|
||||
### Dependency range correction (§6)
|
||||
|
||||
- Change the three `@earendil-works/*` **peerDependencies** from
|
||||
`>=0.80.0 <1` to a range that excludes the unsupported line, e.g.
|
||||
`>=0.80.8 <0.81` (matching the current published minor). Keep the three
|
||||
`devDependencies` on a matching `^0.80.8` (or exact `0.80.8`/`0.80.10`).
|
||||
- `@earendil-works/pi-agent-core` and `@earendil-works/pi-ai` are siblings
|
||||
released in lockstep with `pi-coding-agent` (coding-agent depends on
|
||||
`^0.80.x` of both); correct all three ranges together.
|
||||
- Rationale for the upper bound `<0.81`: the auth refactor shows this line makes
|
||||
breaking changes within `0.80.x` patch releases, so a permissive `<1` is
|
||||
unsafe. Pin to the known-good minor window and widen deliberately after
|
||||
testing new releases.
|
||||
|
||||
### Release / changeset
|
||||
|
||||
- Add a **patch** (or minor, maintainer's call) `.changeset/*.md` for
|
||||
`@jmfederico/pi-web` describing the user-visible fix: "Fix session daemon
|
||||
crash with Pi 0.80.8+ by migrating to the new `ModelRuntime` API; require Pi
|
||||
`>=0.80.8`." Do **not** edit `CHANGELOG.md` directly (Changesets generates it).
|
||||
- Actual npm publish is out of scope for the fix branch; the release skill
|
||||
(`npm-release-via-github-actions`) is only referenced so the changeset is
|
||||
release-ready.
|
||||
|
||||
---
|
||||
|
||||
## 6. Dependency range facts (current state)
|
||||
|
||||
`package.json`:
|
||||
```
|
||||
devDependencies:
|
||||
"@earendil-works/pi-agent-core": "^0.80.6",
|
||||
"@earendil-works/pi-ai": "^0.80.6",
|
||||
"@earendil-works/pi-coding-agent": "^0.80.6",
|
||||
peerDependencies:
|
||||
"@earendil-works/pi-agent-core": ">=0.80.0 <1",
|
||||
"@earendil-works/pi-ai": ">=0.80.0 <1",
|
||||
"@earendil-works/pi-coding-agent": ">=0.80.0 <1",
|
||||
```
|
||||
No `dependencies`/`optionalDependencies` entries for these packages. The
|
||||
permissive peer range `>=0.80.0 <1` is what lets consumers' npm resolve the
|
||||
breaking 0.80.8/0.80.9/0.80.10 against a Pi Web build that expects the old
|
||||
export.
|
||||
|
||||
Published versions (npm): 0.79.10, 0.80.1, 0.80.2, 0.80.3, 0.80.5, 0.80.6,
|
||||
0.80.7, 0.80.8, 0.80.9, 0.80.10. `latest` = 0.80.10. The removal landed in
|
||||
0.80.8 and persists through 0.80.10.
|
||||
|
||||
---
|
||||
|
||||
## 7. Verification artifacts
|
||||
|
||||
- Extracted SDK tarballs for inspection: `/srv/dev/pi-inspect/` (v0.80.7,
|
||||
v0.80.8, v0.80.10 of pi-coding-agent; pi-ai0807, pi-ai0808). These are
|
||||
scratch/inspection only and outside the repo.
|
||||
- Key diffs reproduced in §3.1 (index exports), §3.3 (class shapes), §3.4
|
||||
(session services). 0.80.8 vs 0.80.10 `.d.ts` are identical for the affected
|
||||
files → the target API is stable.
|
||||
|
||||
## 8. Risks / call-outs for the fix
|
||||
|
||||
- **Session daemon restart required:** changes touch `sessiond.ts` and the
|
||||
session runtime path; a manual restart of the sessiond service is needed after
|
||||
the fix (per AGENTS.md).
|
||||
- **Async construction ripple:** moving from sync `AuthStorage/ModelRegistry`
|
||||
construction to `await ModelRuntime.create(...)` changes `AuthService` /
|
||||
`PiSessionService` init and their call sites; keep the async boundary
|
||||
explicit and injected (code-quality-architecture skill).
|
||||
- **OAuth flow contract change is the riskiest slice** — the web UI prompt/
|
||||
select/device-code mapping must be re-verified end to end.
|
||||
- **No local `node_modules`** in this worktree; the relay's first implementation
|
||||
leg must `npm install` (pin to 0.80.8+) before it can typecheck/test. Note the
|
||||
`/tmp` quota issue observed during assessment — install in the worktree, not
|
||||
`/tmp`.
|
||||
@@ -34,7 +34,7 @@ Your browser is the control surface. The work stays where it can keep running.
|
||||
|
||||
Requirements:
|
||||
|
||||
- Node.js 22 or newer
|
||||
- Node.js 22.19.0 or newer
|
||||
- npm
|
||||
- Pi Coding Agent configured for your user
|
||||
- git and the development tools your agents need
|
||||
|
||||
+2
-2
@@ -157,7 +157,7 @@
|
||||
<article id="doctor-fails" class="faq-item">
|
||||
<h2>What does <code>pi-web doctor</code> check?</h2>
|
||||
<p>
|
||||
It keeps two kinds of checks separate. General login-shell readiness covers Node 22+, npm, Pi, and optional
|
||||
It keeps two kinds of checks separate. General login-shell readiness covers Node 22.19.0 or newer, npm, Pi, and optional
|
||||
ripgrep. Native-service diagnostics validate only the exact prerequisites of the selected service plan in
|
||||
the real systemd user-manager or launchd <code>gui/<uid></code> context. Development installs follow their
|
||||
installed checkout plan; production checks are clearly labelled prospective when the installed executable
|
||||
@@ -181,7 +181,7 @@
|
||||
<ul>
|
||||
<li>Prefer version-manager shims when available; for <code>mise</code>, use shims or enable its shim setup rather than relying only on shell activation.</li>
|
||||
<li>Move any required version-manager initialization to your login shell file.</li>
|
||||
<li>Make sure <code>node --version</code> is at least <code>v22</code> from <code>bash -lc</code>, <code>zsh -lc</code>, or your detected shell.</li>
|
||||
<li>Make sure <code>node --version</code> is at least <code>v22.19.0</code> from <code>bash -lc</code>, <code>zsh -lc</code>, or your detected shell.</li>
|
||||
<li>Run <code>pi-web doctor</code> again after changing shell files.</li>
|
||||
</ul>
|
||||
</article>
|
||||
|
||||
+2
-2
@@ -38,7 +38,7 @@
|
||||
"downloadUrl": "https://www.npmjs.com/package/@jmfederico/pi-web",
|
||||
"codeRepository": "https://github.com/jmfederico/pi-web",
|
||||
"description": "PI WEB is a web UI for Pi Coding Agent that keeps persistent agent sessions running in real workspaces on your machine or server.",
|
||||
"softwareRequirements": "Node.js 22 or newer and Pi Coding Agent",
|
||||
"softwareRequirements": "Node.js 22.19.0 or newer and Pi Coding Agent",
|
||||
"license": "https://github.com/jmfederico/pi-web/blob/main/LICENSE"
|
||||
}
|
||||
</script>
|
||||
@@ -138,7 +138,7 @@
|
||||
# laptop, phone, tablet — same live sessions
|
||||
|
||||
<span class="prompt">$</span> pi-web doctor
|
||||
✓ caller login shell can find node >= 22
|
||||
✓ caller login shell can find node >= 22.19.0
|
||||
✓ native-service plan requirements pass in manager context
|
||||
✓ ready for persistent agent work</code></pre>
|
||||
</aside>
|
||||
|
||||
+1
-1
@@ -106,7 +106,7 @@
|
||||
<section id="requirements">
|
||||
<h2>Requirements</h2>
|
||||
<ul>
|
||||
<li><strong>Node.js 22 or newer</strong> and npm.</li>
|
||||
<li><strong>Node.js 22.19.0 or newer</strong> and npm.</li>
|
||||
<li><strong>Pi Coding Agent</strong> installed/configured so the <code>pi</code> command works for your user.</li>
|
||||
<li>A shell login environment that exposes Node, npm, Pi, git, and any tools your agents need.</li>
|
||||
<li>For the automatic installer: a supported per-user service manager.</li>
|
||||
|
||||
Generated
+1
-1
@@ -58,7 +58,7 @@
|
||||
"vitest": "^4.1.10"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22"
|
||||
"node": ">=22.19.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@earendil-works/pi-agent-core": ">=0.80.8 <0.81",
|
||||
|
||||
+1
-1
@@ -101,7 +101,7 @@
|
||||
"access": "public"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22"
|
||||
"node": ">=22.19.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
# Relay charter — issue-62-authstorage
|
||||
|
||||
## Relay identity
|
||||
- **Name:** `issue-62-authstorage`
|
||||
- **Root path:** `relays/issue-62-authstorage/` (in the repo, on branch
|
||||
`fix/issue-62-authstorage`, worktree `/srv/dev/pi-web-issue-62`)
|
||||
- **Packet files:** `charter.md`, `status.md`, `log.md` (this directory).
|
||||
|
||||
## Background (read once, do not re-derive)
|
||||
The full technical assessment lives at the worktree root:
|
||||
`ASSESSMENT-issue-62.md`. Read it once at the start of your leg for the API
|
||||
migration details; do not re-investigate the SDK from scratch. Short version:
|
||||
Pi `@earendil-works/pi-coding-agent` 0.80.8 removed the `AuthStorage` export and
|
||||
replaced the auth/model plumbing with an async `ModelRuntime` + pi-ai
|
||||
`CredentialStore` model. Pi Web imports `AuthStorage` statically and crashes at
|
||||
module load with any Pi 0.80.8+. The agreed fix is a **clean migration to the
|
||||
new `ModelRuntime` API** (no dual-version shim) plus dependency-range
|
||||
correction, tests, and a changeset. Rationale and the per-file migration shape
|
||||
are in `ASSESSMENT-issue-62.md` §5.
|
||||
|
||||
## Goal / finish line
|
||||
Pi Web builds, typechecks, lints, and passes its full test suite against Pi
|
||||
`@earendil-works/pi-coding-agent` **0.80.8+** (target the installed 0.80.8/0.80.10
|
||||
line), with:
|
||||
1. No remaining import or use of the removed `AuthStorage` export (and no
|
||||
reliance on `ModelRegistry.create(authStorage)` / `.inMemory(authStorage)` /
|
||||
`modelRegistry.authStorage`).
|
||||
2. Auth, OAuth login, API-key save/logout, provider enumeration, and the
|
||||
Anthropic subscription warning all working through the new `ModelRuntime` /
|
||||
`readStoredCredential` / pi-ai `CredentialStore` APIs.
|
||||
3. `package.json` peerDependencies and devDependencies for the three
|
||||
`@earendil-works/*` packages corrected so npm cannot resolve an unsupported
|
||||
release (target range `>=0.80.8 <0.81`; devDeps on a matching `^0.80.8`).
|
||||
4. A `.changeset/*.md` fragment describing the user-visible fix (no direct
|
||||
`CHANGELOG.md` edit).
|
||||
5. `npm run verify` (typecheck + lint + knip + test) passing.
|
||||
|
||||
Finish line reached = all of the above true and committed on the branch. **Do
|
||||
not open a PR** (out of scope for this relay).
|
||||
|
||||
## Sizing — one leg
|
||||
One leg = **one coherent slice** from the plan below that leaves the tree in a
|
||||
committed, describable state. Prefer the pre-broken-out slices in `status.md`.
|
||||
A leg does not have to leave `npm run verify` fully green (the migration is
|
||||
interdependent), but it MUST:
|
||||
- leave a clear, honest `status.md` describing what compiles/what doesn't yet,
|
||||
- commit its work with a clear message,
|
||||
- not expand scope beyond its slice ("just a bit more" is the main failure mode
|
||||
here — the auth surfaces are interconnected; resist rewriting everything in
|
||||
one leg).
|
||||
|
||||
If a slice turns out bigger than expected, split it and hand off mid-plan with
|
||||
an updated `status.md` — that is expected and fine.
|
||||
|
||||
## Suggested slice breakdown (task selection default)
|
||||
Follow `status.md`'s named next task. If none is named, pick the lowest-numbered
|
||||
incomplete slice here:
|
||||
|
||||
0. **Bootstrap:** `npm install` in the worktree pinning the three `@earendil-works/*`
|
||||
packages to 0.80.8+ (e.g. `npm i -D @earendil-works/[email protected]
|
||||
@earendil-works/[email protected] @earendil-works/[email protected]`), and
|
||||
correct the peerDependencies range to `>=0.80.8 <0.81`. Confirm the crash
|
||||
reproduces / the new exports resolve. Commit. (Install in the worktree, NOT
|
||||
`/tmp` — `/tmp` has a disk quota problem, see assessment §8.)
|
||||
1. **`authService.ts` core migration:** move to `ModelRuntime` (async
|
||||
construction via `ModelRuntime.create({ authPath, modelsPath })`), migrate
|
||||
saveApiKey/logout/refresh/credential access. Propagate async construction to
|
||||
`sessiond.ts`. (Session-daemon path — see restart note.)
|
||||
2. **`authProviderOptions.ts` migration:** rederive login/logout provider
|
||||
options from `runtime.getProviders()` + `listCredentials()` /
|
||||
`getProviderAuthStatus()`; update its structural interface + test double.
|
||||
3. **`oauthLoginFlowService.ts` migration:** reimplement against pi-ai
|
||||
`AuthInteraction` (`prompt`/`notify`) instead of `OAuthLoginCallbacks`; wire
|
||||
`runtime.login(providerId, "oauth", interaction)`. (Riskiest slice — verify
|
||||
prompt/select/device-code/auth_url mapping.)
|
||||
4. **`piSessionService.ts` migration:** pass `modelRuntime` to
|
||||
`createAgentSessionServices`; update `PiAgentSession` type; switch
|
||||
`anthropicSubscriptionWarning` to `readStoredCredential`.
|
||||
5. **Tests + testSupport:** migrate all test doubles to `InMemoryCredentialStore`
|
||||
+ `ModelRuntime.create`; get `npm run verify` green. Follow the testing-guide
|
||||
skill.
|
||||
6. **Changeset + final verify + cleanup:** add `.changeset/*.md`; run full
|
||||
`npm run verify`; remove scratch (`ASSESSMENT` stays, `/srv/dev/pi-inspect`
|
||||
is outside the repo). Confirm goal, hand off to a final confirmation/stop.
|
||||
|
||||
Slices may merge or split. 1–4 depend on 0. Slice 5 finalizes; slice 6 closes.
|
||||
|
||||
## Handover
|
||||
When handing off, `spawn_session` **once** with a prompt whose first line is:
|
||||
`Relay "issue-62-authstorage" leg <N> begins now.` followed by the standard
|
||||
Relay handoff body pointing at:
|
||||
- `relays/issue-62-authstorage/charter.md`
|
||||
- `relays/issue-62-authstorage/status.md`
|
||||
|
||||
Tell the next runner not to read `log.md` end-to-end. Make all work durable
|
||||
(update `status.md`, append `log.md`, commit) **before** spawning.
|
||||
|
||||
## Intervention signal — stop and get the human when:
|
||||
- The new SDK API does not actually provide an operation the migration needs
|
||||
(e.g. no viable credential persistence path for API-key save), i.e. the
|
||||
assessment's assumed mapping is wrong.
|
||||
- A slice would require changing the charter's goal or the agreed "clean
|
||||
migration, no shim" decision.
|
||||
- `npm install` / registry access fails and cannot be resolved in-leg.
|
||||
- Charter churn: if you find yourself needing to edit this charter to proceed,
|
||||
stop and involve the human instead.
|
||||
To raise it: set a clear `## BLOCKED` section at the top of `status.md`, append a
|
||||
`log.md` entry explaining the blocker and what decision is needed, do **not**
|
||||
spawn the next leg, and end your run.
|
||||
|
||||
## Reading discipline
|
||||
Read, in order: this `charter.md`, then `status.md`, then `ASSESSMENT-issue-62.md`
|
||||
(once), then only the specific `src/server/sessions/*` files your slice touches.
|
||||
Do **not** read `log.md` end-to-end — only targeted entries if `status.md` points
|
||||
you there. Do not re-extract SDK tarballs unless the assessment is contradicted
|
||||
by reality.
|
||||
|
||||
## Standing constraints (project conventions)
|
||||
- Session-daemon changes (`sessiond.ts`, session runtime / auth construction
|
||||
loaded by the daemon) require the human to **manually restart the sessiond
|
||||
service** to take effect. Call this out in `status.md`/handoff whenever a leg
|
||||
changes that path so the human knows a restart is pending.
|
||||
- Follow the skills: `code-quality-architecture` (DI, async boundaries,
|
||||
testable seams), `testing-guide` (test layers, no over-mocking), and
|
||||
`changeset-changelog` (changeset not CHANGELOG edit).
|
||||
- Keep changes scoped to the fix; do not opportunistically refactor unrelated
|
||||
code.
|
||||
@@ -1,397 +0,0 @@
|
||||
# Relay log — issue-62-authstorage
|
||||
|
||||
Append-only. One concise entry per leg. Do not read end-to-end for orientation;
|
||||
use `status.md`. Targeted lookups only.
|
||||
|
||||
---
|
||||
|
||||
## Leg 0 — Planning (assessment + relay packet)
|
||||
|
||||
**Did:**
|
||||
- Read issue #62 and confirmed the crash: static `import { AuthStorage }` fails
|
||||
at ESM load with Pi 0.80.8+.
|
||||
- Investigated all `AuthStorage`/`ModelRegistry` usage in `src/` (3 production
|
||||
files + 5 test/support files under `src/server/sessions/`; other
|
||||
pi-coding-agent imports unaffected).
|
||||
- Downloaded and diffed real npm tarballs (pi-coding-agent 0.80.7/0.80.8/0.80.10
|
||||
and pi-ai 0.80.7/0.80.8) into `/srv/dev/pi-inspect` (scratch, outside repo) to
|
||||
establish the exact new export surface: `AuthStorage` and its backends removed
|
||||
from exports; new `ModelRuntime` (async) + `readStoredCredential`; changed
|
||||
`ModelRegistry` (constructed from a runtime, `refresh()` now async, no
|
||||
`authStorage`); pi-ai `CredentialStore`/`InMemoryCredentialStore`/
|
||||
`AuthInteraction` model. Confirmed 0.80.8 and 0.80.10 `.d.ts` are identical
|
||||
for the affected files (stable target).
|
||||
- Wrote `ASSESSMENT-issue-62.md` (root).
|
||||
|
||||
**Decisions:**
|
||||
- **Clean migration to `ModelRuntime`, no dual-version compat shim.** Rationale:
|
||||
sync→async, credential-store contract change, OAuth callback contract change,
|
||||
and session-services option change span both surfaces with no small clean
|
||||
adapter; 0.80.0–0.80.7 is already broken/superseded (`latest` = 0.80.10).
|
||||
- **Dep range fix:** peerDeps `>=0.80.0 <1` → `>=0.80.8 <0.81` for all three
|
||||
`@earendil-works/*` packages; upper bound `<0.81` because this line ships
|
||||
breaking changes within `0.80.x`.
|
||||
- Relay packet placed under `relays/issue-62-authstorage/` (committed; not in
|
||||
`package.json` `files`, so not published; `.pi-web/` is gitignored so not used).
|
||||
|
||||
**Artifacts changed:** `ASSESSMENT-issue-62.md`;
|
||||
`relays/issue-62-authstorage/{charter,status,log}.md`.
|
||||
|
||||
**Status update:** last completed leg 0, next leg 1 = charter slice 0
|
||||
(Bootstrap: install Pi 0.80.8+, correct dep ranges).
|
||||
|
||||
**Blockers:** none. Noted `/tmp` disk-quota issue (install in worktree) and the
|
||||
pending sessiond restart for later daemon-path slices.
|
||||
|
||||
**Handoff:** Planning only — NOT auto-spawning the first implementation leg.
|
||||
Assessment + relay plan are laid out ready to be kicked off by the user.
|
||||
|
||||
---
|
||||
|
||||
## Leg 1 — Slice 0 Bootstrap (deps + range correction)
|
||||
|
||||
**Did:**
|
||||
- Corrected `package.json`: three `@earendil-works/*` devDependencies
|
||||
`^0.80.6` → `^0.80.8`; peerDependencies `>=0.80.0 <1` → `>=0.80.8 <0.81`.
|
||||
- `npm install` in the worktree. The default `/tmp`-based node-gyp build of
|
||||
`node-pty` failed with "Disk quota exceeded" (/tmp is a 5.8G tmpfs at ~81%).
|
||||
Re-ran with `TMPDIR="$PWD/.tmp-build" npm install`, which succeeded (618
|
||||
packages, 0 vulnerabilities). Removed `.tmp-build` after.
|
||||
- Confirmed installed versions: all three packages resolve to **0.80.10**
|
||||
(lockfile updated).
|
||||
- Verified the new export surface via a node ESM check: `ModelRuntime` ✓,
|
||||
`readStoredCredential` ✓, `ModelRegistry` ✓, `AuthStorage` absent ✓, pi-ai
|
||||
`InMemoryCredentialStore` ✓.
|
||||
- Ran `npx tsc --noEmit`: 24 errors, all in `src/server/sessions/` at the
|
||||
expected migration sites (removed `AuthStorage`, `ModelRegistry.create/inMemory`,
|
||||
`authStorage` on session-services options, `PiAgentSession.modelRegistry`).
|
||||
This matches assessment §2/§3 — the new surface resolves; only Pi Web's old
|
||||
usage is broken.
|
||||
|
||||
**Decisions:**
|
||||
- Committed with `git commit --no-verify`: the pre-commit hook runs a
|
||||
whole-project typecheck which cannot pass until the migration lands. Charter
|
||||
explicitly permits non-verify-green legs. Documented this + the `TMPDIR`
|
||||
workaround in `status.md` so future legs don't rediscover them.
|
||||
- Did not touch any `src/` migration code — kept strictly to slice 0 scope.
|
||||
|
||||
**Artifacts changed:** `package.json`, `package-lock.json` (commit `0fa9d0e`);
|
||||
`relays/issue-62-authstorage/{status,log}.md`. `node_modules/` installed
|
||||
(gitignored, not committed).
|
||||
|
||||
**Status update:** last completed leg 1, next leg 2 = charter slice 1
|
||||
(`authService.ts` core migration → propagate async construction to `sessiond.ts`).
|
||||
|
||||
**Blockers:** none. Sessiond restart still pending (becomes relevant from leg 2
|
||||
onward once `sessiond.ts` changes land).
|
||||
|
||||
**Handoff:** spawning leg 2 (slice 1).
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Leg 2 — slice 1: `authService.ts` core migration (commit `e37148c`)
|
||||
|
||||
**What I did:** migrated `src/server/sessions/authService.ts` off the removed
|
||||
`AuthStorage` / `ModelRegistry.create` surface onto the async `ModelRuntime`
|
||||
API, and propagated the async construction into the session daemon.
|
||||
|
||||
- `authService.ts`:
|
||||
- Imports `ModelRuntime` from `@earendil-works/pi-coding-agent` and
|
||||
`AuthInteraction` (type) from `@earendil-works/pi-ai`. Dropped
|
||||
`AuthStorage` / `ModelRegistry`.
|
||||
- `createModelRegistryForAgentDir` → `createModelRuntimeForAgentDir(agentDir)`
|
||||
returning `ModelRuntime.create({ authPath: <dir>/auth.json, modelsPath:
|
||||
<dir>/models.json })`.
|
||||
- Construction is now async: private constructor + static
|
||||
`AuthService.create({ agentDir? | runtime? | authFlows? })`. `runtime` dep
|
||||
replaces the old `modelRegistry` dep; no-agentDir fallback is
|
||||
`ModelRuntime.create({})`.
|
||||
- Public field `readonly runtime: ModelRuntime` replaces `modelRegistry`.
|
||||
- `saveApiKey` → `runtime.login(providerId, "api_key", interaction)` where
|
||||
`interaction` is a non-interactive `AuthInteraction` (`prompt: async () =>
|
||||
key`, `notify: () => {}`). Verified against pi-ai `envApiKeyAuth().login`,
|
||||
which calls `interaction.prompt({ type: "secret" })` and persists the
|
||||
returned `{ type:"api_key", key }` through `credentials.modify` inside
|
||||
`Models.login`. This is the credential-persistence path the assessment
|
||||
(§5.1) called for.
|
||||
- `logoutProvider` → `await runtime.logout(providerId)`.
|
||||
- `refreshAuthState` → `await runtime.refresh()` (no more `authStorage.reload()`
|
||||
— the file store is re-read by the runtime). Now async.
|
||||
- `authProviders` and `requireOAuthLoginProvider` became async, awaiting
|
||||
`runtime.refresh()` and the now-async `getLogin/LogoutProviderOptions`.
|
||||
- `startOAuthLogin` passes `runtime: this.runtime` into
|
||||
`OAuthLoginFlowService.start` (slice 3 will consume it via `runtime.login`).
|
||||
- `sessiond.ts`: `createRuntime()` is now `async`; `new AuthService(...)` →
|
||||
`await AuthService.create({ agentDir })`; `PiSessionService` now receives
|
||||
`modelRuntime: auth.runtime` instead of `modelRegistry: auth.modelRegistry`.
|
||||
- `sessiond/sessionDaemonStartup.ts`: `createRuntime` may now return
|
||||
`Runtime | Promise<Runtime>` and `runSessionDaemonStartup` `await`s it. The
|
||||
existing sync test doubles still satisfy the widened type.
|
||||
|
||||
**Decisions:**
|
||||
- **saveApiKey via `runtime.login("api_key", …)`** rather than reaching for a
|
||||
raw `CredentialStore.modify`: the pi-ai `CredentialStore` is not exposed off
|
||||
`ModelRuntime` publicly, and the provider's own api-key `login` is the
|
||||
intended persistence entry point (it writes through `credentials.modify`).
|
||||
Feeding the key back through a non-interactive `AuthInteraction.prompt` keeps
|
||||
us on the supported public surface. This matches assessment §5.1's
|
||||
"credential persistence via the pi-ai CredentialStore.modify path" without
|
||||
depending on unexported internals.
|
||||
- Kept `AuthService` construction async via a static factory (private ctor)
|
||||
rather than an `init()` method — cleaner async boundary, single valid
|
||||
construction path (code-quality-architecture skill).
|
||||
- Did NOT touch `authProviderOptions.ts`, `oauthLoginFlowService.ts`,
|
||||
`piSessionService.ts`, or any test/support files — strictly slice 1 scope.
|
||||
The async call sites I introduced (`await getLoginProviderOptions(...)`,
|
||||
`runtime:` in `authFlows.start`, `modelRuntime:` in PiSessionService deps)
|
||||
deliberately point at the interfaces slices 2–4 will expose.
|
||||
|
||||
**Typecheck state:** `npx tsc --noEmit` = 31 errors (was 24 at slice 0). The
|
||||
increase is expected and honest: the migrated authService now calls
|
||||
runtime-based interfaces that slices 2/3/4 have not migrated yet. All remaining
|
||||
`authService.ts` / `sessiond.ts` errors are cross-slice (authProviderOptions
|
||||
shape → slice 2; OAuthLoginFlowService.start `runtime` param → slice 3;
|
||||
`PiSessionServiceDependencies.modelRuntime` → slice 4). Test/support files
|
||||
(slice 5) still import the removed `AuthStorage`.
|
||||
|
||||
**Artifacts changed:** `src/server/sessions/authService.ts`,
|
||||
`src/server/sessiond.ts`, `src/server/sessiond/sessionDaemonStartup.ts`
|
||||
(commit `e37148c`); `relays/issue-62-authstorage/{status,log}.md`.
|
||||
|
||||
**Status update:** last completed leg 2, next leg 3 = charter slice 2
|
||||
(`authProviderOptions.ts` migration).
|
||||
|
||||
**Blockers:** none. **Sessiond restart now ACTIVE-pending** — slice 1 changed
|
||||
`sessiond.ts` and the daemon auth construction path; the human must manually
|
||||
restart the sessiond service once the migration lands (noted in status.md).
|
||||
Also: human confirmed `/tmp` is usable again, so the `TMPDIR` install
|
||||
workaround is no longer required (status.md updated).
|
||||
|
||||
**Handoff:** spawning leg 3 (slice 2).
|
||||
|
||||
## Leg 3 — slice 2: authProviderOptions.ts migration (commit d09d7cc)
|
||||
|
||||
**Did:** Migrated `src/server/sessions/authProviderOptions.ts` off the removed
|
||||
`authStorage`-centric surface onto the new `ModelRuntime` API.
|
||||
- Replaced the `AuthProviderModelRegistry` structural interface (which required
|
||||
`authStorage.getOAuthProviders()/list()/get()`, `getAll()`,
|
||||
`getProviderDisplayName()`) with a runtime-shaped `AuthProviderRuntime`
|
||||
interface exposing `getProviders()` (`{ id, name, auth: { apiKey?, oauth? } }`),
|
||||
`listCredentials()` (`Promise<{ providerId, type }[]>`), and
|
||||
`getProviderAuthStatus(id)`. Kept it structural (not `Pick<ModelRuntime,...>`)
|
||||
so the test can supply a lightweight double; verified the real `ModelRuntime`
|
||||
satisfies it (call sites in `authService.ts` typecheck clean).
|
||||
- Made `getLoginProviderOptions` / `getLogoutProviderOptions` `async` to match
|
||||
the `await` call sites already present in `authService.ts` (leg 2).
|
||||
- Login options: OAuth options from providers with `auth.oauth`; api-key options
|
||||
from providers with `auth.apiKey` filtered through the unchanged
|
||||
`OAUTH_ONLY_PROVIDERS` / `isApiKeyLoginProvider` logic. Display names now come
|
||||
from `Provider.name` (replacing `getProviderDisplayName`). Logout options
|
||||
derived from `listCredentials()`, mapping provider id -> name via
|
||||
`getProviders()`.
|
||||
- Rewrote the `authProviderOptions.test.ts` double to the runtime shape (a
|
||||
`getProviders` array with per-provider `auth`, a `listCredentials` promise,
|
||||
`getProviderAuthStatus`); made the two option-building tests async. All 3
|
||||
tests pass.
|
||||
|
||||
**Decisions:** `AuthProviderInfo.auth` typed as `{ apiKey?: unknown; oauth?:
|
||||
unknown }` — presence is all this module needs, and `unknown` keeps the double
|
||||
trivial while remaining assignable-from the real `ProviderAuth`. Structural
|
||||
interface (not `Pick<ModelRuntime>`) chosen for testability per
|
||||
code-quality-architecture (injectable seam, no SDK construction in unit test).
|
||||
|
||||
**Verify state:** `npx tsc --noEmit` 31 -> 28 errors. No `authProviderOptions`
|
||||
errors; `getLogin/LogoutProviderOptions` call sites in `authService.ts` clean.
|
||||
Remaining 28 are cross-slice: `authService.ts` line-83 `OAuthLoginFlowService.
|
||||
start` still expects `authStorage` (slice 3); `sessiond.ts`(1)+`piSessionService.
|
||||
ts`(6) slice 4; test/support files slice 5.
|
||||
|
||||
**Artifacts:** `src/server/sessions/authProviderOptions.ts`,
|
||||
`src/server/sessions/authProviderOptions.test.ts`; status.md updated; committed
|
||||
`d09d7cc` with `--no-verify` (migration not yet verify-green, permitted).
|
||||
|
||||
**Handoff:** spawning leg 4 (slice 3, oauthLoginFlowService.ts). Sessiond
|
||||
restart from leg 2 still pending — carried forward, not cleared.
|
||||
|
||||
## Leg 4 — slice 3: oauthLoginFlowService.ts migration (commit `1c3d6db`)
|
||||
|
||||
**What:** Reimplemented `OAuthLoginFlowService` against the pi-ai
|
||||
`AuthInteraction` contract and rewrote its test.
|
||||
|
||||
- `start()` now takes `runtime: Pick<ModelRuntime, "login">` instead of
|
||||
`authStorage: Pick<AuthStorage, "login">`; login driven via
|
||||
`runtime.login(providerId, "oauth", interaction)`. Resolves the
|
||||
`authService.ts` line-83 tsc error (authService.ts now at 0 errors).
|
||||
- Built a single `AuthInteraction` adapter (`{ signal, prompt, notify }`)
|
||||
replacing the six `OAuthLoginCallbacks` (`onAuth`/`onDeviceCode`/`onPrompt`/
|
||||
`onManualCodeInput`/`onSelect`/`onProgress`).
|
||||
- **Mapping decisions (verified carefully — riskiest slice):**
|
||||
- `prompt(AuthPrompt)` dispatches on `type`: `select` → `waitForSelect`
|
||||
(options `{id,label,description?}` → CommandOption `{value:id,label}`,
|
||||
resolves chosen id); `manual_code` → web-UI prompt kind `manual`;
|
||||
`text`/`secret` → web-UI prompt kind `prompt`. Old code special-cased
|
||||
`onManualCodeInput` with a hardcoded message; now the provider supplies the
|
||||
`manual_code` message, which is more correct.
|
||||
- `notify(AuthEvent)`: `auth_url` → `auth:{url,instructions?}`; `device_code`
|
||||
→ reuse `auth` field (`url: verificationUri`, instructions
|
||||
`"Enter code: <userCode>"`) exactly as the old `onDeviceCode` did;
|
||||
`info`+`progress` → append `message` to `progress` (old code only had
|
||||
`onProgress`; `info` folds in naturally).
|
||||
- Old `OAuthPrompt.allowEmpty`/`placeholder` handling: the new `AuthPrompt`
|
||||
has no `allowEmpty`, so interactive prompts are always required
|
||||
(`allowEmpty:false`); `select` keeps `allowEmpty:true`. Placeholder still
|
||||
forwarded when present.
|
||||
- **New behavior:** per-prompt `AuthPrompt.signal` now aborts just that pending
|
||||
request (rejects `"Prompt cancelled"`, clears the interaction from state)
|
||||
without ending the overall flow — the documented `manual_code`-vs-callback
|
||||
race. Added `bindPromptSignal` + a dedicated test for it.
|
||||
- **Test:** rewrote `oauthLoginFlowService.test.ts` with a `fakeRuntime`
|
||||
`login` double (returns a stub oauth credential). Replaced the old
|
||||
device-code-via-onDeviceCode coverage with an explicit `notify` device_code
|
||||
test and a per-prompt-signal-abort test. 9 tests pass; both files lint clean.
|
||||
|
||||
**tsc:** 28 → 26 errors. `authService.ts` = 0. Remaining: slice 4
|
||||
(`sessiond.ts` 1, `piSessionService.ts` 6) and slice 5 test/support files
|
||||
(`authService.test.ts` 10, `.testSupport.ts` 3, `.promptQueue.test.ts` 2,
|
||||
`.warnings.test.ts` 4).
|
||||
|
||||
**Status:** updated (current position, leg tracking → last leg 4 / next leg 5,
|
||||
next task = slice 4). Committed with `--no-verify` (migration not yet
|
||||
verify-green, per charter).
|
||||
|
||||
**Blockers:** none. Sessiond-restart-pending note still ACTIVE (unchanged;
|
||||
this slice did not touch the daemon path, but slice 1 did). Handing off to
|
||||
leg 5 (slice 4).
|
||||
|
||||
---
|
||||
|
||||
## Leg 5 — slice 4: piSessionService.ts migration (commit `4ccd4f8`)
|
||||
|
||||
**What:** Migrated `src/server/sessions/piSessionService.ts` to the new
|
||||
`ModelRuntime` API.
|
||||
- `createDefaultRuntimeFactory` now takes a `ModelRuntime` and passes
|
||||
`modelRuntime` to `createAgentSessionServices({ cwd, agentDir, modelRuntime })`
|
||||
(dropped the `authStorage` + `modelRegistry` args).
|
||||
- `PiAgentSession.modelRegistry: ModelRegistryInstance` → `modelRuntime:
|
||||
ModelRuntime`. Removed the `ModelRegistryInstance` type alias and the
|
||||
`AuthStorage`/`ModelRegistry` SDK imports; added `type ModelRuntime` +
|
||||
`readStoredCredential` imports and `join` from node:path. `authService.js`
|
||||
import reduced to just `AuthChange` (dropped `createModelRegistryForAgentDir`).
|
||||
- `anthropicSubscriptionWarning(session, authPath?)`: reads via
|
||||
`readStoredCredential("anthropic", authPath)`; param narrowed to
|
||||
`Pick<PiAgentSession, "model" | "settingsManager">`. `warningsForSession`
|
||||
passes `join(this.agentDir, "auth.json")`.
|
||||
- Model reads rederived onto the runtime: `availableModels`/`setModel` →
|
||||
`await modelRuntime.refresh()` + `getAvailableSnapshot()` + `getModel(...)`;
|
||||
`syncCurrentModelAuthWarning` → `getModel(...)` +
|
||||
`hasConfiguredAuth(providerId)`.
|
||||
- `applyAuthChange` no longer refreshes a registry (shared runtime is refreshed
|
||||
by AuthService before emit; all sessions share it), keeping the subscribe
|
||||
callback synchronous.
|
||||
|
||||
**Decision:** made `modelRuntime` a **required** `PiSessionServiceDependencies`
|
||||
field rather than keeping an optional `modelRegistry?`-style fallback. The old
|
||||
fallback built a registry synchronously in the constructor; `ModelRuntime` can
|
||||
only be created by the async `ModelRuntime.create`, which cannot run in a
|
||||
constructor. `sessiond.ts` already injects `modelRuntime: auth.runtime` (slice
|
||||
1), so production wiring is unaffected. Consequence: the slice-5 test surface is
|
||||
wider than the four files the assessment listed — every `new
|
||||
PiSessionService(...)` in tests now needs `modelRuntime`, and `fakeRuntime`/the
|
||||
`TestSession` type in `testSupport.ts` must expose `modelRuntime`. Documented in
|
||||
status.md "Next task".
|
||||
|
||||
**Result:** `npx tsc --noEmit` — `sessiond.ts` and `piSessionService.ts` at 0
|
||||
errors; all production code migrated (tsc output filtered to non-test/support
|
||||
files is empty). Remaining errors are slice-5 test/support only:
|
||||
authService.test (10), testSupport (4), warnings (5), promptQueue (17),
|
||||
lifecycle (19), archiveCleanup (9), spawnSession (3), spawnSubsession (18),
|
||||
sessionRoutes (1). `piSessionService.ts` lints clean. Committed `--no-verify`
|
||||
(migration not yet verify-green, per charter).
|
||||
|
||||
**Blockers:** none. **Sessiond-restart-pending note still ACTIVE** — this slice
|
||||
added `piSessionService.ts` (a session-daemon path) to the pending-restart
|
||||
surface; do not clear the note. Handing off to leg 6 (slice 5: tests +
|
||||
testSupport).
|
||||
|
||||
## Leg 6 — slice 5 (tests + testSupport migration) — commit `d0cc55c`
|
||||
|
||||
**What:** Migrated all test doubles + `piSessionService.testSupport.ts` off the
|
||||
removed `AuthStorage.inMemory` / `ModelRegistry.create|inMemory` surface to the
|
||||
pi-ai `InMemoryCredentialStore` + async `ModelRuntime.create({ credentials })`.
|
||||
`npm run verify` is now **fully green** (typecheck + lint + knip + 1390 tests,
|
||||
2 skipped) — the migration goal (charter criteria 1, 2, 5) is met; only the
|
||||
changeset (criterion 4) remains for slice 6.
|
||||
|
||||
**Files changed (all `src/server/sessions/`):**
|
||||
- `piSessionService.testSupport.ts`: new seams `createTestModelRuntime`,
|
||||
shared `testModelRuntime` (top-level await), `seedCredential`; `fakeRuntime`
|
||||
and `testModel` moved onto `modelRuntime`; dropped AuthStorage/ModelRegistry.
|
||||
- `archiveCleanup`/`lifecycle`/`promptQueue`/`spawnSession`/`spawnSubsession`/
|
||||
`sessionRoutes` tests: injected `modelRuntime: testModelRuntime` into every
|
||||
`new PiSessionService(...)` (now required) + imported the shared runtime.
|
||||
- `promptQueue.test.ts` auth-loss test: live `InMemoryCredentialStore` +
|
||||
`createTestModelRuntime(credentials)`, driving changes through
|
||||
`delete`/`seedCredential` + `refresh()` + `applyAuthChange(...)`.
|
||||
- `warnings.test.ts`: `anthropicSubscriptionWarning` seam is now a temp
|
||||
`auth.json` read via `readStoredCredential(id, authPath)`; type narrowed.
|
||||
- `authService.test.ts`: reworked to async `AuthService.create` +
|
||||
`InMemoryCredentialStore`; awaits async ops; OAuth-complete uses `vi.waitFor`.
|
||||
|
||||
**Decisions:**
|
||||
- Used a single shared `testModelRuntime` (top-level `await` in testSupport, an
|
||||
allowed ESM pattern here) for the no-auth catalog case so the many
|
||||
`PiSessionService` constructions and `fakeRuntime` sessions inject it
|
||||
synchronously — avoided making `fakeRuntime` itself async (which would have
|
||||
rippled through ~90 call sites). Auth-dependent tests build a dedicated
|
||||
per-test runtime via `createTestModelRuntime(credentials)`.
|
||||
- `anthropicSubscriptionWarning` seam: chose the on-disk temp `auth.json` +
|
||||
`readStoredCredential` path (matches production exactly) rather than adding a
|
||||
new injectable credential-read seam. Clean; no intervention needed.
|
||||
- Made `getLoginProviderOptions` **synchronous** (it did no async work) to
|
||||
satisfy `require-await`; de-awaited its 2 call sites in `authService.ts` and
|
||||
the test. Also fixed pre-existing lint debt from earlier slices surfaced now
|
||||
that lint ran green for the first time: `authRoutes.ts` return-await,
|
||||
`authService.ts` api-key interaction (`() => Promise.resolve(key)` /
|
||||
`notify: () => undefined`).
|
||||
- Verified SDK behavior empirically with throwaway probe scripts (removed)
|
||||
before writing doubles: providers/models catalog, api-key login persistence,
|
||||
`getModel`, `hasConfiguredAuth` across store mutations + refresh, and
|
||||
`readStoredCredential` against a temp auth.json.
|
||||
|
||||
**Verification:** `npm run verify` green. Pre-commit hook (whole-project
|
||||
typecheck + knip + eslint + related vitest) passed — committed normally
|
||||
(no `--no-verify` needed since the tree is verify-green).
|
||||
|
||||
**Blockers:** none. **Sessiond-restart-pending note still ACTIVE** — unchanged
|
||||
this leg but slices 1 + 4 touched session-daemon paths; only the human clears
|
||||
it after restarting the sessiond service. Handing off to leg 7 (slice 6:
|
||||
changeset + final verify + cleanup; the finish line — no PR).
|
||||
|
||||
## Leg 7 (slice 6 — changeset + final verify + cleanup) — RELAY COMPLETE
|
||||
- **Added changeset** `.changeset/fix-pi-0-80-8-modelruntime-auth.md`: single
|
||||
`patch` fragment for `@jmfederico/pi-web` describing the user-visible fix
|
||||
(session daemon crash on Pi 0.80.8+ fixed by migrating auth to the new
|
||||
`ModelRuntime` credential APIs; Pi Web now requires Pi `>=0.80.8`). Did NOT
|
||||
edit `CHANGELOG.md`.
|
||||
- **Bump type decision:** chose `patch`, not `minor`. status.md floated that a
|
||||
minor was "defensible" (narrowed Pi range), but the `changeset-changelog`
|
||||
skill is explicit for this CalVer repo: `patch` for all non-breaking changes,
|
||||
never `minor` (the minor slot = release month), `major` only on explicit user
|
||||
request. The fix is non-breaking to Pi Web consumers, so `patch` is correct.
|
||||
- **Re-ran `npm run verify`:** fully GREEN — typecheck + lint + knip + 188 test
|
||||
files, 1390 passed / 2 skipped.
|
||||
- **Goal criteria confirmed:** (1) no live `AuthStorage`/`ModelRegistry.create|
|
||||
inMemory`/`.authStorage`/`modelRegistry` use in `src/` — only remaining match
|
||||
is an explanatory comment in `piSessionService.testSupport.ts`; (2) auth
|
||||
surfaces all on new APIs (slices 1–5); (3) `package.json` peerDeps for the
|
||||
three `@earendil-works/*` = `>=0.80.8 <0.81`, devDeps = `^0.80.8` (verified);
|
||||
(4) changeset added this leg; (5) verify green.
|
||||
- **Cleanup:** confirmed no scratch files in the repo (no `probe*.mjs`,
|
||||
`.tmp-build/`). `ASSESSMENT-issue-62.md` intentionally kept (plan of record).
|
||||
- **No PR opened** (explicitly out of scope for this relay).
|
||||
- **STOP per charter:** goal reached, so no next leg was spawned. Surfaced to
|
||||
the human: (a) verify green, (b) sessiond restart STILL PENDING (slices 1+4
|
||||
touched session-daemon paths; only the human clears that note after
|
||||
restarting the sessiond service), (c) no PR by design.
|
||||
- Committed status/log/changeset.
|
||||
@@ -1,249 +0,0 @@
|
||||
# Relay status — issue-62-authstorage
|
||||
|
||||
## RELAY COMPLETE — goal reached (leg 7, slice 6)
|
||||
All charter goal criteria (1–5) are met and committed on branch
|
||||
`fix/issue-62-authstorage`. **No PR was opened, by design (out of scope).**
|
||||
The relay is finished; no further leg was spawned.
|
||||
|
||||
**Surface to the human:**
|
||||
- (a) `npm run verify` is fully GREEN (typecheck + lint + knip + 1390 tests,
|
||||
2 skipped).
|
||||
- (b) **Sessiond restart is still PENDING** — slices 1 + 4 changed
|
||||
session-daemon paths (`sessiond.ts`, `piSessionService.ts`). The human must
|
||||
manually restart the sessiond service for the migration to take effect. Only
|
||||
the human clears this note.
|
||||
- (c) No PR opened (explicitly out of scope for this relay).
|
||||
|
||||
Leg 7 (slice 6) added the changeset, re-verified green, confirmed goal
|
||||
criteria, and confirmed no scratch files remain:
|
||||
- **`.changeset/fix-pi-0-80-8-modelruntime-auth.md`** — a single `patch`
|
||||
fragment for `@jmfederico/pi-web` describing the user-visible fix (session
|
||||
daemon crash with Pi 0.80.8+ fixed by migrating to the new `ModelRuntime`
|
||||
auth APIs; Pi Web now requires Pi `>=0.80.8`). Per the `changeset-changelog`
|
||||
skill this repo uses **patch** for all non-breaking changes (CalVer: the
|
||||
`minor` slot is the release month, not feature size; `major` only on explicit
|
||||
request), so `patch` was chosen over the "minor is defensible" note. Commit
|
||||
`<this leg>`.
|
||||
- Re-ran full `npm run verify`: GREEN.
|
||||
- Double-checked `package.json`: peerDeps for the three `@earendil-works/*`
|
||||
packages are `>=0.80.8 <0.81`, devDeps are `^0.80.8` — correct.
|
||||
- Confirmed no scratch files in the repo (no `probe*.mjs`, `.tmp-build/`,
|
||||
etc.). `ASSESSMENT-issue-62.md` intentionally stays (plan of record).
|
||||
- Only `src` mention of the old API is an explanatory comment in
|
||||
`piSessionService.testSupport.ts` (documents what the seam replaced) — no
|
||||
live import/use.
|
||||
|
||||
## Prior position (slice 5, leg 6, commit `d0cc55c`)
|
||||
Slice 5 (tests + testSupport) complete and committed (`d0cc55c`).
|
||||
**`npm run verify` was fully GREEN** — typecheck + lint + knip + 1390 tests
|
||||
pass (2 skipped). All production code and all test/support code are off the
|
||||
removed `AuthStorage` / `ModelRegistry.create|inMemory` surface.
|
||||
|
||||
What slice 5 changed (all under `src/server/sessions/`):
|
||||
- **`piSessionService.testSupport.ts`** (central helper): dropped
|
||||
`AuthStorage`/`ModelRegistry` imports; added pi-ai `InMemoryCredentialStore`.
|
||||
New seams: `createTestModelRuntime(credentials?)` (wraps
|
||||
`ModelRuntime.create({ credentials })`), a shared `testModelRuntime`
|
||||
(top-level `await createTestModelRuntime()` — the common no-auth catalog
|
||||
runtime), and `seedCredential(store, providerId, credential)` (writes via the
|
||||
`CredentialStore.modify` path). `fakeRuntime` session now carries
|
||||
`modelRuntime: testModelRuntime`; `testModel()` reads
|
||||
`testModelRuntime.getModel(...)`.
|
||||
- Threaded `modelRuntime: testModelRuntime` into every `new PiSessionService(...)`
|
||||
(now a required dep) across `archiveCleanup`/`lifecycle`/`promptQueue`/
|
||||
`spawnSession`/`spawnSubsession`/`sessionRoutes` tests, importing
|
||||
`testModelRuntime` in each.
|
||||
- **`piSessionService.promptQueue.test.ts`** auth-loss test rewritten: builds a
|
||||
live `InMemoryCredentialStore` + `createTestModelRuntime(credentials)`, and
|
||||
simulates auth changes via `credentials.delete/seedCredential` +
|
||||
`modelRuntime.refresh()` + `applyAuthChange(...)` (matching AuthService's
|
||||
real refresh-then-emit sequence). Removed the `modelRegistry` dep line.
|
||||
- **`piSessionService.warnings.test.ts`**: `anthropicSubscriptionWarning` now
|
||||
reads `readStoredCredential("anthropic", authPath)`, so the test seam is a
|
||||
temp `auth.json` written per case (helper `anthropicAuthPath(...)`), passed
|
||||
as the 2nd arg. `SubscriptionSession` type narrowed to
|
||||
`Pick<PiAgentSession, "model" | "settingsManager">`. The "no credential"
|
||||
case points at a temp dir with no auth.json (deterministic).
|
||||
- **`authService.test.ts`** fully reworked to the async `AuthService.create({
|
||||
runtime | agentDir })` + `InMemoryCredentialStore` model. `saveApiKey`/
|
||||
`logoutProvider`/`startOAuthLogin` are awaited; OAuth-complete test asserts
|
||||
`startOptions.runtime === runtime` and uses `vi.waitFor` for the async
|
||||
refresh; credential assertions go through `credentials.read(...)`.
|
||||
- Lint fixes surfaced by running lint green for the first time this relay:
|
||||
`getLoginProviderOptions` made **synchronous** (it did no async work) and its
|
||||
call sites in `authService.ts` + `authProviderOptions.test.ts` de-awaited;
|
||||
`authRoutes.ts` handlers now `return await ...` (return-await rule);
|
||||
`authService.ts` api-key interaction uses `() => Promise.resolve(key)` /
|
||||
`notify: () => undefined`; test `modify` arrows use `() => Promise.resolve(...)`.
|
||||
|
||||
SDK behavior verified empirically before writing doubles (throwaway probe
|
||||
scripts, since removed): `ModelRuntime.create({ credentials })` exposes 36
|
||||
providers / 1072 models; `login(id, "api_key", interaction)` persists to the
|
||||
store; `getModel("anthropic", "claude-sonnet-4-5-20250929")` resolves;
|
||||
`hasConfiguredAuth` flips correctly across `delete`/`modify` + `refresh`;
|
||||
`readStoredCredential(id, authPath)` reads a temp auth.json and returns
|
||||
`undefined` for a missing file.
|
||||
|
||||
### Prior position (slice 4, leg 5, commit `4ccd4f8`)
|
||||
Slice 4 (`piSessionService.ts` migration) complete and committed (`4ccd4f8`).
|
||||
`piSessionService.ts` now uses the new `ModelRuntime` API end to end:
|
||||
- `createDefaultRuntimeFactory(modelRuntime, ...)` passes `modelRuntime` to
|
||||
`createAgentSessionServices({ cwd, agentDir, modelRuntime })` (no more
|
||||
`authStorage` + `modelRegistry`).
|
||||
- `PiAgentSession.modelRegistry` → `PiAgentSession.modelRuntime: ModelRuntime`.
|
||||
- `anthropicSubscriptionWarning(session, authPath?)` now reads via
|
||||
`readStoredCredential("anthropic", authPath)` (sync); `warningsForSession`
|
||||
passes `join(this.agentDir, "auth.json")`. Its `session` param narrowed to
|
||||
`Pick<PiAgentSession, "model" | "settingsManager">` (no longer needs the
|
||||
registry).
|
||||
- Model reads rederived onto the runtime: `availableModels`/`setModel` use
|
||||
`await modelRuntime.refresh()` + `getAvailableSnapshot()` + `getModel(...)`;
|
||||
`syncCurrentModelAuthWarning` uses `getModel(...)` +
|
||||
`hasConfiguredAuth(providerId)`.
|
||||
- `applyAuthChange` no longer refreshes a registry (the shared runtime is
|
||||
refreshed by AuthService before it emits, and all sessions share that
|
||||
runtime), so the `auth.subscribe` callback stays synchronous.
|
||||
- **`modelRuntime` is now a REQUIRED `PiSessionServiceDependencies` field**
|
||||
(the old `modelRegistry?` fallback used a *sync* `ModelRegistry.create`; a
|
||||
`ModelRuntime` can only be built by the async `ModelRuntime.create`, which
|
||||
can't run inside a constructor). `sessiond.ts` already injects
|
||||
`modelRuntime: auth.runtime` (slice 1), so it typechecks unchanged.
|
||||
- Dropped the `AuthStorage` / `ModelRegistry` imports and the
|
||||
`createModelRegistryForAgentDir` import (only `AuthChange` is still imported
|
||||
from `authService.js`).
|
||||
|
||||
`npx tsc --noEmit`: **`sessiond.ts` and `piSessionService.ts` are at 0 errors**
|
||||
(production code is fully migrated; `grep -vE '\.test\.ts|testSupport\.ts'` on
|
||||
tsc output is empty). All remaining errors are slice-5 test/support files.
|
||||
`piSessionService.ts` lints clean.
|
||||
|
||||
### Prior position (slice 3, leg 4, commit `1c3d6db`)
|
||||
Slice 3 (`oauthLoginFlowService.ts` migration) complete and committed (`1c3d6db`).
|
||||
`OAuthLoginFlowService` is reimplemented against the pi-ai `AuthInteraction`
|
||||
contract (`{ signal?, prompt(AuthPrompt), notify(AuthEvent) }`); the old
|
||||
`OAuthLoginCallbacks`/`AuthStorage` imports are gone. `start()` now takes a
|
||||
`ModelRuntime` (narrowed to `Pick<ModelRuntime, "login">`) instead of
|
||||
`authStorage`, and drives login via `runtime.login(providerId, "oauth",
|
||||
interaction)`. Mapping: `AuthPrompt` `text`/`secret`/`manual_code` → web-UI
|
||||
`prompt` (kind `prompt`, `manual_code` → kind `manual`); `select` → web-UI
|
||||
`select` (options `{id,label}` → `{value,label}`, returns chosen id);
|
||||
`AuthEvent` `auth_url` → `auth: {url, instructions?}`; `device_code` → reuse
|
||||
`auth` field (`url: verificationUri`, `instructions: "Enter code: <userCode>"`);
|
||||
`info`/`progress` → append `message` to `progress`. Per-prompt
|
||||
`AuthPrompt.signal` now aborts just that pending request (rejects
|
||||
`"Prompt cancelled"`) without ending the overall flow — needed because a
|
||||
`manual_code` prompt can race a callback server. `oauthLoginFlowService.test.ts`
|
||||
rewritten to the new contract via a `fakeRuntime` login double; **9 tests pass**,
|
||||
files lint clean.
|
||||
|
||||
`npx tsc --noEmit` now reports **26 errors** (down from 28). `authService.ts`
|
||||
is now at **0 errors** (as predicted). Remaining errors are all slice 4/5:
|
||||
`sessiond.ts` (1) + `piSessionService.ts` (6) = slice 4;
|
||||
`authService.test.ts` (10), `piSessionService.testSupport.ts` (3),
|
||||
`.promptQueue.test.ts` (2), `.warnings.test.ts` (4) = slice 5.
|
||||
|
||||
### Prior position (slice 2, leg 3, commit `d09d7cc`)
|
||||
Slice 2 (`authProviderOptions.ts` migration) complete and committed (`d09d7cc`).
|
||||
`authProviderOptions.ts` now derives options from a runtime-shaped
|
||||
`AuthProviderRuntime` interface (`getProviders()` + `listCredentials()` +
|
||||
`getProviderAuthStatus()`). `getLoginProviderOptions`/`getLogoutProviderOptions`
|
||||
are now `async` (matching the `await` call sites already in `authService.ts`).
|
||||
The old `AuthProviderModelRegistry` interface is gone; a real `ModelRuntime`
|
||||
satisfies `AuthProviderRuntime` structurally. The test double in
|
||||
`authProviderOptions.test.ts` was rewritten to the runtime shape and its 3
|
||||
tests pass. OAuth-capable = `auth.oauth` present; api-key = `auth.apiKey`
|
||||
present; `OAUTH_ONLY_PROVIDERS` / `isApiKeyLoginProvider` logic preserved;
|
||||
display names come from `Provider.name`.
|
||||
|
||||
`npx tsc --noEmit` now reports **28 errors** (down from 31). No
|
||||
`authProviderOptions` errors remain and the `getLoginProviderOptions` /
|
||||
`getLogoutProviderOptions` call sites in `authService.ts` typecheck cleanly.
|
||||
Remaining errors are all cross-slice: `authService.ts` (1: line-83
|
||||
`OAuthLoginFlowService.start` still expects `authStorage` not `runtime` —
|
||||
slice 3), `sessiond.ts` (1) + `piSessionService.ts` (6, slice 4), and the
|
||||
test/support files (slice 5): `authService.test.ts` (9),
|
||||
`oauthLoginFlowService.ts`/`.test.ts` (1+1, slice 3),
|
||||
`piSessionService.testSupport.ts` (3), `.promptQueue.test.ts` (2),
|
||||
`.warnings.test.ts` (4).
|
||||
|
||||
### Prior position (slice 1, leg 2, commit `e37148c`)
|
||||
Slice 1 (`authService.ts` core migration) complete and committed (`e37148c`).
|
||||
`authService.ts` now uses the async `ModelRuntime` API: `AuthService.create({
|
||||
agentDir | runtime })` factory wraps `ModelRuntime.create({ authPath,
|
||||
modelsPath })`; `createModelRuntimeForAgentDir` replaces
|
||||
`createModelRegistryForAgentDir`. `saveApiKey` → `runtime.login(id, "api_key",
|
||||
nonInteractive)`, `logoutProvider` → `runtime.logout`, `refreshAuthState` →
|
||||
`await runtime.refresh()`. `authProviders` / `requireOAuthLoginProvider` are now
|
||||
async. `startOAuthLogin` passes `runtime` into `OAuthLoginFlowService.start`.
|
||||
`sessiond.ts` uses async `createRuntime`, `AuthService.create`, and passes
|
||||
`modelRuntime: auth.runtime` to `PiSessionService`; `sessionDaemonStartup` now
|
||||
awaits `createRuntime`.
|
||||
|
||||
`npx tsc --noEmit` reports **31 errors** (up from 24 — expected: the migrated
|
||||
authService now calls the runtime-based interfaces that slices 2–4 haven't
|
||||
exposed yet). Slice-1 files are internally consistent; every remaining error
|
||||
in `authService.ts` / `sessiond.ts` is a **cross-slice** dependency:
|
||||
- `authService.ts`: `getLoginProviderOptions/getLogoutProviderOptions` still
|
||||
take the old `AuthProviderModelRegistry` shape (fixed in slice 2);
|
||||
`OAuthLoginFlowService.start` still expects `authStorage` not `runtime`
|
||||
(fixed in slice 3).
|
||||
- `sessiond.ts`: `PiSessionServiceDependencies` still expects `modelRegistry`
|
||||
not `modelRuntime` (fixed in slice 4).
|
||||
Remaining errors otherwise live in slices 2/3/4 files and all test/support
|
||||
files (slice 5).
|
||||
|
||||
## Leg tracking
|
||||
- **Last completed leg:** 7 (slice 6 — changeset + final verify + cleanup). **FINAL LEG.**
|
||||
- **Next leg to run:** none — relay complete, no handoff spawned.
|
||||
|
||||
## Next task
|
||||
None — the relay goal is reached. If new work is needed (e.g. opening a PR),
|
||||
that is a separate task outside this relay's charter.
|
||||
|
||||
### Build/tooling note (important for every leg)
|
||||
**Update (leg 2):** the human reports `/tmp` is now fully usable again, so the
|
||||
previous `TMPDIR` workaround is no longer required — plain `npm install` should
|
||||
work. (If a disk-quota error resurfaces, fall back to
|
||||
`TMPDIR="$PWD/.tmp-build" npm install` and remove `.tmp-build` after; it is
|
||||
scratch, do not commit it.) node_modules is already installed at 0.80.10, so a
|
||||
fresh install is only needed if node_modules is cleared. The pre-commit hook
|
||||
runs a whole-project typecheck; while the migration is incomplete, commit relay
|
||||
work with `git commit --no-verify` (the charter permits legs that aren't
|
||||
verify-green). Node: v24.18.0.
|
||||
|
||||
## Relevant context for the next runner
|
||||
- **Plan of record:** `ASSESSMENT-issue-62.md` (root) — read once. §5 has the
|
||||
per-file migration shape; §3 has the exact new API shapes; §6 the dep ranges.
|
||||
- **Changeset skill:** `.agents/skills/changeset-changelog/SKILL.md` (and
|
||||
`changeset-changelog` in the skills list). Follow it for the fragment format.
|
||||
- **The migration is done** — slice 6 is docs/changeset + confirmation only.
|
||||
No further source changes are expected; if you find yourself editing
|
||||
`src/`, re-check whether that's really in scope.
|
||||
- **New API cheat-sheet:** `ModelRuntime.create({ authPath, modelsPath,
|
||||
credentials? }): Promise<ModelRuntime>`; credential persistence via the
|
||||
pi-ai `CredentialStore.modify` path; `runtime.login(providerId, type,
|
||||
AuthInteraction)`; `runtime.logout`; `runtime.getProviders()` /
|
||||
`listCredentials()` / `getProviderAuthStatus()`; `readStoredCredential(
|
||||
providerId, authPath?)` for the sync anthropic warning; pi-ai
|
||||
`InMemoryCredentialStore` for tests.
|
||||
- **Decision already made:** clean migration, **no dual-version compat shim**
|
||||
(assessment §4/§5). Do not reopen this without the intervention signal.
|
||||
|
||||
## Progress documentation expectations
|
||||
Every leg: update this `status.md` (current position, leg tracking, next task,
|
||||
context, blockers), append a concise `log.md` entry, make work durable, and
|
||||
commit before handing off. Hand off with `spawn_session` **once** per the
|
||||
charter's Handover section.
|
||||
|
||||
## Blockers / intervention state
|
||||
None blocking. Relay complete. Known constraints:
|
||||
- **Sessiond restart pending (ACTIVE):** slice 1 (leg 2, commit `e37148c`)
|
||||
changed `sessiond.ts` + the session-daemon auth construction path; slice 4
|
||||
(leg 5, commit `4ccd4f8`) added `piSessionService.ts` (a session-daemon path)
|
||||
to this surface. Per AGENTS.md the human must **manually restart the sessiond
|
||||
service** for these changes to take effect once the migration lands. Keep
|
||||
this note until the human confirms the restart.
|
||||
- `/tmp` disk-quota issue is resolved (human confirmed usable) — see the
|
||||
Build/tooling note above.
|
||||
- node_modules is installed (gitignored) at 0.80.10; a fresh `npm install` is
|
||||
only needed if node_modules is cleared.
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
doctorExitCode,
|
||||
isCliEntrypoint,
|
||||
launchdRuntimeDetails,
|
||||
nodeVersionCheck,
|
||||
regularFileExists,
|
||||
serviceBackendForPlatform,
|
||||
} from "./cli.js";
|
||||
@@ -58,6 +59,18 @@ describe("commandWithVersionCheck", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("nodeVersionCheck", () => {
|
||||
it("checks the complete supported Node version with the resolved executable", () => {
|
||||
process.env["SHELL"] = "/bin/bash";
|
||||
|
||||
const command = nodeVersionCheck();
|
||||
|
||||
expect(command).toContain("22.19.0");
|
||||
expect(command).toContain("process.versions.node");
|
||||
expect(command).toContain("\"$pi_web_probe_executable\"");
|
||||
});
|
||||
});
|
||||
|
||||
describe("agentCommandForChecks", () => {
|
||||
it("reads the configured agent command for doctor checks", () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "pi-web-cli-test-"));
|
||||
|
||||
+10
-6
@@ -15,6 +15,7 @@ import {
|
||||
type NativeServiceInstallFailure,
|
||||
} from "./nativeServices/serviceInstall.js";
|
||||
import {
|
||||
minimumSupportedNodeVersion,
|
||||
nativeServiceManagerRefs,
|
||||
productionNativeServiceIds,
|
||||
type NativeServiceBackend,
|
||||
@@ -772,11 +773,14 @@ export function commandWithVersionCheck(command: string): string {
|
||||
return `${found} && (${commandWord} --version 2>&1 || true)`;
|
||||
}
|
||||
|
||||
function nodeVersionCheck(): string {
|
||||
return [
|
||||
commandCheck("node"),
|
||||
"node -e \"const major = Number(process.versions.node.split('.')[0]); console.log(process.version); process.exit(major >= 22 ? 0 : 1);\"",
|
||||
].join(" && ");
|
||||
export function nodeVersionCheck(): string {
|
||||
return nativeServicePrerequisiteShellCheck(detectServiceShell().name, {
|
||||
id: "caller.node",
|
||||
kind: "node-version",
|
||||
command: "node",
|
||||
minimumVersion: minimumSupportedNodeVersion,
|
||||
description: `node >= ${minimumSupportedNodeVersion}`,
|
||||
});
|
||||
}
|
||||
|
||||
export function agentCommandForChecks(env: NodeJS.ProcessEnv = process.env): string {
|
||||
@@ -787,7 +791,7 @@ function generalDoctorChecks(): Check[] {
|
||||
const shell = serviceShellLabel();
|
||||
const agentCommand = agentCommandForChecks();
|
||||
return [
|
||||
[`Caller login ${shell} can find node >= 22`, serviceShellCommand(nodeVersionCheck())],
|
||||
[`Caller login ${shell} can find node >= ${minimumSupportedNodeVersion}`, serviceShellCommand(nodeVersionCheck())],
|
||||
[`Caller login ${shell} can find npm`, serviceShellCommand(commandWithVersionCheck("npm"))],
|
||||
[`Caller login ${shell} can find ${agentCommand}`, serviceShellCommand(commandWithVersionCheck(agentCommand))],
|
||||
];
|
||||
|
||||
@@ -100,7 +100,7 @@ describe("production native service planning", () => {
|
||||
wants: [],
|
||||
prerequisites: [
|
||||
{ id: "sessiond.command.pi-web-sessiond", kind: "command-available", command: "pi-web-sessiond" },
|
||||
{ id: "sessiond.node", kind: "node-version", command: "node", minimumMajor: 22 },
|
||||
{ id: "sessiond.node", kind: "node-version", command: "node", minimumVersion: "22.19.0" },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -111,7 +111,7 @@ describe("production native service planning", () => {
|
||||
wants: ["sessiond"],
|
||||
prerequisites: [
|
||||
{ id: "web.command.pi-web-server", kind: "command-available", command: "pi-web-server" },
|
||||
{ id: "web.node", kind: "node-version", command: "node", minimumMajor: 22 },
|
||||
{ id: "web.node", kind: "node-version", command: "node", minimumVersion: "22.19.0" },
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -196,7 +196,7 @@ describe("production native service planning", () => {
|
||||
namedCommandFailure: "command not found",
|
||||
},
|
||||
prerequisites: [
|
||||
{ id: "sessiond.node", kind: "node-version", command: "node", minimumMajor: 22 },
|
||||
{ id: "sessiond.node", kind: "node-version", command: "node", minimumVersion: "22.19.0" },
|
||||
{ id: "sessiond.entrypoint", kind: "readable-file", path: "/package with space/sessiond's entry.js" },
|
||||
],
|
||||
});
|
||||
@@ -324,7 +324,7 @@ describe("development native service planning", () => {
|
||||
environment: { PI_WEB_CONFIG: "/tmp/config.json" },
|
||||
workingDirectory: "/checkout with space",
|
||||
prerequisites: [
|
||||
{ id: "sessiond.node", kind: "node-version", minimumMajor: 22 },
|
||||
{ id: "sessiond.node", kind: "node-version", minimumVersion: "22.19.0" },
|
||||
{ id: "sessiond.command.npm", kind: "command-available", command: "npm" },
|
||||
{ id: "sessiond.package-scripts", kind: "package-scripts", scripts: ["start:sessiond"] },
|
||||
],
|
||||
@@ -338,7 +338,7 @@ describe("development native service planning", () => {
|
||||
after: ["sessiond"],
|
||||
wants: ["sessiond"],
|
||||
prerequisites: [
|
||||
{ id: "uiDev.node", kind: "node-version", minimumMajor: 22 },
|
||||
{ id: "uiDev.node", kind: "node-version", minimumVersion: "22.19.0" },
|
||||
{ id: "uiDev.command.npm", kind: "command-available", command: "npm" },
|
||||
{ id: "uiDev.command.bash", kind: "command-available", command: "bash" },
|
||||
{ id: "uiDev.package-scripts", kind: "package-scripts", scripts: ["dev:web", "dev:client"] },
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
export const minimumSupportedNodeVersion = "22.19.0";
|
||||
|
||||
export type NativeServiceBackendKind = "systemd" | "launchd";
|
||||
export type NativeServiceMode = "production" | "development";
|
||||
export type NativeServiceId = "sessiond" | "web" | "uiDev";
|
||||
@@ -64,7 +66,7 @@ export type NativeServicePrerequisite =
|
||||
id: string;
|
||||
kind: "node-version";
|
||||
command: "node";
|
||||
minimumMajor: number;
|
||||
minimumVersion: string;
|
||||
description: string;
|
||||
}
|
||||
| {
|
||||
@@ -571,8 +573,8 @@ function nodeRequirement(serviceId: NativeServiceId): NativeServicePrerequisite
|
||||
id: `${serviceId}.node`,
|
||||
kind: "node-version",
|
||||
command: "node",
|
||||
minimumMajor: 22,
|
||||
description: "node >= 22 is available to the service shell",
|
||||
minimumVersion: minimumSupportedNodeVersion,
|
||||
description: `node >= ${minimumSupportedNodeVersion} is available to the service shell`,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
LaunchdNativeServiceProbe,
|
||||
@@ -5,6 +6,7 @@ import {
|
||||
SystemdNativeServiceProbe,
|
||||
launchdProbePlist,
|
||||
nativeServicePrerequisiteShellCheck,
|
||||
nodeVersionCheckScript,
|
||||
systemdRunArguments,
|
||||
type LaunchdProbeFileSystem,
|
||||
type ProbeCommandResult,
|
||||
@@ -433,13 +435,26 @@ describe("probe service definitions", () => {
|
||||
id: "sessiond.node",
|
||||
kind: "node-version",
|
||||
command: "node",
|
||||
minimumMajor: 22,
|
||||
description: "node >= 22",
|
||||
minimumVersion: "22.19.0",
|
||||
description: "node >= 22.19.0",
|
||||
});
|
||||
expect(check).toContain("\"$pi_web_probe_executable\" '-e'");
|
||||
expect(check).toContain("22.19.0");
|
||||
expect(check).not.toContain("&& node -e");
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ version: "21.99.99", accepted: false },
|
||||
{ version: "22.18.99", accepted: false },
|
||||
{ version: "22.19.0", accepted: true },
|
||||
{ version: "22.19.1", accepted: true },
|
||||
{ version: "23.0.0", accepted: true },
|
||||
])("checks the complete Node version for $version", ({ version, accepted }) => {
|
||||
const result = spawnSync(process.execPath, ["-e", nodeVersionCheckScript("22.19.0"), version]);
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(result.status).toBe(accepted ? 0 : 1);
|
||||
});
|
||||
|
||||
it("requires bundled entrypoints to be readable regular files", () => {
|
||||
const check = nativeServicePrerequisiteShellCheck("bash", {
|
||||
id: "sessiond.entrypoint",
|
||||
|
||||
@@ -434,10 +434,8 @@ export function nativeServicePrerequisiteShellCheck(shell: NativeServiceShellNam
|
||||
switch (prerequisite.kind) {
|
||||
case "command-available":
|
||||
return externalExecutableShellCheck(shell, prerequisite.command);
|
||||
case "node-version": {
|
||||
const script = `const major=Number(process.versions.node.split('.')[0]);process.exit(major>=${String(prerequisite.minimumMajor)}?0:1)`;
|
||||
return externalExecutableShellCheck(shell, "node", ["-e", script]);
|
||||
}
|
||||
case "node-version":
|
||||
return externalExecutableShellCheck(shell, "node", ["-e", nodeVersionCheckScript(prerequisite.minimumVersion)]);
|
||||
case "readable-file": {
|
||||
const path = shellQuote(shell, prerequisite.path);
|
||||
return `test -f ${path} && test -r ${path}`;
|
||||
@@ -449,6 +447,11 @@ export function nativeServicePrerequisiteShellCheck(shell: NativeServiceShellNam
|
||||
}
|
||||
}
|
||||
|
||||
export function nodeVersionCheckScript(minimumVersion: string): string {
|
||||
const encodedMinimum = JSON.stringify(minimumVersion);
|
||||
return `const version=process.argv[1]??process.versions.node;console.log(process.version);const current=version.split('.').map(Number);const minimum=${encodedMinimum}.split('.').map(Number);const length=Math.max(current.length,minimum.length);let comparison=0;for(let index=0;index<length;index+=1){const left=current[index]??0;const right=minimum[index]??0;if(left!==right){comparison=left>right?1:-1;break}}process.exit(comparison>=0?0:1)`;
|
||||
}
|
||||
|
||||
function externalExecutableShellCheck(
|
||||
shell: NativeServiceShellName,
|
||||
command: string,
|
||||
@@ -518,7 +521,7 @@ function unsatisfiedDetail(prerequisite: NativeServicePrerequisite): string {
|
||||
case "command-available":
|
||||
return `${prerequisite.command} did not resolve to an external executable in the native service environment.`;
|
||||
case "node-version":
|
||||
return `node >= ${String(prerequisite.minimumMajor)} was not available in the native service environment.`;
|
||||
return `node >= ${prerequisite.minimumVersion} was not available in the native service environment.`;
|
||||
case "readable-file":
|
||||
return `${prerequisite.path} was not a readable regular file in the native service environment.`;
|
||||
case "package-scripts":
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ModelRuntime } from "@earendil-works/pi-coding-agent";
|
||||
import { InMemoryCredentialStore, type AuthPrompt, type Credential } from "@earendil-works/pi-ai";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OAuthFlowState } from "../../shared/apiTypes.js";
|
||||
import { AuthService, type AuthChange, type AuthServiceLogger } from "./authService.js";
|
||||
import { AuthService, createModelRuntimeForAgentDir, type AuthChange, type AuthServiceLogger } from "./authService.js";
|
||||
import { OAuthLoginFlowService } from "./oauthLoginFlowService.js";
|
||||
|
||||
const tempDirs: string[] = [];
|
||||
@@ -251,7 +251,8 @@ describe("AuthService", () => {
|
||||
|
||||
it("stores credentials in the configured agent directory", async () => {
|
||||
const agentDir = await tempAgentDir();
|
||||
const auth = await AuthService.create({ agentDir });
|
||||
const runtime = await createModelRuntimeForAgentDir(agentDir, false);
|
||||
const auth = await AuthService.create({ runtime });
|
||||
|
||||
await auth.saveApiKey("anthropic", "sk-test");
|
||||
|
||||
|
||||
@@ -31,8 +31,12 @@ interface AuthChangeContext {
|
||||
|
||||
const noopLogger: AuthServiceLogger = { error() { /* no-op */ } };
|
||||
|
||||
export function createModelRuntimeForAgentDir(agentDir: string): Promise<ModelRuntime> {
|
||||
return ModelRuntime.create({ authPath: join(agentDir, "auth.json"), modelsPath: join(agentDir, "models.json") });
|
||||
export function createModelRuntimeForAgentDir(agentDir: string, allowModelNetwork?: boolean): Promise<ModelRuntime> {
|
||||
return ModelRuntime.create({
|
||||
authPath: join(agentDir, "auth.json"),
|
||||
modelsPath: join(agentDir, "models.json"),
|
||||
...(allowModelNetwork === undefined ? {} : { allowModelNetwork }),
|
||||
});
|
||||
}
|
||||
|
||||
export class AuthService {
|
||||
|
||||
@@ -79,7 +79,7 @@ export async function seedCredential(store: InMemoryCredentialStore, providerId:
|
||||
* behavior (e.g. auth-loss warnings).
|
||||
*/
|
||||
export function createTestModelRuntime(credentials: CredentialStore = new InMemoryCredentialStore()): Promise<ModelRuntime> {
|
||||
return ModelRuntime.create({ credentials });
|
||||
return ModelRuntime.create({ credentials, modelsPath: null, allowModelNetwork: false });
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user