pi-freeflow 1.10.0 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,194 +1,200 @@
1
- # Changelog
2
-
3
- ## 1.10.0
4
-
5
- ### Minor Changes
6
-
7
- - cf48598: Hands-free proxy recovery plus catalog refresh: the proxy now watches its own health, restarts with backoff instead of leaving a dead port, and can start on login. Removed two discontinued free models from the picker.
8
-
9
- All notable changes to pi-freeflow. Public, user-visible behavior only.
10
-
11
- ## 1.9.9 - 2026-09-06
12
-
13
- ### Fixes
14
-
15
- - **Removed our own request cap — sorry, that one was on us.** The proxy used to enforce a built-in request limit and could answer 429 before upstream quota was actually exhausted. That was a bug, not your quota. From this version the proxy never rejects on quota itself: a 429 only surfaces when the upstream — and every relay in your pool — genuinely is rate-limited, and that response now points you at `/freeflow deploy` to add relay egress.
16
-
17
- ## 1.9.8 - 2026-09-06
18
-
19
- ### Fixes
20
-
21
- - **Stale model list heals itself (follow-up to #6).** If your saved model list predates a newly added model, the background refresh now repairs the entry (correct endpoint and details) instead of sending requests to the wrong addressno manual `/freeflow refresh` or cache deletion needed.
22
- - **Paid models stay out of the picker even from old saved lists.** Every read of the saved model list now drops non-free entries, so models requiring an API key cannot linger after an upgrade.
23
- - **Old saved lists without a sync marker now re-sync once.** A saved list that could never trigger a network check now performs one plain revalidation (then syncs normally), so newly added free models appear without manual intervention. Missing or corrupt lists still fall back silently with no network call.
24
- - **Upstream errors are now visible in the proxy log.** Failed upstream responses log their status code and model, and a model routed to the wrong endpoint logs the mismatch with the fix (restart Pi/OMP after upgrade).
25
-
26
- ### Validation
27
-
28
- - TypeScript typecheck passed cleanly (`tsc --noEmit`).
29
- - Full test suite passed on Windows (304 tests) and Ubuntu Linux (`acerblue`, 305/305 tests passed), including new regressions for the stale-cache shape from #6.
30
- - Live sweep of all 26 models through a fresh install on `acerblue`: 23/26 answered on first try (both Muse Spark models via the Responses endpoint); the 3 misses are upstream per-model daily quotas (429), zero server errors.
31
-
32
- ## 1.9.7 - 2026-09-06
33
-
34
- ### Fixes
35
-
36
- - **Model picker only shows verified free models (fixes #6).** Background catalog refresh now filters out non-free models from upstream endpoints so paid models requiring an API key no longer leak into your picker.
37
- - **Relay pool automatically recovers from dead deployments (fixes #5).** If a relay URL in your pool returns an infrastructure 404 (such as a missing or deleted Vercel deployment), the proxy marks it as failed and immediately rolls over to your next healthy relay or direct mode instead of getting trapped in a 404 loop.
38
-
39
- ### Changes
40
-
41
- - **Updated model catalog (26 verified free models: 7 OpenCode Zen + 19 KiloCode Gateway).**
42
- - Added `inclusionai/ling-3.0-flash-sante:free` (Ling 3.0 Flash Sante, 262K context, 32K output, reasoning supported). Clean CLI alias `ling-3.0-flash-sante` supported.
43
- - Removed `laguna-s-2.1-free` (OpenCode Zen) after upstream dropped free-tier access. `poolside/laguna-s-2.1:free` on Kilo remains active.
44
- - **Public by default for new relay deployments.** Relays deployed via `/freeflow deploy` are now public by default with no mandatory authentication tokens. This enables seamless copy-paste migration across proxy tools (such as 9router) while maintaining safety guards for allowed AI upstreams.
45
-
46
- ### Validation
47
-
48
- - TypeScript typecheck passed cleanly (`tsc --noEmit`).
49
- - Full test suite passed across Windows (299 tests) and Ubuntu Linux (`acerblue-local`, 300/300 tests passed).
50
- - End-to-end concurrency and failover stress testing verified (100 parallel requests, 20 subagent leases, burst failover).
51
-
52
- ## 1.9.6 - 2026-09-03
53
-
54
- ### Fixes
55
-
56
- - **Hiding the status widget now sticks.** `/freeflow hide` survives restarts, new sessions, and background update notices — previously the widget could reappear on the next session.
57
- - **Calmer startup with many sessions.** Sessions starting at the same time now converge on a single background proxy instead of each spawning its own; a spawn that never becomes ready falls back to in-process mode instead of running untracked.
58
- - **Survives daemon restarts mid-session.** If the background proxy is replaced while sessions run, connected sessions re-register automatically instead of silently losing their lease (which could retire the daemon mid-use).
59
- - **Stale-version cleanup targets the right process.** Windows port matching is now exact, and Linux without `lsof`/`fuser` resolves the holder via `/proc` instead of giving up.
60
- - **`/freeflow logs --follow` no longer runs forever.** The live tail stops when the session ends and caps burst output; the update/install subprocess now has a 2-minute timeout.
61
-
62
- ### Validation
63
-
64
- - `npx tsc --noEmit` clean, `npm test` 299 (298 pass + 1 Linux-only skip) + smoke green on Windows; stress harness 7/7 on Windows and Linux `acerblue-local`. **`macOS not tested`** this cycle.
65
-
66
- ## 1.9.5 - 2026-09-03
67
-
68
- ### Changes
69
-
70
- - **Catalog refreshed against live upstream model lists (26 models: 8 OpenCode Zen + 18 KiloCode Gateway).** Re-checked both upstreams against their live endpoints and live inference probes:
71
- - **Added** `muse-spark-1.3-contributor-free` (OpenCode Zen, Responses API, 1M context, 131K output, vision) — verified live: completes with reasoning, accepts effort levels, and answers vision queries.
72
- - **Removed** `hy3-free` (OpenCode Zen) — upstream no longer serves it (`Model hy3-free is not supported`).
73
- - **Removed** `tencent/hy3:free` and `meituan/longcat-2.0-free` (KiloCode Gateway) — upstream free tier dropped them (model unavailable / sign-in required).
74
- - Pruned IDs added to the dead-model filter so a stale disk cache cannot resurrect them.
75
-
76
- ### Validation
77
-
78
- - `npx tsc --noEmit` clean, `npm test` full suite green on Windows.
79
-
80
- ## 1.9.4 - 2026-09-02
81
-
82
- ### Dependencies
83
-
84
- - **Zero runtime dependencies.** Removed `undici@8.10.0` `pi` (`0.84.4`) and `omp` (`18.1.3`) already bundle `undici` 6.x/7.x and expose `global fetch` with keep-alive pooling. `relayFetch` and proxy now use `global fetch` directly (`src/relay.ts` `Agent` + `canUseCustomDispatcher` + `dispatcher: agent` removed; `src/proxy.ts` dispatcher removed). Keeps thin `11.3k` + `298 tests` + `0 deps` compatible directly with `reference/pi` + `reference/oh-my-pi`.
85
-
86
- ### Validation
87
-
88
- - `npx tsc --noEmit` clean, `npm test 298/298` on **Windows** (`omp/18.1.3`, `pi 0.84.4`) and **Linux `acerblue-local`** (`Ubuntu 6.8.0-138`, `node v22.23.2`, `pi 0.84.4`) via `/tmp/pi-freeflow-validation`. **`macOS not tested`** this cycle.
89
-
90
- ## 1.9.3 - 2026-09-02
91
-
92
- ### Fixes
93
-
94
- - **Windows console flood fixed.** Two Windows-only helpers flashed a visible `conhost`/`cmd` window on every daemon probe: `netstat -ano | findstr :28180` / `taskkill` in the stale-daemon replace path and `spawn(omp|npm, shell:true)` for `/freeflow update`. Both now use `windowsHide: true` (no-op on Linux/macOS) and `spawnWithProgress` was refactored to `Promise.withResolvers` to satisfy `ts-promise-with-resolvers`. Idle `pi` no longer spawns many visible consoles even after closing the terminal (detached daemon at `127.0.0.1:28180` survives by design; `beatOnce` 10s heartbeat now throttled 2s via `lastSpawnAt`).
95
- - Daemon spawn now throttled per-process (2s) as a storm guard when `28180` is contended or blocked; the `ensuring` guard + `waitForReady 5s` already prevented tight loops.
96
-
97
- ### Validation
98
-
99
- - `npx tsc --noEmit` clean, `npm test 279/279` on **Windows** (`omp/18.1.3`, `pi 0.84.4`) and **Linux `acerblue-local`** (`Ubuntu 6.8.0-138`, `node v22.23.2`, `pi 0.84.4`) via `/tmp/pi-freeflow-validation`. **`macOS not tested`** this cycle.
100
- - Reporter `LOYINuts` issue #3 (`pi idle creates many sessions force reboot`) `grep -r rtk src` confirms `rtk` is an external global skill (`~/.agents/skills/rtk` `Command::new("cmd")` without `CREATE_NO_WINDOW`), not `pi-freeflow`. After this fix, closing the terminal no longer leaves flashing zombies; kill via `netstat -ano | findstr :28180` `taskkill /F /PID` or `/freeflow kill`.
101
-
102
- ## 1.9.2 - 2026-08-31
103
-
104
- ### Fixes
105
-
106
- - **Closing one session no longer stops the shared local proxy.** The proxy daemon
107
- is now a fully detached background process: it outlives any single OMP/Pi session
108
- (previously, closing the session that owned the daemon could shut it down even
109
- while other sessions were still using it). It retires by itself only when no
110
- session is connected, no request is in flight, and it has been idle for a grace
111
- period. The next use starts it again automatically.
112
- - New command: `/freeflow kill` (aliases `stop`, `shutdown`) stops the background
113
- daemon on demand. The next freeflow use restarts it.
114
-
115
- ### Improvements
116
-
117
- - The proxy tracks connected sessions and last request time; `/freeflow status`
118
- and the health endpoint now report active session leases, so you can see when
119
- other sessions are keeping the daemon alive.
120
- - Docs: the command reference now lists `kill`, and the FAQ explains the shared
121
- daemon lifecycle (survives session close; self-retires when unused).
122
-
123
- ## 1.9.1 - 2026-08-30
124
-
125
- ### Fixes
126
-
127
- - Stale-daemon guard completed: a pre-1.9.0 daemon cannot report its in-flight requests, and usage cannot be verified — so it is now left running instead of being replaced. Previously such a daemon could still be killed mid-stream while busy (a one-time window when upgrading from 1.8.x). The guarantee now holds in every case: only older, verified-idle daemons are replaced.
128
- - The stale-daemon kill ritual was consolidated into one path (it was duplicated at five call sites); behavior unchanged.
129
- - Tests: new coverage for the pre-1.9 daemon case; shared sandbox helpers extracted.
130
-
131
- ## 1.9.0 - 2026-08-30
132
-
133
- ### Development hardening
134
-
135
- - Test suite now runs fully sandboxed: every test uses a temporary data directory, so no test can ever write to your real `~/.pi/agent/` files or interfere with a running local proxy. A single env override (`PI_*_DATA_DIR`) re-roots all data files for tests/CI.
136
- - Added a complete mocked user-flow e2e suite: fresh install → onboarding → proxy health → direct chat → relay add/roll/fallback → guided deploy → update check → command surface — all deterministic, zero network.
137
- - Docs: test counts no longer hardcoded in README (they drifted with releases); release history tracked here.
138
-
139
- ### Fixes
140
-
141
- - **Stale-daemon replacement no longer interrupts running sessions.** The shared local proxy can be held by another OMP/Pi session; the old upgrade logic killed that holder on version mismatch, which could terminate the session that owned the proxy mid-stream. Replacement now only happens when the running daemon is strictly older AND idle (0 in-flight requests, reported via `/_health`); newer or busy daemons are reused with a log note instead. Optional opt-out: set the no-kill env to `1` (see README FAQ for upgrades).
142
-
143
- ## 1.8.2 - 2026-08-30
144
-
145
- ### Fixes & polish
146
-
147
- - Deploy: compare-and-swap on concurrent deployments (no duplicate relays when two sessions deploy at once).
148
- - Proxy: URL-encoding edge cases (`%` in paths), startup timeout and kill handling, port conflict fallback.
149
- - Logs: sanitized sensitive headers in debug output.
150
- - Kilo gateway models: compatibility pass for all 25 models.
151
- - Docs site build included; README per-host usage guide (Oh My Pi & Pi install + pick + manage).
152
-
153
- ## 1.8.1 - 2026-08-29
154
-
155
- - Fix: stale-daemon auto-heal is now shipped in the published package (previously only in the repo).
156
-
157
- ## 1.8.0 - 2026-08-29
158
-
159
- ### Automatic upgrades
160
-
161
- - On version upgrade, the extension detects a stale local proxy daemon and replaces it automatically users get fixes without manual restarts or killing sessions.
162
- - Fix: client auth key stripped before reaching the opencode.ai/zen upstream (failed with 401 for some clients).
163
- - 3 new free models (28 total).
164
-
165
- ## 1.7.1 - 2026-08-29
166
-
167
- - Docs: clarified npm is the distribution channel (the package is an extension loaded by OMP/Pi, not a standalone CLI).
168
-
169
- ## 1.7.0 - 2026-08-29
170
-
171
- - 4 new free models (25 total) with live-verified specs (context/output limits, vision, thinking levels).
172
- - New alias map aligned with host model selectors.
173
-
174
- ## 1.6.1 - 2026-08-29
175
-
176
- - Catalog: thinking-level map locked per model; docs sync.
177
-
178
- ## 1.6.0 - 2026-08-29
179
-
180
- ### Onboarding & UX
181
-
182
- - First-run onboarding message; 429 guidance hint (add your own relay egress).
183
- - `/freeflow test` to probe a relay; relay latency tracking with health badges in `/freeflow list`.
184
- - Guided deploy with context picker + confirmation; post-deploy health check.
185
- - Status clarity: current mode + state file path; log text filter; per-relay usage counters; throttled roll notifications.
186
-
187
- ### Reliability
188
-
189
- - Relay state write-protection: before every save, the current state is snapshotted to `.bak`; if the main file is corrupted or missing, the backup is recovered automatically.
190
- - New-user flow never seeds a fake relay; starts in direct mode with an empty pool.
191
-
192
- ## 1.5.1 - 2026-08-28
193
-
194
- - Edge-sweep fixes: port 28180 with legacy 18080 dual-probe auto-migration, OMP/Pi compatibility.
1
+ # Changelog
2
+
3
+ ## 1.11.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Proxy daemon now stays up while any session uses it, even when idle. It shuts down only after the last session leaves, instead of retiring after a short quiet window.
8
+
9
+ ## 1.10.0
10
+
11
+ ### Minor Changes
12
+
13
+ - cf48598: Hands-free proxy recovery plus catalog refresh: the proxy now watches its own health, restarts with backoff instead of leaving a dead port, and can start on login. Removed two discontinued free models from the picker.
14
+
15
+ All notable changes to pi-freeflow. Public, user-visible behavior only.
16
+
17
+ ## 1.9.9 - 2026-09-06
18
+
19
+ ### Fixes
20
+
21
+ - **Removed our own request cap sorry, that one was on us.** The proxy used to enforce a built-in request limit and could answer 429 before upstream quota was actually exhausted. That was a bug, not your quota. From this version the proxy never rejects on quota itself: a 429 only surfaces when the upstream and every relay in your pool genuinely is rate-limited, and that response now points you at `/freeflow deploy` to add relay egress.
22
+
23
+ ## 1.9.8 - 2026-09-06
24
+
25
+ ### Fixes
26
+
27
+ - **Stale model list heals itself (follow-up to #6).** If your saved model list predates a newly added model, the background refresh now repairs the entry (correct endpoint and details) instead of sending requests to the wrong address — no manual `/freeflow refresh` or cache deletion needed.
28
+ - **Paid models stay out of the picker even from old saved lists.** Every read of the saved model list now drops non-free entries, so models requiring an API key cannot linger after an upgrade.
29
+ - **Old saved lists without a sync marker now re-sync once.** A saved list that could never trigger a network check now performs one plain revalidation (then syncs normally), so newly added free models appear without manual intervention. Missing or corrupt lists still fall back silently with no network call.
30
+ - **Upstream errors are now visible in the proxy log.** Failed upstream responses log their status code and model, and a model routed to the wrong endpoint logs the mismatch with the fix (restart Pi/OMP after upgrade).
31
+
32
+ ### Validation
33
+
34
+ - TypeScript typecheck passed cleanly (`tsc --noEmit`).
35
+ - Full test suite passed on Windows (304 tests) and Ubuntu Linux (`acerblue`, 305/305 tests passed), including new regressions for the stale-cache shape from #6.
36
+ - Live sweep of all 26 models through a fresh install on `acerblue`: 23/26 answered on first try (both Muse Spark models via the Responses endpoint); the 3 misses are upstream per-model daily quotas (429), zero server errors.
37
+
38
+ ## 1.9.7 - 2026-09-06
39
+
40
+ ### Fixes
41
+
42
+ - **Model picker only shows verified free models (fixes #6).** Background catalog refresh now filters out non-free models from upstream endpoints so paid models requiring an API key no longer leak into your picker.
43
+ - **Relay pool automatically recovers from dead deployments (fixes #5).** If a relay URL in your pool returns an infrastructure 404 (such as a missing or deleted Vercel deployment), the proxy marks it as failed and immediately rolls over to your next healthy relay or direct mode instead of getting trapped in a 404 loop.
44
+
45
+ ### Changes
46
+
47
+ - **Updated model catalog (26 verified free models: 7 OpenCode Zen + 19 KiloCode Gateway).**
48
+ - Added `inclusionai/ling-3.0-flash-sante:free` (Ling 3.0 Flash Sante, 262K context, 32K output, reasoning supported). Clean CLI alias `ling-3.0-flash-sante` supported.
49
+ - Removed `laguna-s-2.1-free` (OpenCode Zen) after upstream dropped free-tier access. `poolside/laguna-s-2.1:free` on Kilo remains active.
50
+ - **Public by default for new relay deployments.** Relays deployed via `/freeflow deploy` are now public by default with no mandatory authentication tokens. This enables seamless copy-paste migration across proxy tools (such as 9router) while maintaining safety guards for allowed AI upstreams.
51
+
52
+ ### Validation
53
+
54
+ - TypeScript typecheck passed cleanly (`tsc --noEmit`).
55
+ - Full test suite passed across Windows (299 tests) and Ubuntu Linux (`acerblue-local`, 300/300 tests passed).
56
+ - End-to-end concurrency and failover stress testing verified (100 parallel requests, 20 subagent leases, burst failover).
57
+
58
+ ## 1.9.6 - 2026-09-03
59
+
60
+ ### Fixes
61
+
62
+ - **Hiding the status widget now sticks.** `/freeflow hide` survives restarts, new sessions, and background update notices — previously the widget could reappear on the next session.
63
+ - **Calmer startup with many sessions.** Sessions starting at the same time now converge on a single background proxy instead of each spawning its own; a spawn that never becomes ready falls back to in-process mode instead of running untracked.
64
+ - **Survives daemon restarts mid-session.** If the background proxy is replaced while sessions run, connected sessions re-register automatically instead of silently losing their lease (which could retire the daemon mid-use).
65
+ - **Stale-version cleanup targets the right process.** Windows port matching is now exact, and Linux without `lsof`/`fuser` resolves the holder via `/proc` instead of giving up.
66
+ - **`/freeflow logs --follow` no longer runs forever.** The live tail stops when the session ends and caps burst output; the update/install subprocess now has a 2-minute timeout.
67
+
68
+ ### Validation
69
+
70
+ - `npx tsc --noEmit` clean, `npm test` 299 (298 pass + 1 Linux-only skip) + smoke green on Windows; stress harness 7/7 on Windows and Linux `acerblue-local`. **`macOS not tested`** this cycle.
71
+
72
+ ## 1.9.5 - 2026-09-03
73
+
74
+ ### Changes
75
+
76
+ - **Catalog refreshed against live upstream model lists (26 models: 8 OpenCode Zen + 18 KiloCode Gateway).** Re-checked both upstreams against their live endpoints and live inference probes:
77
+ - **Added** `muse-spark-1.3-contributor-free` (OpenCode Zen, Responses API, 1M context, 131K output, vision) — verified live: completes with reasoning, accepts effort levels, and answers vision queries.
78
+ - **Removed** `hy3-free` (OpenCode Zen) upstream no longer serves it (`Model hy3-free is not supported`).
79
+ - **Removed** `tencent/hy3:free` and `meituan/longcat-2.0-free` (KiloCode Gateway) — upstream free tier dropped them (model unavailable / sign-in required).
80
+ - Pruned IDs added to the dead-model filter so a stale disk cache cannot resurrect them.
81
+
82
+ ### Validation
83
+
84
+ - `npx tsc --noEmit` clean, `npm test` full suite green on Windows.
85
+
86
+ ## 1.9.4 - 2026-09-02
87
+
88
+ ### Dependencies
89
+
90
+ - **Zero runtime dependencies.** Removed `undici@8.10.0` — `pi` (`0.84.4`) and `omp` (`18.1.3`) already bundle `undici` 6.x/7.x and expose `global fetch` with keep-alive pooling. `relayFetch` and proxy now use `global fetch` directly (`src/relay.ts` `Agent` + `canUseCustomDispatcher` + `dispatcher: agent` removed; `src/proxy.ts` dispatcher removed). Keeps thin `11.3k` + `298 tests` + `0 deps` compatible directly with `reference/pi` + `reference/oh-my-pi`.
91
+
92
+ ### Validation
93
+
94
+ - `npx tsc --noEmit` clean, `npm test 298/298` on **Windows** (`omp/18.1.3`, `pi 0.84.4`) and **Linux `acerblue-local`** (`Ubuntu 6.8.0-138`, `node v22.23.2`, `pi 0.84.4`) via `/tmp/pi-freeflow-validation`. **`macOS not tested`** this cycle.
95
+
96
+ ## 1.9.3 - 2026-09-02
97
+
98
+ ### Fixes
99
+
100
+ - **Windows console flood fixed.** Two Windows-only helpers flashed a visible `conhost`/`cmd` window on every daemon probe: `netstat -ano | findstr :28180` / `taskkill` in the stale-daemon replace path and `spawn(omp|npm, shell:true)` for `/freeflow update`. Both now use `windowsHide: true` (no-op on Linux/macOS) and `spawnWithProgress` was refactored to `Promise.withResolvers` to satisfy `ts-promise-with-resolvers`. Idle `pi` no longer spawns many visible consoles even after closing the terminal (detached daemon at `127.0.0.1:28180` survives by design; `beatOnce` 10s heartbeat now throttled 2s via `lastSpawnAt`).
101
+ - Daemon spawn now throttled per-process (2s) as a storm guard when `28180` is contended or blocked; the `ensuring` guard + `waitForReady 5s` already prevented tight loops.
102
+
103
+ ### Validation
104
+
105
+ - `npx tsc --noEmit` clean, `npm test 279/279` on **Windows** (`omp/18.1.3`, `pi 0.84.4`) and **Linux `acerblue-local`** (`Ubuntu 6.8.0-138`, `node v22.23.2`, `pi 0.84.4`) via `/tmp/pi-freeflow-validation`. **`macOS not tested`** this cycle.
106
+ - Reporter `LOYINuts` issue #3 (`pi idle creates many sessions → force reboot`) — `grep -r rtk src` ∅ confirms `rtk` is an external global skill (`~/.agents/skills/rtk` → `Command::new("cmd")` without `CREATE_NO_WINDOW`), not `pi-freeflow`. After this fix, closing the terminal no longer leaves flashing zombies; kill via `netstat -ano | findstr :28180` → `taskkill /F /PID` or `/freeflow kill`.
107
+
108
+ ## 1.9.2 - 2026-08-31
109
+
110
+ ### Fixes
111
+
112
+ - **Closing one session no longer stops the shared local proxy.** The proxy daemon
113
+ is now a fully detached background process: it outlives any single OMP/Pi session
114
+ (previously, closing the session that owned the daemon could shut it down even
115
+ while other sessions were still using it). It retires by itself only when no
116
+ session is connected, no request is in flight, and it has been idle for a grace
117
+ period. The next use starts it again automatically.
118
+ - New command: `/freeflow kill` (aliases `stop`, `shutdown`) stops the background
119
+ daemon on demand. The next freeflow use restarts it.
120
+
121
+ ### Improvements
122
+
123
+ - The proxy tracks connected sessions and last request time; `/freeflow status`
124
+ and the health endpoint now report active session leases, so you can see when
125
+ other sessions are keeping the daemon alive.
126
+ - Docs: the command reference now lists `kill`, and the FAQ explains the shared
127
+ daemon lifecycle (survives session close; self-retires when unused).
128
+
129
+ ## 1.9.1 - 2026-08-30
130
+
131
+ ### Fixes
132
+
133
+ - Stale-daemon guard completed: a pre-1.9.0 daemon cannot report its in-flight requests, and usage cannot be verified — so it is now left running instead of being replaced. Previously such a daemon could still be killed mid-stream while busy (a one-time window when upgrading from 1.8.x). The guarantee now holds in every case: only older, verified-idle daemons are replaced.
134
+ - The stale-daemon kill ritual was consolidated into one path (it was duplicated at five call sites); behavior unchanged.
135
+ - Tests: new coverage for the pre-1.9 daemon case; shared sandbox helpers extracted.
136
+
137
+ ## 1.9.0 - 2026-08-30
138
+
139
+ ### Development hardening
140
+
141
+ - Test suite now runs fully sandboxed: every test uses a temporary data directory, so no test can ever write to your real `~/.pi/agent/` files or interfere with a running local proxy. A single env override (`PI_*_DATA_DIR`) re-roots all data files for tests/CI.
142
+ - Added a complete mocked user-flow e2e suite: fresh install → onboarding → proxy health → direct chat → relay add/roll/fallback → guided deploy → update check → command surface — all deterministic, zero network.
143
+ - Docs: test counts no longer hardcoded in README (they drifted with releases); release history tracked here.
144
+
145
+ ### Fixes
146
+
147
+ - **Stale-daemon replacement no longer interrupts running sessions.** The shared local proxy can be held by another OMP/Pi session; the old upgrade logic killed that holder on version mismatch, which could terminate the session that owned the proxy mid-stream. Replacement now only happens when the running daemon is strictly older AND idle (0 in-flight requests, reported via `/_health`); newer or busy daemons are reused with a log note instead. Optional opt-out: set the no-kill env to `1` (see README FAQ for upgrades).
148
+
149
+ ## 1.8.2 - 2026-08-30
150
+
151
+ ### Fixes & polish
152
+
153
+ - Deploy: compare-and-swap on concurrent deployments (no duplicate relays when two sessions deploy at once).
154
+ - Proxy: URL-encoding edge cases (`%` in paths), startup timeout and kill handling, port conflict fallback.
155
+ - Logs: sanitized sensitive headers in debug output.
156
+ - Kilo gateway models: compatibility pass for all 25 models.
157
+ - Docs site build included; README per-host usage guide (Oh My Pi & Pi install + pick + manage).
158
+
159
+ ## 1.8.1 - 2026-08-29
160
+
161
+ - Fix: stale-daemon auto-heal is now shipped in the published package (previously only in the repo).
162
+
163
+ ## 1.8.0 - 2026-08-29
164
+
165
+ ### Automatic upgrades
166
+
167
+ - On version upgrade, the extension detects a stale local proxy daemon and replaces it automatically users get fixes without manual restarts or killing sessions.
168
+ - Fix: client auth key stripped before reaching the opencode.ai/zen upstream (failed with 401 for some clients).
169
+ - 3 new free models (28 total).
170
+
171
+ ## 1.7.1 - 2026-08-29
172
+
173
+ - Docs: clarified npm is the distribution channel (the package is an extension loaded by OMP/Pi, not a standalone CLI).
174
+
175
+ ## 1.7.0 - 2026-08-29
176
+
177
+ - 4 new free models (25 total) with live-verified specs (context/output limits, vision, thinking levels).
178
+ - New alias map aligned with host model selectors.
179
+
180
+ ## 1.6.1 - 2026-08-29
181
+
182
+ - Catalog: thinking-level map locked per model; docs sync.
183
+
184
+ ## 1.6.0 - 2026-08-29
185
+
186
+ ### Onboarding & UX
187
+
188
+ - First-run onboarding message; 429 guidance hint (add your own relay egress).
189
+ - `/freeflow test` to probe a relay; relay latency tracking with health badges in `/freeflow list`.
190
+ - Guided deploy with context picker + confirmation; post-deploy health check.
191
+ - Status clarity: current mode + state file path; log text filter; per-relay usage counters; throttled roll notifications.
192
+
193
+ ### Reliability
194
+
195
+ - Relay state write-protection: before every save, the current state is snapshotted to `.bak`; if the main file is corrupted or missing, the backup is recovered automatically.
196
+ - New-user flow never seeds a fake relay; starts in direct mode with an empty pool.
197
+
198
+ ## 1.5.1 - 2026-08-28
199
+
200
+ - Edge-sweep fixes: port 28180 with legacy 18080 dual-probe auto-migration, OMP/Pi compatibility.
package/README.md CHANGED
@@ -290,7 +290,7 @@ to `1` before starting a session.
290
290
  Nothing visible to your other sessions. The proxy daemon is a separate background
291
291
  process shared by every OMP/Pi session on the machine. Closing one session just
292
292
  unregisters it; the daemon keeps serving the rest and retires itself automatically
293
- once the last client disconnects and it has been idle for a short grace period.
293
+ once the last client disconnects and no client re-attaches within a short grace window.
294
294
  To stop it manually, run `/freeflow kill` — the next freeflow use starts it again.
295
295
 
296
296
  **Where's the normalizer?**
package/package.json CHANGED
@@ -1,61 +1,61 @@
1
- {
2
- "name": "pi-freeflow",
3
- "type": "module",
4
- "version": "1.10.0",
5
- "description": "Thin provider for OMP/Pi — model list + dumb relay proxy + log; host pi-ai owns thinking/normalization",
6
- "main": "extensions/index.ts",
7
- "types": "src/index.ts",
8
- "keywords": [
9
- "pi-package",
10
- "pi-extension",
11
- "oh-my-pi",
12
- "omp",
13
- "free-models",
14
- "opencode",
15
- "kilocode",
16
- "ai-models",
17
- "relay"
18
- ],
19
- "author": "trefeon",
20
- "license": "MIT",
21
- "repository": {
22
- "type": "git",
23
- "url": "git+https://github.com/trefeon/pi-freeflow.git"
24
- },
25
- "homepage": "https://github.com/trefeon/pi-freeflow#readme",
26
- "engines": {
27
- "node": ">=22.19.0"
28
- },
29
- "omp": {
30
- "extensions": [
31
- "./extensions"
32
- ]
33
- },
34
- "pi": {
35
- "extensions": [
36
- "./extensions"
37
- ]
38
- },
39
- "files": [
40
- "extensions",
41
- "src",
42
- "README.md",
43
- "CHANGELOG.md",
44
- "LICENSE"
45
- ],
46
- "scripts": {
47
- "test": "node --experimental-strip-types --import ./test/setup.mjs --test --test-concurrency=1 test/**/*.test.ts",
48
- "typecheck": "tsc --noEmit",
49
- "smoke": "node --experimental-strip-types -e \"import('./extensions/index.ts').then(() => console.log('✓ Smoke test passed: extensions/index.ts loaded successfully')).catch(err => { console.error(err); process.exit(1); })\"",
50
- "changeset": "changeset",
51
- "docs:dev": "vitepress dev docs",
52
- "docs:build": "vitepress build docs"
53
- },
54
- "devDependencies": {
55
- "@changesets/cli": "^2.27.0",
56
- "@types/node": "^22.13.9",
57
- "typescript": "^5.8.2",
58
- "vitepress": "^1.6.4"
59
- },
60
- "dependencies": {}
61
- }
1
+ {
2
+ "name": "pi-freeflow",
3
+ "type": "module",
4
+ "version": "1.11.0",
5
+ "description": "Thin provider for OMP/Pi — model list + dumb relay proxy + log; host pi-ai owns thinking/normalization",
6
+ "main": "extensions/index.ts",
7
+ "types": "src/index.ts",
8
+ "keywords": [
9
+ "pi-package",
10
+ "pi-extension",
11
+ "oh-my-pi",
12
+ "omp",
13
+ "free-models",
14
+ "opencode",
15
+ "kilocode",
16
+ "ai-models",
17
+ "relay"
18
+ ],
19
+ "author": "trefeon",
20
+ "license": "MIT",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/trefeon/pi-freeflow.git"
24
+ },
25
+ "homepage": "https://github.com/trefeon/pi-freeflow#readme",
26
+ "engines": {
27
+ "node": ">=22.19.0"
28
+ },
29
+ "omp": {
30
+ "extensions": [
31
+ "./extensions"
32
+ ]
33
+ },
34
+ "pi": {
35
+ "extensions": [
36
+ "./extensions"
37
+ ]
38
+ },
39
+ "files": [
40
+ "extensions",
41
+ "src",
42
+ "README.md",
43
+ "CHANGELOG.md",
44
+ "LICENSE"
45
+ ],
46
+ "scripts": {
47
+ "test": "node --experimental-strip-types --import ./test/setup.mjs --test --test-concurrency=1 test/**/*.test.ts",
48
+ "typecheck": "tsc --noEmit",
49
+ "smoke": "node --experimental-strip-types -e \"import('./extensions/index.ts').then(() => console.log('✓ Smoke test passed: extensions/index.ts loaded successfully')).catch(err => { console.error(err); process.exit(1); })\"",
50
+ "changeset": "changeset",
51
+ "docs:dev": "vitepress dev docs",
52
+ "docs:build": "vitepress build docs"
53
+ },
54
+ "devDependencies": {
55
+ "@changesets/cli": "^2.27.0",
56
+ "@types/node": "^22.13.9",
57
+ "typescript": "^5.8.2",
58
+ "vitepress": "^1.6.4"
59
+ },
60
+ "dependencies": {}
61
+ }
package/src/client.ts CHANGED
@@ -5,7 +5,8 @@
5
5
  * at 127.0.0.1:28180 (or spawns one if none is alive), registers a lease, and
6
6
  * renews it with a heartbeat while the session lives. When the session ends
7
7
  * the heartbeat stops; the daemon drops the lease after its TTL and retires
8
- * once no client holds a live lease and no request has been proxied recently.
8
+ * once no client holds a live lease for a grace window with nothing in flight
9
+ * (request-idleness alone never retires it).
9
10
  */
10
11
 
11
12
  import { randomUUID } from "node:crypto";
package/src/config.ts CHANGED
@@ -206,7 +206,7 @@ export const DAEMON_TTL_MS_ENV = DATA_DIR_ENV.replace("_DATA_DIR", "_DAEMON_TTL_
206
206
  export const DAEMON_HEARTBEAT_MS_ENV = DATA_DIR_ENV.replace("_DATA_DIR", "_DAEMON_HEARTBEAT_MS");
207
207
  /** Daemon GC sweep interval (ms). */
208
208
  export const DAEMON_GC_MS_ENV = DATA_DIR_ENV.replace("_DATA_DIR", "_DAEMON_GC_MS");
209
- /** Idle grace after the last request before a lease-less daemon exits (ms). */
209
+ /** Zero-lease persistence window before a lease-less daemon exits (ms). */
210
210
  export const DAEMON_GRACE_MS_ENV = DATA_DIR_ENV.replace("_DATA_DIR", "_DAEMON_GRACE_MS");
211
211
  /** Max time a client waits for a freshly spawned daemon to answer /_health (ms). */
212
212
  export const DAEMON_READY_TIMEOUT_MS_ENV = DATA_DIR_ENV.replace("_DATA_DIR", "_DAEMON_READY_TIMEOUT_MS");
package/src/daemon.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * Spawned as a separate OS process by src/client.ts so the local proxy survives
5
5
  * the OMP/Pi session that started it. Owns port 28180, serves the proxy plus
6
6
  * the client lease/control endpoints, and retires itself once no client holds
7
- * a live lease and no request has been proxied recently.
7
+ * a live lease (request-idleness alone never retires it).
8
8
  *
9
9
  * Run directly: `node --experimental-strip-types src/daemon.ts` (or `bun src/daemon.ts`).
10
10
  */
@@ -113,18 +113,21 @@ export async function runDaemon(): Promise<void> {
113
113
  process.exit(1);
114
114
  }
115
115
 
116
- // lastActivityAt is initialized AT BIND TIME: a freshly started daemon with
117
- // zero leases must never be GC'd during the parent's readiness-poll window.
116
+ // lastActivityAt is still seeded AT BIND TIME for the /_health snapshot, but
117
+ // retirement no longer reads it only the zero-lease persistence window
118
+ // gates the GC, so a fresh spawn always survives its re-attach window.
118
119
  touchActivity();
119
120
  syncRelayStateFromDisk();
120
121
  void seedCatalog();
121
122
 
123
+ // Retire only when zero leases persist past the grace window with nothing
124
+ // in flight — never for request-idleness.
122
125
  startLeaseGC({
123
126
  ttlMs: DAEMON_TTL_MS,
124
127
  gcMs: DAEMON_GC_MS,
125
128
  graceMs: DAEMON_GRACE_MS,
126
129
  getActiveRequests,
127
- onIdle: () => retire("no clients and idle"),
130
+ onIdle: () => retire("no clients"),
128
131
  });
129
132
 
130
133
  logInfo(`pi-freeflow daemon v${PKG_VERSION} listening on http://${HOST}:${PORT}`);
package/src/lease.ts CHANGED
@@ -2,12 +2,13 @@
2
2
  * Client lease registry for the detached pi-freeflow proxy daemon.
3
3
  *
4
4
  * Each OMP/Pi session is a client that registers a lease and renews it with a
5
- * heartbeat while alive. The daemon drops expired leases and, once NO client
6
- * holds a live lease AND no request has been proxied recently, retires itself.
5
+ * heartbeat while alive. The daemon drops expired leases and retires itself
6
+ * once NO client holds a live lease: the empty state must persist for the
7
+ * grace window (a fresh spawn's clients re-attach within seconds) with
8
+ * nothing in flight. Request-idleness alone NEVER retires the daemon.
7
9
  *
8
- * The request-touch (`lastActivityAt`) is the fallback for legacy clients that
9
- * never heartbeated: any proxied request counts as a live user, so the daemon
10
- * is never idle-killed while a session is actually using it.
10
+ * The request-touch (`lastActivityAt`) is still recorded and surfaced in
11
+ * /_health for observability, but it no longer gates retirement.
11
12
  */
12
13
 
13
14
  export interface LeaseOptions {
@@ -15,7 +16,7 @@ export interface LeaseOptions {
15
16
  ttlMs: number;
16
17
  /** GC sweep interval (ms). */
17
18
  gcMs: number;
18
- /** Idle grace after the last proxied request before a lease-less daemon exits (ms). */
19
+ /** Zero-lease persistence window (ms): how long leases must stay empty before a lease-less daemon exits. */
19
20
  graceMs: number;
20
21
  /** Current in-flight proxied requests — daemon never exits mid-stream. */
21
22
  getActiveRequests: () => number;
@@ -26,6 +27,8 @@ export interface LeaseOptions {
26
27
  const leases = new Map<string, number>();
27
28
  let lastActivityAt = Date.now();
28
29
  let gcTimer: ReturnType<typeof setInterval> | null = null;
30
+ /** First sweep timestamp at which leases were observed empty; null while any lease exists. */
31
+ let emptySince: number | null = null;
29
32
 
30
33
  /** Register or refresh a client lease. */
31
34
  export function registerClient(clientId: string): void {
@@ -68,7 +71,7 @@ export function getLastActivityAt(): number {
68
71
 
69
72
  /**
70
73
  * Start the lease GC sweep. Prunes expired leases and, when no client holds a
71
- * lease, nothing is in flight, and no request has been proxied within the
74
+ * lease, nothing is in flight, and the lease-less state has persisted for the
72
75
  * grace window, invokes `onIdle` (the daemon retires). Idempotent — a second
73
76
  * call is a no-op.
74
77
  */
@@ -81,11 +84,15 @@ export function startLeaseGC(opts: LeaseOptions): void {
81
84
  leases.delete(id);
82
85
  }
83
86
  }
84
- if (
85
- leases.size === 0 &&
86
- opts.getActiveRequests() === 0 &&
87
- now - lastActivityAt > opts.graceMs
88
- ) {
87
+ if (leases.size > 0) {
88
+ emptySince = null;
89
+ return;
90
+ }
91
+ if (emptySince === null) {
92
+ emptySince = now;
93
+ return;
94
+ }
95
+ if (opts.getActiveRequests() === 0 && now - emptySince >= opts.graceMs) {
89
96
  stopLeaseGC();
90
97
  opts.onIdle();
91
98
  }
@@ -104,5 +111,6 @@ export function stopLeaseGC(): void {
104
111
  export function _resetLeaseStateForTest(): void {
105
112
  leases.clear();
106
113
  lastActivityAt = Date.now();
114
+ emptySince = null;
107
115
  stopLeaseGC();
108
116
  }