flowviant 0.57.0 → 0.58.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/README.md +1 -1
- package/bin/cli.mjs +5 -2
- package/bin/lib/update.mjs +94 -6
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -41,7 +41,7 @@ Prefer an explicit token? Create a machine credential in the app and pass it dir
|
|
|
41
41
|
FLOWVIANT_FLEET=fva_… npx flowviant@latest
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
Launch with `@latest` so each start pulls the newest published version — a bare `npx flowviant` can reuse a stale cache. A running daemon also
|
|
44
|
+
Launch with `@latest` so each start pulls the newest published version — a bare `npx flowviant` can reuse a stale cache. A running daemon also keeps itself current: from **0.58.0** it restarts itself through `npx flowviant@latest` when a new version ships, so npx launches stay up to date the same way a global install does. (Before 0.58.0 that was only true of a global install — under npx the daemon printed a notice and stayed put, which is how machines ended up sitting several releases back.) It only ever restarts when no turn is running. `FLOWVIANT_NO_UPDATE=1` makes it nag-only; `flowviant update` updates now.
|
|
45
45
|
|
|
46
46
|
## Sessions
|
|
47
47
|
|
package/bin/cli.mjs
CHANGED
|
@@ -16,8 +16,11 @@
|
|
|
16
16
|
* sat against an empty tool list, which is a worse failure than not starting.
|
|
17
17
|
*
|
|
18
18
|
* Launch with `@latest` so each start pulls the newest published version (bare
|
|
19
|
-
* `npx flowviant` can reuse a stale cache). A running daemon also
|
|
20
|
-
*
|
|
19
|
+
* `npx flowviant` can reuse a stale cache). A running daemon also keeps itself
|
|
20
|
+
* current — at startup and when idle, never mid-turn. Since 0.58.0 that is true
|
|
21
|
+
* under NPX too, by relaunching through `npx flowviant@latest`; before it, the
|
|
22
|
+
* npx branch refused to install and only nagged, so npx launches — the way this
|
|
23
|
+
* README tells everyone to start — silently stayed on whatever was cached
|
|
21
24
|
* (FLOWVIANT_NO_UPDATE=1 makes it nag-only; `flowviant update` updates now).
|
|
22
25
|
* `flowviant stop` stops every daemon on this box — the answer to "is one even
|
|
23
26
|
* running?", which otherwise ends in a pid hunt through `ps`.
|
package/bin/lib/update.mjs
CHANGED
|
@@ -6,8 +6,27 @@
|
|
|
6
6
|
* version ships. The server reports {latest, min} on every roster poll; the
|
|
7
7
|
* daemon compares its own VERSION and, at a SAFE boundary (startup or idle —
|
|
8
8
|
* never mid-task), self-updates + re-execs. Below `min` it updates regardless
|
|
9
|
-
* (older protocol is known-broken); otherwise it honors AUTO_UPDATE.
|
|
10
|
-
*
|
|
9
|
+
* (older protocol is known-broken); otherwise it honors AUTO_UPDATE.
|
|
10
|
+
*
|
|
11
|
+
* NPX UPDATES TOO, SINCE 0.58.0 — and until then it never did, which is the
|
|
12
|
+
* whole reason this comment is longer than it was. `AUTO_UPDATE` is ON by
|
|
13
|
+
* default (`FLOWVIANT_NO_UPDATE !== '1'`), so the flag was never what held
|
|
14
|
+
* machines back: the npx branch was. It refused to install — correctly, since
|
|
15
|
+
* `npm i -g` lands where the running process will never look — and then only
|
|
16
|
+
* NAGGED A CONSOLE NOBODY READS. The README meanwhile told everyone to launch
|
|
17
|
+
* with `npx flowviant@latest` and promised that "a running daemon also
|
|
18
|
+
* self-updates", which was false for exactly the audience it was written for.
|
|
19
|
+
* The result, measured across one account's five machines on 2026-08-25:
|
|
20
|
+
* 0.48.3, 0.51.1, 0.51.2, 0.54.2 and 0.56.0, each frozen at whatever npx had
|
|
21
|
+
* cached the day it launched. The clincher was the 0.56.0 one — it polled that
|
|
22
|
+
* morning, saw LATEST 0.56.1, had AUTO_UPDATE on, and still did not move.
|
|
23
|
+
*
|
|
24
|
+
* The fix is that under npx the RE-EXEC IS THE UPDATE. There is nothing to
|
|
25
|
+
* install: relaunching through `npx -y flowviant@latest` makes npx resolve
|
|
26
|
+
* `latest` against the registry and fetch it (measured — it pulled 0.57.0 into
|
|
27
|
+
* a new cache entry beside the stale 0.54.2). So the npx path stops nagging and
|
|
28
|
+
* starts restarting itself, honouring AUTO_UPDATE and the same idle gate as the
|
|
29
|
+
* global path.
|
|
11
30
|
*/
|
|
12
31
|
|
|
13
32
|
import { execFileSync, spawn } from 'node:child_process';
|
|
@@ -51,13 +70,22 @@ export function runningViaNpx() {
|
|
|
51
70
|
* this process alive only as a thin proxy waiting on the child, so the user's
|
|
52
71
|
* shell stays attached to one foreground process.
|
|
53
72
|
*/
|
|
54
|
-
function reexec(teardown) {
|
|
73
|
+
function reexec(teardown, { viaNpx = false, target = null } = {}) {
|
|
55
74
|
try {
|
|
56
75
|
teardown?.();
|
|
57
76
|
} catch {
|
|
58
77
|
/* best-effort */
|
|
59
78
|
}
|
|
60
|
-
|
|
79
|
+
// UNDER NPX THE RE-EXEC IS THE UPDATE, so it must not re-run our own argv[1]:
|
|
80
|
+
// that path points into the npx cache entry holding the version we are trying
|
|
81
|
+
// to leave, and re-running it would reload the stale copy forever. Going back
|
|
82
|
+
// through `npx -y flowviant@latest` is what makes npx resolve `latest` against
|
|
83
|
+
// the registry and fetch the new one. `-y` because a restart must never stop
|
|
84
|
+
// on npx's install prompt — the same rule FLOWVIANT_REEXEC keeps below.
|
|
85
|
+
const [cmd, args] = viaNpx
|
|
86
|
+
? ['npx', ['-y', 'flowviant@latest', ...process.argv.slice(2)]]
|
|
87
|
+
: [process.execPath, process.argv.slice(1)];
|
|
88
|
+
const child = spawn(cmd, args, {
|
|
61
89
|
stdio: 'inherit',
|
|
62
90
|
// MARK THE CHILD AS A RESTART, not as a person typing `flowviant`.
|
|
63
91
|
// stdio is inherited, so the child sees two TTYs and believes a human is
|
|
@@ -69,11 +97,35 @@ function reexec(teardown) {
|
|
|
69
97
|
// is not a widening — the daemon serves exactly the credential it was
|
|
70
98
|
// already serving one second ago, and the question gets asked the next
|
|
71
99
|
// time a human starts it by hand.
|
|
72
|
-
env: {
|
|
100
|
+
env: {
|
|
101
|
+
...process.env,
|
|
102
|
+
FLOWVIANT_REEXEC: '1',
|
|
103
|
+
// WHAT WE RESTARTED IN ORDER TO BECOME. The successor compares its own
|
|
104
|
+
// VERSION against this: if it came back still short, the update did not
|
|
105
|
+
// take (a registry serving a stale `latest`, an npx cache that refused to
|
|
106
|
+
// move, a half-written global install) and it must NAG rather than
|
|
107
|
+
// restart again. Without this the npx path is a re-exec loop — and unlike
|
|
108
|
+
// the global path there is no install step whose failure would throw and
|
|
109
|
+
// stop it.
|
|
110
|
+
...(target ? { FLOWVIANT_UPDATE_TARGET: target } : {}),
|
|
111
|
+
},
|
|
73
112
|
});
|
|
74
113
|
child.on('exit', (code) => process.exit(code ?? 0));
|
|
75
114
|
}
|
|
76
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Did a restart that was supposed to land us on `target` fail to?
|
|
118
|
+
*
|
|
119
|
+
* True only when a PREVIOUS process handed us a target we are still below. A
|
|
120
|
+
* plain start has no marker, and a successful update is at or above it — so
|
|
121
|
+
* this is false in every case except the one it exists for.
|
|
122
|
+
*/
|
|
123
|
+
export function updateRestartFailed(target) {
|
|
124
|
+
const attempted = process.env.FLOWVIANT_UPDATE_TARGET;
|
|
125
|
+
if (!attempted) return false;
|
|
126
|
+
return cmpVersion(VERSION, attempted) < 0 && cmpVersion(target ?? attempted, attempted) <= 0;
|
|
127
|
+
}
|
|
128
|
+
|
|
77
129
|
/** Install @latest globally. Throws on failure (EACCES without sudo, offline…). */
|
|
78
130
|
function installLatest() {
|
|
79
131
|
execFileSync('npm', ['install', '-g', 'flowviant@latest'], { stdio: 'inherit' });
|
|
@@ -122,6 +174,42 @@ export function handleVersionSignal({ latest, min, autoUpdate, safeToUpdate, tea
|
|
|
122
174
|
const npx = runningViaNpx();
|
|
123
175
|
const wantInstall = belowMin || autoUpdate;
|
|
124
176
|
|
|
177
|
+
// A RESTART THAT DID NOT TAKE must not be tried again on the next poll. The
|
|
178
|
+
// global path is self-limiting (a failed `npm i -g` throws and lands in the
|
|
179
|
+
// 15-minute backoff), but the npx path has no install step to fail — it just
|
|
180
|
+
// relaunches, so a registry or cache that keeps serving the old version would
|
|
181
|
+
// loop this process forever, tearing down live turns every ten seconds.
|
|
182
|
+
if (updateRestartFailed(target)) {
|
|
183
|
+
if (naggedFor !== target) {
|
|
184
|
+
naggedFor = target;
|
|
185
|
+
warn(
|
|
186
|
+
`restarted to pick up ${target} but came back as ${cur} — staying put. Update by hand: ${
|
|
187
|
+
npx ? 'relaunch with `npx flowviant@latest`' : 'npm i -g flowviant@latest'
|
|
188
|
+
}.`
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// UNDER NPX THERE IS NOTHING TO INSTALL — the relaunch IS the update, because
|
|
195
|
+
// `npx -y flowviant@latest` resolves `latest` against the registry. Same two
|
|
196
|
+
// gates as the global path: the operator's AUTO_UPDATE choice, and an idle
|
|
197
|
+
// machine, because a re-exec mid-turn SIGTERMs the tab's CLI. No npm-view
|
|
198
|
+
// probe here: npx is about to ask the registry itself, and the loop guard
|
|
199
|
+
// above is what a stale answer runs into.
|
|
200
|
+
if (wantInstall && npx) {
|
|
201
|
+
if (!safeToUpdate) {
|
|
202
|
+
if (naggedFor !== target) {
|
|
203
|
+
naggedFor = target;
|
|
204
|
+
note(`flowviant ${cur} → ${target} available — restarting once no turn is running.`);
|
|
205
|
+
}
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
note(`flowviant ${cur} → ${target}: restarting through npx to pick it up…`);
|
|
209
|
+
reexec(teardown, { viaNpx: true, target });
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
|
|
125
213
|
if (wantInstall && !npx) {
|
|
126
214
|
if (!safeToUpdate) {
|
|
127
215
|
// Outdated but a turn is running — wait until the machine is quiet. Nag
|
|
@@ -167,7 +255,7 @@ export function handleVersionSignal({ latest, min, autoUpdate, safeToUpdate, tea
|
|
|
167
255
|
note(`flowviant ${cur} → ${published}: self-updating…`);
|
|
168
256
|
installLatest();
|
|
169
257
|
ok('updated — restarting into the new version.');
|
|
170
|
-
reexec(teardown);
|
|
258
|
+
reexec(teardown, { target: published });
|
|
171
259
|
return true;
|
|
172
260
|
} catch (e) {
|
|
173
261
|
lastInstallFailAt = Date.now();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flowviant",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Run your own coding CLIs as build agents for Flowviant
|
|
3
|
+
"version": "0.58.0",
|
|
4
|
+
"description": "Run your own coding CLIs as build agents for Flowviant — Claude Code, Codex or Antigravity, on your own credentials. Holds your sessions, keeps a worktree per tab, and ships branches on your word.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"flowviant": "bin/cli.mjs"
|