flowviant 0.59.0 → 0.60.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/bin/lib/work.mjs +41 -7
- package/package.json +1 -1
package/bin/lib/work.mjs
CHANGED
|
@@ -337,6 +337,32 @@ export function createWorkManager({ repoRoot, baseDir, baseRef, getMcpUrl, getLe
|
|
|
337
337
|
* network, and a teammate's push being visible within three minutes is the
|
|
338
338
|
* same promise the rest of the product makes. */
|
|
339
339
|
const WORKTREE_FETCH_MS = 3 * 60_000;
|
|
340
|
+
/**
|
|
341
|
+
* WHERE EACH SESSION WORKS, learned from the turns we are handed.
|
|
342
|
+
*
|
|
343
|
+
* Every other beat — the worktree sweep, ship, the preview re-check — has to
|
|
344
|
+
* ask the SAME directory the turn ran in, and only the turn job carries
|
|
345
|
+
* `place`. Caching it here is what keeps them agreeing without a second
|
|
346
|
+
* server→daemon field: a session absent from this map has never run a turn,
|
|
347
|
+
* and its own id is the right answer for that case anyway (it is the default
|
|
348
|
+
* place, and a session with no turn has no worktree either).
|
|
349
|
+
*
|
|
350
|
+
* A tab standing in the CHECKOUT is the case this exists for: its directory
|
|
351
|
+
* is not `sessions/<id>` and never will be, so a sweep that assumed the
|
|
352
|
+
* default would measure a directory that does not exist and report nothing —
|
|
353
|
+
* which is exactly why "I still do not see a preview URL" was true of a tab
|
|
354
|
+
* opened in the checkout.
|
|
355
|
+
*/
|
|
356
|
+
const sessionPlaces = new Map();
|
|
357
|
+
const placeOf = (sessionId) => sessionPlaces.get(sessionId) ?? sessionId;
|
|
358
|
+
/** The DIRECTORY a session works in. Every path that used to build
|
|
359
|
+
* `sessions/<id>` by hand goes through here, or a tab in the checkout gets
|
|
360
|
+
* measured against a directory that does not exist. */
|
|
361
|
+
const placeDir = (sessionId) => {
|
|
362
|
+
const place = placeOf(sessionId);
|
|
363
|
+
return place === REPO_PLACE ? repoRoot : join(baseDir, 'sessions', place);
|
|
364
|
+
};
|
|
365
|
+
|
|
340
366
|
let lastWorktreeSweep = 0;
|
|
341
367
|
let lastWorktreeFetch = 0;
|
|
342
368
|
let sweepingWorktrees = false;
|
|
@@ -358,8 +384,11 @@ export function createWorkManager({ repoRoot, baseDir, baseRef, getMcpUrl, getLe
|
|
|
358
384
|
}
|
|
359
385
|
};
|
|
360
386
|
const sessionWorktreeReport = (sessionId) => {
|
|
361
|
-
|
|
362
|
-
|
|
387
|
+
// The session's PLACE, not its name: a tab in the checkout is measured in
|
|
388
|
+
// the checkout, and a tab sharing another tab's worktree is measured there.
|
|
389
|
+
const place = placeOf(sessionId);
|
|
390
|
+
if (place !== REPO_PLACE && !isSafePathSegment(place)) return null;
|
|
391
|
+
const wt = placeDir(sessionId);
|
|
363
392
|
const d = worktreeDiff(wt, baseRef);
|
|
364
393
|
if (!d) return null;
|
|
365
394
|
// WHAT IS LISTENING in this worktree, attributed by the CWD of the process
|
|
@@ -585,7 +614,7 @@ export function createWorkManager({ repoRoot, baseDir, baseRef, getMcpUrl, getLe
|
|
|
585
614
|
void (async () => {
|
|
586
615
|
try {
|
|
587
616
|
if (!(await claimPreview(sessionId))) return; // somebody else has it
|
|
588
|
-
const wt =
|
|
617
|
+
const wt = placeDir(sessionId);
|
|
589
618
|
// RE-VALIDATE the attribution here, not just the liveness. The server
|
|
590
619
|
// checked this port against a report up to a minute old; more
|
|
591
620
|
// importantly, checking `listenersIn` again is what keeps the answer
|
|
@@ -773,7 +802,7 @@ export function createWorkManager({ repoRoot, baseDir, baseRef, getMcpUrl, getLe
|
|
|
773
802
|
void (async () => {
|
|
774
803
|
try {
|
|
775
804
|
if (!(await claimDevRun(sessionId))) return; // somebody else has it
|
|
776
|
-
const wt =
|
|
805
|
+
const wt = placeDir(sessionId);
|
|
777
806
|
const r = await startDevServer({
|
|
778
807
|
sessionId,
|
|
779
808
|
worktree: wt,
|
|
@@ -845,7 +874,7 @@ export function createWorkManager({ repoRoot, baseDir, baseRef, getMcpUrl, getLe
|
|
|
845
874
|
const adoptDevRuns = (activeIds) => {
|
|
846
875
|
let adopted = 0;
|
|
847
876
|
for (const entry of reapOrphanDevRuns(activeIds, note)) {
|
|
848
|
-
const wt =
|
|
877
|
+
const wt = placeDir(entry.sessionId);
|
|
849
878
|
// The recorded cwd must still be this session's worktree. A recycled pid
|
|
850
879
|
// pointing anywhere else is somebody else's process.
|
|
851
880
|
if (entry.cwd !== wt) continue;
|
|
@@ -1647,6 +1676,9 @@ export function createWorkManager({ repoRoot, baseDir, baseRef, getMcpUrl, getLe
|
|
|
1647
1676
|
// Serialized by PLACE: two tabs sharing a worktree take turns in it
|
|
1648
1677
|
// rather than editing the same files at the same time.
|
|
1649
1678
|
const place = job.place || job.sessionId;
|
|
1679
|
+
// Remembered for every other beat — the sweep, ship, the preview
|
|
1680
|
+
// re-check — so they all ask the same directory this turn runs in.
|
|
1681
|
+
sessionPlaces.set(job.sessionId, place);
|
|
1650
1682
|
chainFor(place, async () => {
|
|
1651
1683
|
try {
|
|
1652
1684
|
const tries = workAttempts.get(job.id) ?? 0;
|
|
@@ -2273,7 +2305,9 @@ export function createWorkManager({ repoRoot, baseDir, baseRef, getMcpUrl, getLe
|
|
|
2273
2305
|
* so the recorded name is the fallback — the one case where the name
|
|
2274
2306
|
* is the only thing there is.
|
|
2275
2307
|
*/
|
|
2276
|
-
|
|
2308
|
+
// The session's PLACE — the directory it actually works in.
|
|
2309
|
+
const shipPlace = placeOf(job.sessionId);
|
|
2310
|
+
const wt = shipPlace === REPO_PLACE ? repoRoot : join(baseDir, 'sessions', shipPlace);
|
|
2277
2311
|
let branch = `session/${job.sessionId}`;
|
|
2278
2312
|
let detached = false;
|
|
2279
2313
|
if (existsSync(wt)) {
|
|
@@ -2459,7 +2493,7 @@ export function createWorkManager({ repoRoot, baseDir, baseRef, getMcpUrl, getLe
|
|
|
2459
2493
|
ok(`${c.cyan('ship')} ${c.dim(`— ${commits.length} commit${commits.length === 1 ? '' : 's'} on main`)}`);
|
|
2460
2494
|
return;
|
|
2461
2495
|
}
|
|
2462
|
-
const dir =
|
|
2496
|
+
const dir = placeWtFor(shipPlace);
|
|
2463
2497
|
if (!dir) {
|
|
2464
2498
|
await done({
|
|
2465
2499
|
ok: false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flowviant",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.60.0",
|
|
4
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": {
|