docs: implementation plan for session-scoped server selection

This commit is contained in:
Troed Sångberg
2026-08-21 13:35:52 +02:00
parent b5b6fe9be4
commit cad446da2b
@@ -0,0 +1,319 @@
# Session-Scoped Server Selection Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Poll only the llama-server backing the current session's model provider, falling back to polling all detected servers when the mapping is unknown; fold the result into the existing v1.3.0 release.
**Architecture:** New pure function `selectSessionUrls(candidates, providers, providerID)` in `src/stats.ts` narrows the candidate URL list per poll cycle using the route session's `providerID` matched against `api.state.provider`. `tui.tsx` calls it inside `pollMetrics`. `resolveServerUrls` additionally dedupes detected lists.
**Tech Stack:** TypeScript, node:test via `npx -y tsx test-backoff.ts` (the repo has no node_modules/tsconfig; `npm run prepack` runs the same test file).
**Spec:** `docs/superpowers/specs/2026-08-21-session-scoped-server-selection-design.md`
---
### Task 1: `selectSessionUrls` in src/stats.ts
**Files:**
- Modify: `src/stats.ts` (append after `resolveServerUrls`, end of file)
- Test: `test-backoff.ts`
- [ ] **Step 1: Write failing tests**
Add to the end of `test-backoff.ts`:
```ts
test("selectSessionUrls narrows to the matching provider's baseURL", () => {
const candidates = ["http://a:1", "http://b:2"]
const providers = [
{ id: "vision", name: "Vision", options: { baseURL: "http://c:3" } },
{ id: "llamacpp-a", name: "Llama A", options: { baseURL: "http://b:2/v1" } },
]
assert.deepEqual(selectSessionUrls(candidates, providers, "llamacpp-a"), ["http://b:2"])
})
test("selectSessionUrls matches base_url variant", () => {
const candidates = ["http://localhost:9090"]
const providers = [{ id: "llamacpp-b", name: "L", options: { base_url: "http://localhost:9090" } }]
assert.deepEqual(selectSessionUrls(candidates, providers, "llamacpp-b"), ["http://localhost:9090"])
})
test("selectSessionUrls normalizes /v1 before comparing", () => {
const candidates = ["http://x:9"]
const providers = [{ id: "p", name: "P", options: { baseURL: "http://x:9/v1" } }]
assert.deepEqual(selectSessionUrls(candidates, providers, "p"), ["http://x:9"])
})
test("selectSessionUrls returns candidates unchanged when provider not found", () => {
const candidates = ["http://a:1"]
assert.deepEqual(selectSessionUrls(candidates, [], "nope"), ["http://a:1"])
})
test("selectSessionUrls returns candidates unchanged when matched provider URL is not a candidate", () => {
const candidates = ["http://a:1"]
const providers = [{ id: "ollama", name: "Ollama", options: { baseURL: "http://d:4" } }]
assert.deepEqual(selectSessionUrls(candidates, providers, "ollama"), ["http://a:1"])
})
test("selectSessionUrls returns candidates unchanged for missing or empty providerID", () => {
assert.deepEqual(selectSessionUrls(["http://a:1"], [], undefined), ["http://a:1"])
assert.deepEqual(selectSessionUrls(["http://a:1"], [], ""), ["http://a:1"])
})
test("selectSessionUrls returns empty candidates unchanged", () => {
assert.deepEqual(selectSessionUrls([], [], "p"), [])
})
test("selectSessionUrls tolerates non-array and malformed providers", () => {
const c = ["http://a:1"]
assert.deepEqual(selectSessionUrls(c, undefined, "p"), c)
assert.deepEqual(selectSessionUrls(c, [null, 42, {}, { id: "p" }], "p"), c)
})
test("selectSessionUrls first match wins for duplicate ids", () => {
const candidates = ["http://a:1", "http://b:2"]
const providers = [
{ id: "dup", name: "first", options: { baseURL: "http://b:2/v1" } },
{ id: "dup", name: "second", options: { baseURL: "http://a:1" } },
]
assert.deepEqual(selectSessionUrls(candidates, providers, "dup"), ["http://b:2"])
})
```
Also update the import line at the top of `test-backoff.ts` to include `selectSessionUrls` alongside the existing stats imports.
- [ ] **Step 2: Run tests to verify they fail**
Run: `npx -y tsx test-backoff.ts`
Expected: FAIL — `does not provide an export named 'selectSessionUrls'`.
- [ ] **Step 3: Implement**
Append to `src/stats.ts` after `resolveServerUrls`:
```ts
export function selectSessionUrls(
candidates: string[],
providers: unknown,
providerID: string | undefined,
): string[] {
if (!providerID || providerID.trim() === "" || candidates.length === 0) return candidates
if (!Array.isArray(providers)) return candidates
for (const entry of providers) {
if (!entry || typeof entry !== "object") continue
const provider = entry as Record<string, unknown>
if (provider.id !== providerID) continue
const opts = provider.options
if (opts && typeof opts === "object") {
const baseURL =
(opts as Record<string, unknown>).baseURL || (opts as Record<string, unknown>).base_url
if (typeof baseURL === "string") {
const normalized = stripBaseUrlPath(baseURL)
if (candidates.includes(normalized)) return [normalized]
}
}
break
}
return candidates
}
```
Note the `break`: the first entry whose `id` equals `providerID` decides the outcome; later duplicates are never consulted.
- [ ] **Step 4: Run tests to verify they pass**
Run: `npx -y tsx test-backoff.ts`
Expected: PASS — all prior 21 plus the new ones, 0 failures.
- [ ] **Step 5: Commit**
```bash
git add src/stats.ts test-backoff.ts
git commit -m "feat: narrow polled servers to the current session's provider"
```
---
### Task 2: Dedup detected lists in `resolveServerUrls`
**Files:**
- Modify: `src/stats.ts` (`resolveServerUrls` body, last four lines of the function)
- Test: `test-backoff.ts`
- [ ] **Step 1: Write failing test**
Append to `test-backoff.ts`:
```ts
test("duplicate provider URLs are deduped", () => {
const providers = [
{ id: "llamacpp-a", name: "A", options: { baseURL: "http://same:7" } },
{ id: "llamacpp-b", name: "B", options: { baseURL: "http://same:7/v1" } },
]
assert.deepEqual(resolveServerUrls(undefined, providers, {}), ["http://same:7"])
})
```
- [ ] **Step 2: Run to verify failure**
Run: `npx -y tsx test-backoff.ts`
Expected: FAIL — actual result contains `"http://same:7"` twice.
- [ ] **Step 3: Implement**
In `src/stats.ts`, change the tail of `resolveServerUrls` from:
```ts
const detected = extractProviderUrlsFromList(providers)
if (detected.length > 0) return detected
const legacy = extractProviderUrls(config)
return legacy.length > 0 ? legacy : ["http://localhost:8080"]
```
to:
```ts
const detected = [...new Set(extractProviderUrlsFromList(providers))]
if (detected.length > 0) return detected
const legacy = [...new Set(extractProviderUrls(config))]
return legacy.length > 0 ? legacy : ["http://localhost:8080"]
```
- [ ] **Step 4: Run tests to verify pass**
Run: `npx -y tsx test-backoff.ts`
Expected: PASS, 0 failures.
- [ ] **Step 5: Commit**
```bash
git add src/stats.ts test-backoff.ts
git commit -m "fix: dedupe discovered server URLs"
```
---
### Task 3: Wire session-scoped selection into tui.tsx
**Files:**
- Modify: `tui.tsx` (import line ~5; `pollMetrics` body lines ~183197)
No unit-test step: `tui.tsx` is host wiring, untested by the suite (established convention). Verify via grep + test run.
- [ ] **Step 1: Extend the import from `./src/stats`**
The import at the top currently includes `resolveServerUrls`; add `selectSessionUrls` so it reads (keep alphabetical order as in the file):
```tsx
import { backoffDelayMs, fetchSlots, processPoll, resolveServerUrls, selectSessionUrls } from "./src/stats"
```
(Match the exact current import statement style — single import from `./src/stats`.)
- [ ] **Step 2: Capture providerID and select per cycle**
In `pollMetrics`, replace:
```tsx
let model = llamaServerModel
if (!model) {
const routeSessionID = api.route.current.params?.sessionID
if (routeSessionID) {
const session = api.state.session.get(routeSessionID)
if (session?.model?.id) {
model = session.model.id
}
}
}
for (const baseUrl of llamaServerUrls) {
```
with:
```tsx
let model = llamaServerModel
let sessionProviderId: string | undefined
if (!model) {
const routeSessionID = api.route.current.params?.sessionID
if (routeSessionID) {
const session = api.state.session.get(routeSessionID)
if (session?.model?.id) {
model = session.model.id
}
sessionProviderId = session?.model?.providerID
}
}
const activeUrls = selectSessionUrls(llamaServerUrls, api.state.provider, sessionProviderId)
if (!activeUrls.length) return
for (const baseUrl of activeUrls) {
```
Leave the rest of the loop body untouched. The startup block at ~line 308 (`llamaServerUrls = resolveServerUrls(...)`) stays exactly as is.
- [ ] **Step 3: Verify**
Run: `npx -y tsx test-backoff.ts` → all pass.
Run: `grep -n "selectSessionUrls\|sessionProviderId\|activeUrls" tui.tsx` → import + the three new usages, nothing else changed.
- [ ] **Step 4: Commit**
```bash
git add tui.tsx
git commit -m "feat: poll only the current session's llama-server"
```
---
### Task 4: README note
**Files:**
- Modify: `README.md` (Server Discovery section, after the provider-list paragraph)
- [ ] **Step 1: Add one sentence**
After the paragraph beginning "When no explicit `server` option is set…" (the provider-list discovery paragraph), add:
```markdown
When several llama-server providers are detected, stats follow the provider used by the current session; until a session has picked a model, every detected server is polled.
```
- [ ] **Step 2: Commit**
```bash
git add README.md
git commit -m "docs: describe session-scoped server selection"
```
---
### Task 5: Fold into the v1.3.0 release
**Files:**
- None modified. Tag/publish operations only.
- [ ] **Step 1: Full verification**
Run: `npm run prepack`
Expected: all tests pass, exit 0. Working tree clean.
- [ ] **Step 2: Move tag and push**
```bash
git tag -f v1.3.0
git push origin main
git push --force origin refs/tags/v1.3.0
```
Expected: tag now points at HEAD (which still has `package.json` version `1.3.0`); remote updated.
- [ ] **Step 3: Republish**
`npm publish` publishes to the `@troed` scope registry configured in `~/.npmrc` (git.sync.wtf). If the registry rejects re-publishing an existing version number, retry with `npm publish --force`.
Verify afterwards: `npm view @troed/oc-ls-stats versions --registry $(npm config get "@troed:registry" 2>/dev/null || npm config get registry)` shows 1.3.0, and the tarball for 1.3.0 contains `selectSessionUrls` in `src/stats.ts` (check `npm pack --dry-run` output lists files, then inspect the packed tarball).
- [ ] **Step 4: Report**
Report final commit list since `cfd9d93`, the moved tag SHA, and publish result.