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 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 self-updates at startup and when idle (`FLOWVIANT_NO_UPDATE=1` makes it nag-only; `flowviant update` updates now).
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 self-updates
20
- * on its own — at startup and when idle so it stays current without restarts
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`.
@@ -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. When it
10
- * can't install (npx cache, or auto off) it nags with the exact command.
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
- const child = spawn(process.execPath, process.argv.slice(1), {
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: { ...process.env, FLOWVIANT_REEXEC: '1' },
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.57.0",
4
- "description": "Run your own coding CLIs as build agents for Flowviant \u2014 Claude Code, Codex or Antigravity, on your own credentials. Holds your sessions, keeps a worktree per tab, and ships branches on your word.",
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"