8.9 KiB
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/ModelRegistryusage insrc/(3 production files + 5 test/support files undersrc/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:AuthStorageand its backends removed from exports; newModelRuntime(async) +readStoredCredential; changedModelRegistry(constructed from a runtime,refresh()now async, noauthStorage); pi-aiCredentialStore/InMemoryCredentialStore/AuthInteractionmodel. Confirmed 0.80.8 and 0.80.10.d.tsare 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.81for all three@earendil-works/*packages; upper bound<0.81because this line ships breaking changes within0.80.x. - Relay packet placed under
relays/issue-62-authstorage/(committed; not inpackage.jsonfiles, 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 installin the worktree. The default/tmp-based node-gyp build ofnode-ptyfailed with "Disk quota exceeded" (/tmp is a 5.8G tmpfs at ~81%). Re-ran withTMPDIR="$PWD/.tmp-build" npm install, which succeeded (618 packages, 0 vulnerabilities). Removed.tmp-buildafter.- 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✓,AuthStorageabsent ✓, pi-aiInMemoryCredentialStore✓. - Ran
npx tsc --noEmit: 24 errors, all insrc/server/sessions/at the expected migration sites (removedAuthStorage,ModelRegistry.create/inMemory,authStorageon 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 + theTMPDIRworkaround instatus.mdso 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
ModelRuntimefrom@earendil-works/pi-coding-agentandAuthInteraction(type) from@earendil-works/pi-ai. DroppedAuthStorage/ModelRegistry. createModelRegistryForAgentDir→createModelRuntimeForAgentDir(agentDir)returning `ModelRuntime.create({ authPath: /auth.json, modelsPath: /models.json })`.- Construction is now async: private constructor + static
AuthService.create({ agentDir? | runtime? | authFlows? }).runtimedep replaces the oldmodelRegistrydep; no-agentDir fallback isModelRuntime.create({}). - Public field
readonly runtime: ModelRuntimereplacesmodelRegistry. saveApiKey→runtime.login(providerId, "api_key", interaction)whereinteractionis a non-interactiveAuthInteraction(prompt: async () => key,notify: () => {}). Verified against pi-aienvApiKeyAuth().login, which callsinteraction.prompt({ type: "secret" })and persists the returned{ type:"api_key", key }throughcredentials.modifyinsideModels.login. This is the credential-persistence path the assessment (§5.1) called for.logoutProvider→await runtime.logout(providerId).refreshAuthState→await runtime.refresh()(no moreauthStorage.reload()— the file store is re-read by the runtime). Now async.authProvidersandrequireOAuthLoginProviderbecame async, awaitingruntime.refresh()and the now-asyncgetLogin/LogoutProviderOptions.startOAuthLoginpassesruntime: this.runtimeintoOAuthLoginFlowService.start(slice 3 will consume it viaruntime.login).
- Imports
sessiond.ts:createRuntime()is nowasync;new AuthService(...)→await AuthService.create({ agentDir });PiSessionServicenow receivesmodelRuntime: auth.runtimeinstead ofmodelRegistry: auth.modelRegistry.sessiond/sessionDaemonStartup.ts:createRuntimemay now returnRuntime | Promise<Runtime>andrunSessionDaemonStartupawaits it. The existing sync test doubles still satisfy the widened type.
Decisions:
- saveApiKey via
runtime.login("api_key", …)rather than reaching for a rawCredentialStore.modify: the pi-aiCredentialStoreis not exposed offModelRuntimepublicly, and the provider's own api-keyloginis the intended persistence entry point (it writes throughcredentials.modify). Feeding the key back through a non-interactiveAuthInteraction.promptkeeps 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
AuthServiceconstruction async via a static factory (private ctor) rather than aninit()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:inauthFlows.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).