flowviant 0.97.0 → 0.100.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 +15 -13
- package/bin/cli.mjs +108 -28
- package/bin/lib/agentPlan.mjs +159 -37
- package/bin/lib/artifacts.mjs +136 -26
- package/bin/lib/authReject.mjs +32 -0
- package/bin/lib/authproxy.mjs +52 -16
- package/bin/lib/childEnv.mjs +33 -1
- package/bin/lib/claude.mjs +166 -71
- package/bin/lib/config.mjs +36 -9
- package/bin/lib/credentials.mjs +29 -5
- package/bin/lib/deploy.mjs +364 -30
- package/bin/lib/desktopContract.mjs +227 -0
- package/bin/lib/env.mjs +130 -13
- package/bin/lib/fleet.mjs +121 -30
- package/bin/lib/git.mjs +131 -3
- package/bin/lib/hooks/readGuard.mjs +24 -0
- package/bin/lib/instance.mjs +129 -14
- package/bin/lib/knowledge.mjs +180 -60
- package/bin/lib/landed.mjs +38 -30
- package/bin/lib/launchCommand.mjs +23 -0
- package/bin/lib/listeners.mjs +211 -0
- package/bin/lib/localSessions.mjs +29 -4
- package/bin/lib/login.mjs +36 -12
- package/bin/lib/machines.mjs +24 -14
- package/bin/lib/mcp-cli.mjs +4 -3
- package/bin/lib/preflight.mjs +2 -1
- package/bin/lib/preview.mjs +95 -6
- package/bin/lib/printable.mjs +42 -0
- package/bin/lib/procRegistry.mjs +65 -0
- package/bin/lib/processes.mjs +20 -7
- package/bin/lib/projectTools.mjs +248 -0
- package/bin/lib/prompts.mjs +16 -11
- package/bin/lib/resources.mjs +46 -4
- package/bin/lib/runtimeOfferings.mjs +75 -0
- package/bin/lib/runtimes.mjs +71 -9
- package/bin/lib/shipMerge.mjs +8 -4
- package/bin/lib/trace.mjs +16 -6
- package/bin/lib/update.mjs +114 -25
- package/bin/lib/work.mjs +1192 -4584
- package/bin/lib/workAgentMerges.mjs +504 -0
- package/bin/lib/workAgentPlans.mjs +447 -0
- package/bin/lib/workAgentReview.mjs +702 -0
- package/bin/lib/workAgentTurns.mjs +1151 -0
- package/bin/lib/workDiffs.mjs +107 -0
- package/bin/lib/workPreviews.mjs +351 -0
- package/bin/lib/workProcesses.mjs +236 -0
- package/bin/lib/workPullRequests.mjs +313 -0
- package/bin/lib/workShip.mjs +385 -0
- package/bin/lib/worktreeDiff.mjs +99 -43
- package/package.json +2 -3
package/bin/lib/update.mjs
CHANGED
|
@@ -30,7 +30,14 @@
|
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
32
|
import { execFileSync, spawn } from 'node:child_process';
|
|
33
|
-
import {
|
|
33
|
+
import { createHash } from 'node:crypto';
|
|
34
|
+
import { createReadStream, createWriteStream } from 'node:fs';
|
|
35
|
+
import { chmod, mkdtemp, open, rename, rm } from 'node:fs/promises';
|
|
36
|
+
import { dirname, join } from 'node:path';
|
|
37
|
+
import { platform, arch } from 'node:process';
|
|
38
|
+
import { pipeline } from 'node:stream/promises';
|
|
39
|
+
import { Readable } from 'node:stream';
|
|
40
|
+
import { runningViaNpx, terminalCommand } from './launchCommand.mjs';
|
|
34
41
|
import { VERSION } from './config.mjs';
|
|
35
42
|
import { note, ok, warn } from './ui.mjs';
|
|
36
43
|
|
|
@@ -45,22 +52,66 @@ export function cmpVersion(a, b) {
|
|
|
45
52
|
return 0;
|
|
46
53
|
}
|
|
47
54
|
|
|
48
|
-
/**
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
/** Bun marks compiled entrypoints with a virtual /$bunfs/ script path. */
|
|
56
|
+
export function runningCompiledBinary() {
|
|
57
|
+
return Boolean(process.versions.bun && process.argv[1]?.startsWith('/$bunfs/'));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const RELEASE_ORIGIN = 'https://api.flowviant.com';
|
|
61
|
+
|
|
62
|
+
/** Download and verify before replacing the executable. Exposed for temp-dir tests. */
|
|
63
|
+
export async function installBinaryUpdate({
|
|
64
|
+
executable = process.execPath,
|
|
65
|
+
target = `${platform === 'darwin' ? 'darwin' : platform}-${arch}`,
|
|
66
|
+
fetchImpl = fetch,
|
|
67
|
+
origin = RELEASE_ORIGIN,
|
|
68
|
+
minimumVersion = null,
|
|
69
|
+
} = {}) {
|
|
70
|
+
if (!/^(linux|darwin)-(x64|arm64)$/.test(target)) throw new Error(`unsupported binary target ${target}`);
|
|
71
|
+
const manifestResponse = await fetchImpl(`${origin}/dl/latest.json`);
|
|
72
|
+
if (!manifestResponse.ok) throw new Error(`release manifest: HTTP ${manifestResponse.status}`);
|
|
73
|
+
const manifest = await manifestResponse.json();
|
|
74
|
+
const version = manifest?.version;
|
|
75
|
+
const file = manifest?.files?.[target];
|
|
76
|
+
if (!/^\d+\.\d+\.\d+$/.test(version) ||
|
|
77
|
+
file?.name !== `flowviant-${version}-${target}` ||
|
|
78
|
+
!/^[a-f0-9]{64}$/.test(file?.sha256) ||
|
|
79
|
+
!Number.isSafeInteger(file?.bytes) || file.bytes <= 0) {
|
|
80
|
+
throw new Error('invalid release manifest');
|
|
81
|
+
}
|
|
82
|
+
if (minimumVersion && cmpVersion(version, minimumVersion) < 0) {
|
|
83
|
+
throw new Error(`release manifest still serves ${version}; waiting for ${minimumVersion}`);
|
|
84
|
+
}
|
|
85
|
+
const binaryResponse = await fetchImpl(`${origin}/dl/${version}/${file.name}`);
|
|
86
|
+
if (!binaryResponse.ok || !binaryResponse.body) throw new Error(`binary download: HTTP ${binaryResponse.status}`);
|
|
87
|
+
const parent = dirname(executable);
|
|
88
|
+
const temporaryDir = await mkdtemp(join(parent, '.flowviant-update-'));
|
|
89
|
+
const temporaryFile = join(temporaryDir, file.name);
|
|
58
90
|
try {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
91
|
+
await pipeline(Readable.fromWeb(binaryResponse.body), createWriteStream(temporaryFile, { mode: 0o755 }));
|
|
92
|
+
const digest = createHash('sha256');
|
|
93
|
+
let bytes = 0;
|
|
94
|
+
for await (const chunk of createReadStream(temporaryFile)) {
|
|
95
|
+
digest.update(chunk);
|
|
96
|
+
bytes += chunk.length;
|
|
97
|
+
}
|
|
98
|
+
if (bytes !== file.bytes || digest.digest('hex') !== file.sha256) {
|
|
99
|
+
throw new Error('downloaded binary failed SHA-256 or size verification');
|
|
100
|
+
}
|
|
101
|
+
await chmod(temporaryFile, 0o755);
|
|
102
|
+
const handle = await open(temporaryFile, 'r');
|
|
103
|
+
try { await handle.sync(); } finally { await handle.close(); }
|
|
104
|
+
await rename(temporaryFile, executable);
|
|
105
|
+
// The rename has committed. Directory fsync is best effort on filesystems
|
|
106
|
+
// that do not support it; a failure here must not claim the binary survived.
|
|
107
|
+
try {
|
|
108
|
+
const dirHandle = await open(parent, 'r');
|
|
109
|
+
try { await dirHandle.sync(); } finally { await dirHandle.close(); }
|
|
110
|
+
} catch { /* rename already succeeded */ }
|
|
111
|
+
return version;
|
|
112
|
+
} finally {
|
|
113
|
+
await rm(temporaryDir, { recursive: true, force: true }).catch(() => {});
|
|
62
114
|
}
|
|
63
|
-
return /\bnpx\b/.test(ua) || /[\\/]_npx[\\/]/.test(argv1) || /[\\/]_npx[\\/]/.test(self);
|
|
64
115
|
}
|
|
65
116
|
|
|
66
117
|
/**
|
|
@@ -70,7 +121,7 @@ export function runningViaNpx() {
|
|
|
70
121
|
* this process alive only as a thin proxy waiting on the child, so the user's
|
|
71
122
|
* shell stays attached to one foreground process.
|
|
72
123
|
*/
|
|
73
|
-
function reexec(teardown, { viaNpx = false, target = null } = {}) {
|
|
124
|
+
function reexec(teardown, { viaNpx = false, viaBinary = false, target = null } = {}) {
|
|
74
125
|
try {
|
|
75
126
|
teardown?.();
|
|
76
127
|
} catch {
|
|
@@ -84,7 +135,7 @@ function reexec(teardown, { viaNpx = false, target = null } = {}) {
|
|
|
84
135
|
// on npx's install prompt — the same rule FLOWVIANT_REEXEC keeps below.
|
|
85
136
|
const [cmd, args] = viaNpx
|
|
86
137
|
? ['npx', ['-y', 'flowviant@latest', ...process.argv.slice(2)]]
|
|
87
|
-
: [process.execPath, process.argv.slice(1)];
|
|
138
|
+
: [process.execPath, process.argv.slice(viaBinary ? 2 : 1)];
|
|
88
139
|
const child = spawn(cmd, args, {
|
|
89
140
|
stdio: 'inherit',
|
|
90
141
|
// MARK THE CHILD AS A RESTART, not as a person typing `flowviant`.
|
|
@@ -100,6 +151,11 @@ function reexec(teardown, { viaNpx = false, target = null } = {}) {
|
|
|
100
151
|
env: {
|
|
101
152
|
...process.env,
|
|
102
153
|
FLOWVIANT_REEXEC: '1',
|
|
154
|
+
// WHICH DAEMON THE SUCCESSOR IS REPLACING. Under npx the successor's
|
|
155
|
+
// parent is `npm exec`, not us, so the instance lock's ppid check read us
|
|
156
|
+
// as a rival and the new daemon SIGTERMed this proxy. instance.mjs adopts
|
|
157
|
+
// the lock from this pid when it is really an ancestor of the successor.
|
|
158
|
+
FLOWVIANT_REEXEC_FROM: String(process.pid),
|
|
103
159
|
// WHAT WE RESTARTED IN ORDER TO BECOME. The successor compares its own
|
|
104
160
|
// VERSION against this: if it came back still short, the update did not
|
|
105
161
|
// take (a registry serving a stale `latest`, an npx cache that refused to
|
|
@@ -108,6 +164,7 @@ function reexec(teardown, { viaNpx = false, target = null } = {}) {
|
|
|
108
164
|
// the global path there is no install step whose failure would throw and
|
|
109
165
|
// stop it.
|
|
110
166
|
...(target ? { FLOWVIANT_UPDATE_TARGET: target } : {}),
|
|
167
|
+
FLOWVIANT_UPDATE_FROM: VERSION,
|
|
111
168
|
},
|
|
112
169
|
});
|
|
113
170
|
child.on('exit', (code) => process.exit(code ?? 0));
|
|
@@ -133,9 +190,18 @@ function installLatest() {
|
|
|
133
190
|
|
|
134
191
|
/** `flowviant update` — explicit, manual update. Does not re-exec into a daemon
|
|
135
192
|
* (the user ran a one-shot command); it installs and tells them to relaunch. */
|
|
136
|
-
export function runUpdateCommand() {
|
|
193
|
+
export async function runUpdateCommand() {
|
|
194
|
+
if (runningCompiledBinary()) {
|
|
195
|
+
try {
|
|
196
|
+
const version = await installBinaryUpdate();
|
|
197
|
+
ok(`updated to ${version}. Relaunch \`flowviant\` to run the new version.`);
|
|
198
|
+
} catch (e) {
|
|
199
|
+
warn(`update failed (${e?.message ?? e}) — running binary untouched.`);
|
|
200
|
+
}
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
137
203
|
if (runningViaNpx()) {
|
|
138
|
-
note('running via npx —
|
|
204
|
+
note('running via npx — relaunch `flowviant` to get the newest.');
|
|
139
205
|
return;
|
|
140
206
|
}
|
|
141
207
|
try {
|
|
@@ -143,7 +209,7 @@ export function runUpdateCommand() {
|
|
|
143
209
|
installLatest();
|
|
144
210
|
ok('updated. Relaunch `flowviant` to run the new version.');
|
|
145
211
|
} catch (e) {
|
|
146
|
-
warn(`update failed (${e?.message ?? e}). Try:
|
|
212
|
+
warn(`update failed (${e?.message ?? e}). Try: ${terminalCommand('update')}`);
|
|
147
213
|
}
|
|
148
214
|
}
|
|
149
215
|
|
|
@@ -165,13 +231,14 @@ const INSTALL_RETRY_MS = 15 * 60_000;
|
|
|
165
231
|
* React to the server's {latest, min} signal from a roster poll.
|
|
166
232
|
* @returns true if it kicked off a self-update + re-exec (caller must stop).
|
|
167
233
|
*/
|
|
168
|
-
export function handleVersionSignal({ latest, min, autoUpdate, safeToUpdate, teardown }) {
|
|
234
|
+
export async function handleVersionSignal({ latest, min, autoUpdate, safeToUpdate, teardown }) {
|
|
169
235
|
const cur = VERSION;
|
|
170
236
|
const belowMin = min && cmpVersion(cur, min) < 0;
|
|
171
237
|
const belowLatest = latest && cmpVersion(cur, latest) < 0;
|
|
172
238
|
if (!belowMin && !belowLatest) return false; // current — nothing to do
|
|
173
239
|
const target = latest || min;
|
|
174
240
|
const npx = runningViaNpx();
|
|
241
|
+
const binary = runningCompiledBinary();
|
|
175
242
|
const wantInstall = belowMin || autoUpdate;
|
|
176
243
|
|
|
177
244
|
// A RESTART THAT DID NOT TAKE must not be tried again on the next poll. The
|
|
@@ -184,13 +251,35 @@ export function handleVersionSignal({ latest, min, autoUpdate, safeToUpdate, tea
|
|
|
184
251
|
naggedFor = target;
|
|
185
252
|
warn(
|
|
186
253
|
`restarted to pick up ${target} but came back as ${cur} — staying put. Update by hand: ${
|
|
187
|
-
|
|
254
|
+
binary ? 'run `flowviant update`' : npx ? 'relaunch `flowviant`' : `run \`${terminalCommand('update')}\``
|
|
188
255
|
}.`
|
|
189
256
|
);
|
|
190
257
|
}
|
|
191
258
|
return false;
|
|
192
259
|
}
|
|
193
260
|
|
|
261
|
+
if (wantInstall && binary) {
|
|
262
|
+
if (!safeToUpdate) {
|
|
263
|
+
if (naggedFor !== target) {
|
|
264
|
+
naggedFor = target;
|
|
265
|
+
note(`flowviant ${cur} → ${target} available — self-updating once no turn is running.`);
|
|
266
|
+
}
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
if (Date.now() - lastInstallFailAt < INSTALL_RETRY_MS) return false;
|
|
270
|
+
try {
|
|
271
|
+
note(`flowviant ${cur} → ${target}: downloading binary…`);
|
|
272
|
+
const installed = await installBinaryUpdate({ minimumVersion: target });
|
|
273
|
+
ok('updated — restarting into the new version.');
|
|
274
|
+
reexec(teardown, { viaBinary: true, target: installed });
|
|
275
|
+
return true;
|
|
276
|
+
} catch (e) {
|
|
277
|
+
lastInstallFailAt = Date.now();
|
|
278
|
+
warn(`binary self-update failed (${e?.message ?? e}) — running binary untouched; retrying in 15m.`);
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
194
283
|
// UNDER NPX THERE IS NOTHING TO INSTALL — the relaunch IS the update, because
|
|
195
284
|
// `npx -y flowviant@latest` resolves `latest` against the registry. Same two
|
|
196
285
|
// gates as the global path: the operator's AUTO_UPDATE choice, and an idle
|
|
@@ -210,7 +299,7 @@ export function handleVersionSignal({ latest, min, autoUpdate, safeToUpdate, tea
|
|
|
210
299
|
return true;
|
|
211
300
|
}
|
|
212
301
|
|
|
213
|
-
if (wantInstall && !npx) {
|
|
302
|
+
if (wantInstall && !npx && !binary) {
|
|
214
303
|
if (!safeToUpdate) {
|
|
215
304
|
// Outdated but a turn is running — wait until the machine is quiet. Nag
|
|
216
305
|
// once meanwhile.
|
|
@@ -260,7 +349,7 @@ export function handleVersionSignal({ latest, min, autoUpdate, safeToUpdate, tea
|
|
|
260
349
|
} catch (e) {
|
|
261
350
|
lastInstallFailAt = Date.now();
|
|
262
351
|
warn(
|
|
263
|
-
`self-update failed (${e?.message ?? e}) — retrying in 15m; to fix it now:
|
|
352
|
+
`self-update failed (${e?.message ?? e}) — retrying in 15m; to fix it now: ${terminalCommand('update')}`
|
|
264
353
|
);
|
|
265
354
|
return false;
|
|
266
355
|
}
|
|
@@ -269,7 +358,7 @@ export function handleVersionSignal({ latest, min, autoUpdate, safeToUpdate, tea
|
|
|
269
358
|
// Can't or won't auto-install → nag once per target version.
|
|
270
359
|
if (naggedFor !== target) {
|
|
271
360
|
naggedFor = target;
|
|
272
|
-
const how =
|
|
361
|
+
const how = binary ? 'run `flowviant update`' : npx ? 'relaunch `flowviant`' : `run \`${terminalCommand('update')}\``;
|
|
273
362
|
if (belowMin) {
|
|
274
363
|
warn(`flowviant ${cur} is below the minimum ${min} — live mode may not work. Update: ${how}.`);
|
|
275
364
|
} else {
|