flowviant 0.125.27 → 0.125.28

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.
@@ -30,6 +30,7 @@ import { sawSentinel } from './claude.mjs';
30
30
  import { runTurn as defaultRunTurn } from './runTurn.mjs';
31
31
  import { SYSTEM_WIKI, WIKI_KICKOFF, SYSTEM_REGROUND, REGROUND_KICKOFF } from './prompts.mjs';
32
32
  import { ensureVault, syncVault } from './vault.mjs';
33
+ import { beginWikiDraft } from './wikiDraft.mjs';
33
34
  import { scrub as envScrub } from './uplinkScrub.mjs';
34
35
  import { runtimeLabel } from './runtimes.mjs';
35
36
  import { pickRuntimeFor as defaultPickRuntimeFor } from './runtimeDetection.mjs';
@@ -238,7 +239,8 @@ export function createWikiRunner({
238
239
  // always taken (audit 2026-10-10, F749).
239
240
  if (!SHA1_ABBREV_RE.test(String(sha).toLowerCase())) continue;
240
241
  try {
241
- const out = gitRaw(['show', '--name-only', '--pretty=format:', String(sha)], repoRoot);
242
+ // Report both sides of a rename so old files: citations are affected.
243
+ const out = gitRaw(['show', '--no-renames', '--name-only', '--pretty=format:', String(sha)], repoRoot);
242
244
  for (const line of out.split('\n')) {
243
245
  const f = line.trim();
244
246
  if (f) files.add(f);
@@ -374,6 +376,7 @@ export function createWikiRunner({
374
376
  * readout. Null (and absent from the frame) when no CLI ran.
375
377
  */
376
378
  let usage = null;
379
+ let draft = null;
377
380
  const onUsage = (rt) => (u) => {
378
381
  usage = addUsage(usage, { ...u, runtime: rt });
379
382
  };
@@ -394,20 +397,23 @@ export function createWikiRunner({
394
397
  resetWorktree(wikiWt, getBaseRef());
395
398
  // No HEAD → '' — still writes the map, just ungrounded.
396
399
  const sha = gitOrNull(['rev-parse', 'HEAD'], wikiWt) ?? '';
397
- // Sync the vault after the turn regardless of the sentinel: a died
398
- // sweep's partial pages still persist (merge, no prune) — only a
399
- // COMPLETED sweep finalizes, so an interrupted one can't erase pages.
400
+ const repoFullName = originSlug(repoRoot) || undefined;
401
+ const persistentVaultDir = vaultDir;
402
+ draft = beginWikiDraft(persistentVaultDir);
403
+ vaultDir = draft.dir;
404
+ // Only a complete draft can replace last-good pages or prune them.
405
+ // The CLI's write fence names the draft, never Knowledge or sources.
400
406
  const runSync = async (finalize) => {
401
407
  try {
402
408
  const r = await syncVault({
403
- dir: vaultDir,
409
+ dir: persistentVaultDir,
404
410
  url: WIKI_VAULT_URL,
405
411
  token: FLEET_TOKEN,
406
412
  userAgent: USER_AGENT,
407
413
  finalize,
408
414
  groundedAtSha: sha || undefined,
409
415
  // Powers the GitHub blob links behind every cited file path.
410
- repoFullName: originSlug(repoRoot) || undefined,
416
+ repoFullName,
411
417
  warn,
412
418
  // Redact synced secrets a page may have quoted from the repo.
413
419
  scrub: envScrub,
@@ -435,7 +441,7 @@ export function createWikiRunner({
435
441
  sweepAttempts++;
436
442
  note(`${c.cyan('wiki')} ${c.dim(`— regenerating: your ${wikiLabel} is reading the repo…`)}`);
437
443
  const out = await runTurn({
438
- prompt: WIKI_KICKOFF(sha, vaultDir),
444
+ prompt: WIKI_KICKOFF(sha, vaultDir, repoFullName),
439
445
  resume: false,
440
446
  system: SYSTEM_WIKI(vaultDir),
441
447
  cwd: wikiWt,
@@ -454,12 +460,13 @@ export function createWikiRunner({
454
460
  });
455
461
  const complete = sawSentinel(out, 'WIKI_DONE');
456
462
  if (complete) {
463
+ draft.publish({ sha, repoFullName });
457
464
  sweepCompleted = true;
458
465
  ok(`${c.cyan('wiki')} ${c.dim('— vault regenerated from your code.')}`);
459
466
  } else {
460
- warn('wiki sweep ended without WIKI_DONE — partial pages synced');
467
+ warn('wiki sweep ended without WIKI_DONE — last good pages retained');
461
468
  }
462
- await runSync(complete);
469
+ if (complete) await runSync(true);
463
470
  } else {
464
471
  const files = changedFilesForShas(task.shas);
465
472
  if (files === null) {
@@ -484,6 +491,7 @@ export function createWikiRunner({
484
491
  title: task.title,
485
492
  files,
486
493
  vaultDir,
494
+ repoFullName,
487
495
  predictedPages: task.dirtiesPages ?? [],
488
496
  }),
489
497
  resume: false,
@@ -499,10 +507,11 @@ export function createWikiRunner({
499
507
  wikiChild = ch;
500
508
  },
501
509
  });
502
- if (sawSentinel(out, 'REGROUND_DONE'))
510
+ if (sawSentinel(out, 'REGROUND_DONE')) {
511
+ draft.publish({ sha, repoFullName });
503
512
  ok(`${c.cyan('wiki')} ${c.dim(`— vault updated for "${task.title}".`)}`);
504
- else warn(`wiki re-ground for "${task.title}" ended without REGROUND_DONE.`);
505
- await runSync(false);
513
+ await runSync(false);
514
+ } else warn(`wiki re-ground for "${task.title}" ended without REGROUND_DONE — last good pages retained.`);
506
515
  }
507
516
  // Consume the durable job: attempted = done (success or not — the
508
517
  // sync is idempotent and a failed turn heals on the next full
@@ -521,6 +530,7 @@ export function createWikiRunner({
521
530
  } catch (e) {
522
531
  warn(`wiki ${task.type} failed: ${e.message}`);
523
532
  } finally {
533
+ draft?.discard();
524
534
  wikiChild = null;
525
535
  if (heartbeat) clearInterval(heartbeat);
526
536
  /**
@@ -27,13 +27,15 @@ import { postureToolsNote } from './postureTools.mjs';
27
27
  import { machineMcpServers } from './claudeMachineMcp.mjs';
28
28
  import { ORCHESTRATOR_SERVER } from './orchestratorProtocol.mjs';
29
29
  import { VIDEO_SERVER } from './videoRender.mjs';
30
+ import { BROWSER_SERVER } from './managedBrowser.mjs';
30
31
 
31
32
  /** The servers the daemon itself mounts on a card turn, by the names the
32
33
  * agent calls them: a multi-repository card's add_repository, an
33
34
  * orchestrated card's routing, a build's UI review and a video card's
34
- * render_video (2026-10-26). Never a project server's, whatever the base
35
+ * render_video (2026-10-26), and a code turn's managed browser (2026-10-30).
36
+ * Never a project server's, whatever the base
35
37
  * `.mcp.json` names. */
36
- export const CARD_TURN_SERVERS = Object.freeze({ repositories: 'card_repositories', orchestrator: ORCHESTRATOR_SERVER, uiReview: 'card_ui_review', video: VIDEO_SERVER });
38
+ export const CARD_TURN_SERVERS = Object.freeze({ repositories: 'card_repositories', orchestrator: ORCHESTRATOR_SERVER, uiReview: 'card_ui_review', video: VIDEO_SERVER, browser: BROWSER_SERVER });
37
39
 
38
40
  /**
39
41
  * The card turn's tools, prepared, and what it is told of them.
@@ -46,7 +48,7 @@ export const CARD_TURN_SERVERS = Object.freeze({ repositories: 'card_repositorie
46
48
  * from here), the note, the mount's part of the CLI's `mcpEnv` and whether it
47
49
  * needs one, and the keys the runner takes the mount as.
48
50
  */
49
- export function prepareCardTools({ rt, posture, wt, repoRoot, baseRef, sources = [], repositoryTool = null, orchestratorConfig = null, uiReviewConfig = null, videoConfig = null, orchestrated = false, codexHome = null, env = process.env, machineServers = machineMcpServers }) {
51
+ export function prepareCardTools({ rt, posture, wt, repoRoot, baseRef, sources = [], repositoryTool = null, orchestratorConfig = null, uiReviewConfig = null, videoConfig = null, browserConfig = null, orchestrated = false, codexHome = null, env = process.env, machineServers = machineMcpServers }) {
50
52
  // A turn reads the base ref afresh. Changes the agent has made to its
51
53
  // own worktree cannot silently change the next turn's tools.
52
54
  const source = sources.find((r) => r.wt === wt);
@@ -59,6 +61,7 @@ export function prepareCardTools({ rt, posture, wt, repoRoot, baseRef, sources =
59
61
  ...(repositoryTool ? { [CARD_TURN_SERVERS.repositories]: repositoryTool.config } : {}),
60
62
  ...(orchestratorConfig ? { [CARD_TURN_SERVERS.orchestrator]: orchestratorConfig } : {}),
61
63
  ...(posture === 'build' && uiReviewConfig ? { [CARD_TURN_SERVERS.uiReview]: uiReviewConfig } : {}),
64
+ ...(posture === 'build' && browserConfig ? { [CARD_TURN_SERVERS.browser]: browserConfig } : {}),
62
65
  // A video card's render tool rides its fenced posture (videoRender.mjs).
63
66
  ...(videoConfig ? { [CARD_TURN_SERVERS.video]: videoConfig } : {}),
64
67
  } });
@@ -70,6 +70,7 @@ import { measuredRuntimeVersion } from './runtimeDetection.mjs';
70
70
  import { createIntermediateArtifacts, IntermediateArtifactCapacityError } from './intermediateArtifacts.mjs';
71
71
  import { createUiReview } from './uiReview.mjs';
72
72
  import { createVideoRender } from './videoRender.mjs';
73
+ import { createManagedBrowser } from './managedBrowser.mjs';
73
74
  import { withUiReview, appendUiReviewEvidence } from './uiReviewTurn.mjs';
74
75
 
75
76
  export function createAgentTurnExecution({
@@ -102,6 +103,7 @@ export function createAgentTurnExecution({
102
103
  exportModel,
103
104
  makeUiReview = createUiReview,
104
105
  makeVideoRender = createVideoRender,
106
+ makeManagedBrowser = createManagedBrowser,
105
107
  }) {
106
108
  const reportDelivery = createDeliveryReporter();
107
109
  /** How long the final flush may hold the settle. Bounded because the settle
@@ -410,6 +412,8 @@ export function createAgentTurnExecution({
410
412
  /** The CLI, unless a person already asked to stop this turn (Interrupt
411
413
  * now): then nothing starts — no fresh retry, no continuation. */
412
414
  let uiReview = null;
415
+ let managedBrowser = null;
416
+ let browserNote = '';
413
417
  let uiReviewNote = '';
414
418
  let uiReviewEvidence = '';
415
419
  // A video card's render tool (videoRender.mjs), and the words a turn
@@ -424,6 +428,8 @@ export function createAgentTurnExecution({
424
428
  args.verifiedWorktree = true;
425
429
  args = withUiReview(args, uiReviewNote, uiReview?.env);
426
430
  args = withUiReview(args, videoNote);
431
+ args = withUiReview(args, browserNote, managedBrowser?.env);
432
+ args.managedBrowser = Boolean(managedBrowser);
427
433
  return runTurnImpl(args);
428
434
  };
429
435
  /**
@@ -473,6 +479,15 @@ export function createAgentTurnExecution({
473
479
  let orchestrator = null;
474
480
  let intermediateDelivery = null;
475
481
  try {
482
+ if (posture === 'build' && job.kind !== 'merge_resolve' && ['claude', 'codex'].includes(rt)) {
483
+ try {
484
+ managedBrowser = await makeManagedBrowser({ wt, turnId, signal: setupController.signal, onChild: ch => {
485
+ workChildren.set(ch, agentId); groupKillChildren?.add(ch);
486
+ return () => { workChildren.delete(ch); groupKillChildren?.delete(ch); };
487
+ } });
488
+ browserNote = managedBrowser.instructions;
489
+ } catch (error) { browserNote = `The managed browser could not start: ${envScrub(error.message)}. Report interactive browser checks unavailable.`; }
490
+ }
476
491
  if (posture === 'build' && job.kind !== 'merge_resolve') {
477
492
  try {
478
493
  uiReview = await makeUiReview({ wt, signal: setupController.signal, onChild: ch => {
@@ -499,7 +514,7 @@ export function createAgentTurnExecution({
499
514
  notify: message => liveInput ? run.live?.inject(message) === true : false,
500
515
  taken: id => run.live?.onTaken(id) });
501
516
  }
502
- 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, orchestrated: Boolean(orchestrator), codexHome: agentResume.codexHome });
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 });
503
518
  projectTools = tools.projectTools;
504
519
  localCleanup?.begin(turnId);
505
520
  const { toolsNote } = tools;
@@ -759,6 +774,7 @@ export function createAgentTurnExecution({
759
774
  // owned by its review tool, then bind evidence to the final source
760
775
  // before the existing artifact upload and delivery settlement.
761
776
  try { await videoRender?.close(); } catch { /* a closed listener */ }
777
+ try { await managedBrowser?.close(); } catch { /* turn teardown continues */ }
762
778
  try { await uiReview?.close(); }
763
779
  finally {
764
780
  try { uiReviewEvidence = uiReview?.finalize() ?? uiReviewEvidence; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flowviant",
3
- "version": "0.125.27",
3
+ "version": "0.125.28",
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": {
@@ -1,69 +0,0 @@
1
- {
2
- "name": "flowviant",
3
- "version": "0.125.27",
4
- "lockfileVersion": 3,
5
- "requires": true,
6
- "packages": {
7
- "": {
8
- "name": "flowviant",
9
- "version": "0.125.27",
10
- "license": "MIT",
11
- "dependencies": {
12
- "libsodium-wrappers-sumo": "^0.8.4",
13
- "ws": "^8.18.0"
14
- },
15
- "bin": {
16
- "flowviant": "bin/cli.mjs"
17
- },
18
- "devDependencies": {
19
- "three": "0.180.0"
20
- },
21
- "engines": {
22
- "node": ">=20"
23
- }
24
- },
25
- "node_modules/libsodium-sumo": {
26
- "version": "0.8.4",
27
- "resolved": "https://registry.npmjs.org/libsodium-sumo/-/libsodium-sumo-0.8.4.tgz",
28
- "integrity": "sha512-TMtHShQfVVsaxDygyapvUC3o7YsPgXa/hRWeIgzyFz6w5k/1hirGptCxp1U7XwW3rCskaTTYKgV10v86UiGgNw==",
29
- "license": "ISC"
30
- },
31
- "node_modules/libsodium-wrappers-sumo": {
32
- "version": "0.8.4",
33
- "resolved": "https://registry.npmjs.org/libsodium-wrappers-sumo/-/libsodium-wrappers-sumo-0.8.4.tgz",
34
- "integrity": "sha512-ql7hcgulKZ3ekfa2DGAogcCKsWU0diA/0nArz1CFzh93WQdb46/Kj18ka/Hifq6uA3Ush34Pc6vU/6HXeRwUkg==",
35
- "license": "ISC",
36
- "dependencies": {
37
- "libsodium-sumo": "^0.8.0"
38
- }
39
- },
40
- "node_modules/three": {
41
- "version": "0.180.0",
42
- "resolved": "https://registry.npmjs.org/three/-/three-0.180.0.tgz",
43
- "integrity": "sha512-o+qycAMZrh+TsE01GqWUxUIKR1AL0S8pq7zDkYOQw8GqfX8b8VoCKYUoHbhiX5j+7hr8XsuHDVU6+gkQJQKg9w==",
44
- "dev": true,
45
- "license": "MIT"
46
- },
47
- "node_modules/ws": {
48
- "version": "8.21.1",
49
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz",
50
- "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==",
51
- "license": "MIT",
52
- "engines": {
53
- "node": ">=10.0.0"
54
- },
55
- "peerDependencies": {
56
- "bufferutil": "^4.0.1",
57
- "utf-8-validate": ">=5.0.2"
58
- },
59
- "peerDependenciesMeta": {
60
- "bufferutil": {
61
- "optional": true
62
- },
63
- "utf-8-validate": {
64
- "optional": true
65
- }
66
- }
67
- }
68
- }
69
- }