flowviant 0.125.28 → 0.125.30
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 +364 -0
- package/bin/cli.mjs +23 -0
- package/bin/lib/browserHandoff.mjs +25 -0
- package/bin/lib/desktopContract.mjs +8 -6
- package/bin/lib/environmentProcess.mjs +44 -0
- package/bin/lib/fleet.mjs +2 -0
- package/bin/lib/linuxDesktopBridge.mjs +62 -0
- package/bin/lib/managedBrowser.mjs +132 -30
- package/bin/lib/managedBrowserPage.mjs +12 -7
- package/bin/lib/mergeBatch.mjs +1 -1
- package/bin/lib/noWindowEnv.mjs +4 -2
- package/bin/lib/projectToolRuntimes.mjs +4 -2
- package/bin/lib/projectTools.mjs +2 -2
- package/bin/lib/repositoryPlaces.mjs +1 -1
- package/bin/lib/repositorySetup.mjs +15 -24
- package/bin/lib/reviewBridge.mjs +110 -0
- package/bin/lib/reviewDatabase.mjs +112 -0
- package/bin/lib/reviewDesktop.mjs +153 -0
- package/bin/lib/reviewEnvironment.mjs +286 -0
- package/bin/lib/reviewEnvironmentConfig.mjs +104 -0
- package/bin/lib/reviewEnvironmentMain.mjs +38 -0
- package/bin/lib/reviewImage.mjs +58 -0
- package/bin/lib/reviewMcpClient.mjs +69 -0
- package/bin/lib/reviewProcess.mjs +51 -0
- package/bin/lib/reviewTargets.mjs +50 -0
- package/bin/lib/robloxReview.mjs +234 -0
- package/bin/lib/robloxStudioBridge.mjs +68 -0
- package/bin/lib/runtimeAuth.mjs +56 -16
- package/bin/lib/runtimeReview.mjs +185 -0
- package/bin/lib/uiReview.mjs +5 -2
- package/bin/lib/work.mjs +22 -1
- package/bin/lib/workAgentCheck.mjs +24 -1
- package/bin/lib/workAgentTools.mjs +6 -4
- package/bin/lib/workAgentTurnExecution.mjs +75 -11
- package/package.json +1 -1
|
@@ -71,7 +71,13 @@ import { createIntermediateArtifacts, IntermediateArtifactCapacityError } from '
|
|
|
71
71
|
import { createUiReview } from './uiReview.mjs';
|
|
72
72
|
import { createVideoRender } from './videoRender.mjs';
|
|
73
73
|
import { createManagedBrowser } from './managedBrowser.mjs';
|
|
74
|
+
import { completeBrowserReview, browserHandoffPrompt } from './browserHandoff.mjs';
|
|
74
75
|
import { withUiReview, appendUiReviewEvidence } from './uiReviewTurn.mjs';
|
|
76
|
+
import { setupRepository } from './repositorySetup.mjs';
|
|
77
|
+
import { readEnvironmentRecipe } from './reviewEnvironmentConfig.mjs';
|
|
78
|
+
import { environmentInstructions, environmentOverlay, holdWorktreeEnvironment } from './reviewEnvironment.mjs';
|
|
79
|
+
import { environmentCredentialNames } from './reviewDatabase.mjs';
|
|
80
|
+
import { createRuntimeReview } from './runtimeReview.mjs';
|
|
75
81
|
|
|
76
82
|
export function createAgentTurnExecution({
|
|
77
83
|
admit,
|
|
@@ -104,6 +110,7 @@ export function createAgentTurnExecution({
|
|
|
104
110
|
makeUiReview = createUiReview,
|
|
105
111
|
makeVideoRender = createVideoRender,
|
|
106
112
|
makeManagedBrowser = createManagedBrowser,
|
|
113
|
+
makeRuntimeReview = createRuntimeReview,
|
|
107
114
|
}) {
|
|
108
115
|
const reportDelivery = createDeliveryReporter();
|
|
109
116
|
/** How long the final flush may hold the settle. Bounded because the settle
|
|
@@ -125,6 +132,7 @@ export function createAgentTurnExecution({
|
|
|
125
132
|
* `interruptAgentTurns`); absent, nothing can interrupt it. */
|
|
126
133
|
const runAgentTurnInPlace = async (job, { turnId, agentId, place, releaseSlot, postAgentTurn, measured, run = { interrupted: false } }) => {
|
|
127
134
|
let repositoryRun = null;
|
|
135
|
+
const environmentReleases = [];
|
|
128
136
|
const setupController = new AbortController();
|
|
129
137
|
run.abortSetup = () => setupController.abort();
|
|
130
138
|
if (run.interrupted) setupController.abort();
|
|
@@ -416,6 +424,8 @@ export function createAgentTurnExecution({
|
|
|
416
424
|
let browserNote = '';
|
|
417
425
|
let uiReviewNote = '';
|
|
418
426
|
let uiReviewEvidence = '';
|
|
427
|
+
let environmentNote = '', reviewEnv = null;
|
|
428
|
+
let runtimeReview = null, runtimeNote = '', runtimeEvidence = '';
|
|
419
429
|
// A video card's render tool (videoRender.mjs), and the words a turn
|
|
420
430
|
// gets when it could not be held, so it stops rather than guesses.
|
|
421
431
|
let videoRender = null;
|
|
@@ -429,6 +439,8 @@ export function createAgentTurnExecution({
|
|
|
429
439
|
args = withUiReview(args, uiReviewNote, uiReview?.env);
|
|
430
440
|
args = withUiReview(args, videoNote);
|
|
431
441
|
args = withUiReview(args, browserNote, managedBrowser?.env);
|
|
442
|
+
args = withUiReview(args, environmentNote, reviewEnv);
|
|
443
|
+
args = withUiReview(args, runtimeNote, runtimeReview?.env);
|
|
432
444
|
args.managedBrowser = Boolean(managedBrowser);
|
|
433
445
|
return runTurnImpl(args);
|
|
434
446
|
};
|
|
@@ -479,6 +491,22 @@ export function createAgentTurnExecution({
|
|
|
479
491
|
let orchestrator = null;
|
|
480
492
|
let intermediateDelivery = null;
|
|
481
493
|
try {
|
|
494
|
+
if (posture === 'build') {
|
|
495
|
+
const sources = repositoryRun?.sources ?? [{ wt, repoRoot: worktreeRepo, baseRef: baseRef() }];
|
|
496
|
+
const administrativeNames = new Set();
|
|
497
|
+
for (const source of sources) {
|
|
498
|
+
const snapshot = readEnvironmentRecipe(source.repoRoot, source.baseRef);
|
|
499
|
+
if (!snapshot) continue;
|
|
500
|
+
const environment = await setupRepository(source.repoRoot, source.wt, { snapshot, signal: setupController.signal,
|
|
501
|
+
onSpawn: ch => { workChildren.set(ch, agentId); groupKillChildren?.add(ch); },
|
|
502
|
+
onClose: ch => { workChildren.delete(ch); groupKillChildren?.delete(ch); } });
|
|
503
|
+
environmentReleases.push(holdWorktreeEnvironment(environment));
|
|
504
|
+
environmentNote += '\n' + environmentInstructions(environment);
|
|
505
|
+
for (const name of environmentCredentialNames(snapshot.recipe)) administrativeNames.add(name);
|
|
506
|
+
if (source.wt === wt) reviewEnv = environmentOverlay(environment);
|
|
507
|
+
}
|
|
508
|
+
if (administrativeNames.size) reviewEnv = { ...reviewEnv, ...Object.fromEntries([...administrativeNames].map(name => [name, undefined])) };
|
|
509
|
+
}
|
|
482
510
|
if (posture === 'build' && job.kind !== 'merge_resolve' && ['claude', 'codex'].includes(rt)) {
|
|
483
511
|
try {
|
|
484
512
|
managedBrowser = await makeManagedBrowser({ wt, turnId, signal: setupController.signal, onChild: ch => {
|
|
@@ -489,6 +517,14 @@ export function createAgentTurnExecution({
|
|
|
489
517
|
} catch (error) { browserNote = `The managed browser could not start: ${envScrub(error.message)}. Report interactive browser checks unavailable.`; }
|
|
490
518
|
}
|
|
491
519
|
if (posture === 'build' && job.kind !== 'merge_resolve') {
|
|
520
|
+
try {
|
|
521
|
+
runtimeReview = await makeRuntimeReview({ wt, repoRoot: worktreeRepo, baseRef: baseRef(), sources: repositoryRun?.sources ?? [],
|
|
522
|
+
browser: managedBrowser, signal: setupController.signal, onChild: ch => {
|
|
523
|
+
workChildren.set(ch, agentId); groupKillChildren?.add(ch);
|
|
524
|
+
return () => { workChildren.delete(ch); groupKillChildren?.delete(ch); };
|
|
525
|
+
} });
|
|
526
|
+
runtimeNote = runtimeReview?.instructions ?? '';
|
|
527
|
+
} catch (error) { runtimeNote = `Configured runtime review is unavailable: ${envScrub(error.message)}. Use the repository’s existing checks and report unavailable runtime verification.`; }
|
|
492
528
|
try {
|
|
493
529
|
uiReview = await makeUiReview({ wt, signal: setupController.signal, onChild: ch => {
|
|
494
530
|
workChildren.set(ch, agentId); groupKillChildren?.add(ch);
|
|
@@ -514,10 +550,13 @@ export function createAgentTurnExecution({
|
|
|
514
550
|
notify: message => liveInput ? run.live?.inject(message) === true : false,
|
|
515
551
|
taken: id => run.live?.onTaken(id) });
|
|
516
552
|
}
|
|
517
|
-
const tools = prepareCardTools({ rt, posture, wt, repoRoot, baseRef, sources: repositoryRun?.sources ?? [], repositoryTool: repositoryRun?.tool ?? null, orchestratorConfig: orchestrator?.config ?? null, uiReviewConfig: uiReview?.config, videoConfig: videoRender?.config ?? null, browserConfig: managedBrowser?.config, orchestrated: Boolean(orchestrator), codexHome: agentResume.codexHome
|
|
553
|
+
const tools = prepareCardTools({ rt, posture, wt, repoRoot, baseRef, sources: repositoryRun?.sources ?? [], repositoryTool: repositoryRun?.tool ?? null, orchestratorConfig: orchestrator?.config ?? null, uiReviewConfig: uiReview?.config, videoConfig: videoRender?.config ?? null, browserConfig: managedBrowser?.config, orchestrated: Boolean(orchestrator), codexHome: agentResume.codexHome,
|
|
554
|
+
runtimeConfig: runtimeReview?.config,
|
|
555
|
+
...(reviewEnv ? { env: { ...process.env, ...reviewEnv } } : {}) });
|
|
518
556
|
projectTools = tools.projectTools;
|
|
519
557
|
localCleanup?.begin(turnId);
|
|
520
558
|
const { toolsNote } = tools;
|
|
559
|
+
let browserThread = agentResume.resumeThreadId;
|
|
521
560
|
const agentTurnArgs = {
|
|
522
561
|
...((liveInput || orchestrator) ? { liveMessages: (run.live = makeLiveMessages({
|
|
523
562
|
scope: 'agent', turnId, cwd: wt, sessionMetaPath, interrupted: () => run.interrupted,
|
|
@@ -565,12 +604,14 @@ export function createAgentTurnExecution({
|
|
|
565
604
|
resume: agentResume.resume,
|
|
566
605
|
resumeThreadId: agentResume.resumeThreadId,
|
|
567
606
|
onInit: (init) => {
|
|
607
|
+
if (rt === 'claude' && typeof init.sessionId === 'string') browserThread = init.sessionId;
|
|
568
608
|
orchestrator?.root(init.sessionId);
|
|
569
609
|
// A server that did not connect, as the CLI said (unknown: nothing).
|
|
570
610
|
trace.mcpStatus(init.mcpServers);
|
|
571
611
|
agentResume.sawSession(init.sessionId);
|
|
572
612
|
},
|
|
573
613
|
onThreadId: (id) => {
|
|
614
|
+
browserThread = id;
|
|
574
615
|
orchestrator?.root(id);
|
|
575
616
|
agentResume.sawThread(id);
|
|
576
617
|
},
|
|
@@ -741,6 +782,7 @@ export function createAgentTurnExecution({
|
|
|
741
782
|
runtime: rt,
|
|
742
783
|
beforeFresh: () => {
|
|
743
784
|
agentResume.forgetThread();
|
|
785
|
+
browserThread = undefined;
|
|
744
786
|
lastAnswer = null;
|
|
745
787
|
model = null;
|
|
746
788
|
},
|
|
@@ -751,19 +793,34 @@ export function createAgentTurnExecution({
|
|
|
751
793
|
void postAgentActivity(agentId, 'resuming after an internal wait');
|
|
752
794
|
} }
|
|
753
795
|
);
|
|
754
|
-
out = await
|
|
755
|
-
|
|
756
|
-
resume: async (
|
|
796
|
+
out = await completeBrowserReview(out, {
|
|
797
|
+
browser: managedBrowser, signal: setupController.signal,
|
|
798
|
+
resume: async (handoff) => {
|
|
799
|
+
const thread = agentResume.threadNow() ?? browserThread;
|
|
800
|
+
if (!thread) throw new Error('The browser handoff could not resume: the CLI reported no conversation id. Interactive verification is incomplete.');
|
|
757
801
|
lastAnswer = null;
|
|
758
802
|
const counted = usage;
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
writableDirs: repositoryRun.sourcesNow().filter(
|
|
763
|
-
|
|
764
|
-
resumeThreadId: agentResume.threadNow() ?? agentTurnArgs.resumeThreadId,
|
|
803
|
+
trace.prose('text', handoff.reason === 'returned' ? 'Browser control returned. Resuming verification.' : 'The browser handoff ended. Resuming to report incomplete interactive checks.');
|
|
804
|
+
return runAgentWithInternalWaits(args => runCli(args), {
|
|
805
|
+
...agentTurnArgs, prompt: browserHandoffPrompt(handoff), resume: true, resumeThreadId: thread, liveContinuation: true,
|
|
806
|
+
...(repositoryRun ? { writableDirs: repositoryRun.sourcesNow().filter(r => r.wt !== wt).map(r => r.wt) } : {}),
|
|
807
|
+
onUsage: next => agentTurnArgs.onUsage(addUsage(counted, next)),
|
|
765
808
|
});
|
|
766
809
|
},
|
|
810
|
+
finish: current => finishRepositoryDelivery({
|
|
811
|
+
job, run: repositoryRun, out: current, answer: () => lastAnswer,
|
|
812
|
+
resume: async (prompt) => {
|
|
813
|
+
lastAnswer = null;
|
|
814
|
+
const counted = usage;
|
|
815
|
+
return runCli({
|
|
816
|
+
...agentTurnArgs, prompt, liveContinuation: true,
|
|
817
|
+
onUsage: (next) => agentTurnArgs.onUsage(addUsage(counted, next)),
|
|
818
|
+
writableDirs: repositoryRun.sourcesNow().filter((r) => r.wt !== wt).map((r) => r.wt),
|
|
819
|
+
resume: true,
|
|
820
|
+
resumeThreadId: agentResume.threadNow() ?? browserThread,
|
|
821
|
+
});
|
|
822
|
+
},
|
|
823
|
+
}),
|
|
767
824
|
});
|
|
768
825
|
} finally {
|
|
769
826
|
// The loopback capability stays registered until every retained CLI
|
|
@@ -774,12 +831,15 @@ export function createAgentTurnExecution({
|
|
|
774
831
|
// owned by its review tool, then bind evidence to the final source
|
|
775
832
|
// before the existing artifact upload and delivery settlement.
|
|
776
833
|
try { await videoRender?.close(); } catch { /* a closed listener */ }
|
|
834
|
+
try { await runtimeReview?.close(); } catch { /* turn teardown continues */ }
|
|
777
835
|
try { await managedBrowser?.close(); } catch { /* turn teardown continues */ }
|
|
778
836
|
try { await uiReview?.close(); }
|
|
779
837
|
finally {
|
|
780
838
|
try { uiReviewEvidence = uiReview?.finalize() ?? uiReviewEvidence; }
|
|
781
839
|
catch (error) { uiReviewEvidence = `Local UI review final source/evidence measurement unavailable: ${envScrub(error.message)}. Earlier checks do not establish the final source.`; }
|
|
782
840
|
}
|
|
841
|
+
try { runtimeEvidence = runtimeReview?.finalize() ?? ''; }
|
|
842
|
+
catch { runtimeEvidence = 'Runtime review final source could not be measured; earlier results do not verify final source.'; }
|
|
783
843
|
await run.live?.stop();
|
|
784
844
|
await orchestrator?.close(run.interrupted);
|
|
785
845
|
await brief?.stop();
|
|
@@ -890,6 +950,7 @@ export function createAgentTurnExecution({
|
|
|
890
950
|
modelExports: intermediateDelivery?.modelExports,
|
|
891
951
|
});
|
|
892
952
|
appendUiReviewEvidence(body, uiReviewEvidence);
|
|
953
|
+
appendUiReviewEvidence(body, runtimeEvidence);
|
|
893
954
|
// The agent's own account of what it did not deliver, for the pre-review.
|
|
894
955
|
if (job.kind === 'task' && job.task && body.outcome === 'delivered' && res?.gaps)
|
|
895
956
|
stashGaps(sessionMetaPath(wt, 'flowviant-agent-cards', agentId), job.task.id, res.gaps);
|
|
@@ -945,7 +1006,10 @@ export function createAgentTurnExecution({
|
|
|
945
1006
|
// hold the lock on — see `runReviewEntry`. Only after the settle that may
|
|
946
1007
|
// say delivered: the three `nothing` settles never opened it.
|
|
947
1008
|
if (final && reply?.review === true) await runReviewEntry(agentId, wt, job.agentName, undefined, job);
|
|
948
|
-
} finally {
|
|
1009
|
+
} finally {
|
|
1010
|
+
for (const release of environmentReleases) release();
|
|
1011
|
+
await repositoryRun?.tool?.close();
|
|
1012
|
+
}
|
|
949
1013
|
};
|
|
950
1014
|
|
|
951
1015
|
return { runAgentTurnInPlace };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flowviant",
|
|
3
|
-
"version": "0.125.
|
|
3
|
+
"version": "0.125.30",
|
|
4
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.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|