flowviant 0.125.29 → 0.125.31

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.
Files changed (72) hide show
  1. package/README.md +364 -0
  2. package/bin/cli.mjs +23 -0
  3. package/bin/lib/agentPlan.mjs +8 -0
  4. package/bin/lib/agentQueueContext.mjs +6 -0
  5. package/bin/lib/agentSplit.mjs +90 -0
  6. package/bin/lib/architectureContract.mjs +57 -0
  7. package/bin/lib/architectureReport.mjs +38 -0
  8. package/bin/lib/artifactContracts.mjs +21 -2
  9. package/bin/lib/claudeInput.mjs +1 -1
  10. package/bin/lib/claudeSubagents.mjs +46 -6
  11. package/bin/lib/claudeWorkflows.mjs +94 -0
  12. package/bin/lib/cliLimit.mjs +5 -0
  13. package/bin/lib/codexInput.mjs +191 -0
  14. package/bin/lib/codexSubagentEvents.mjs +3 -1
  15. package/bin/lib/codexSubagents.mjs +34 -9
  16. package/bin/lib/conversationOutput.mjs +14 -0
  17. package/bin/lib/crossCliChild.mjs +327 -0
  18. package/bin/lib/crossCliLaunch.mjs +92 -0
  19. package/bin/lib/crossCliPosture.mjs +55 -0
  20. package/bin/lib/crossCliResearch.mjs +19 -0
  21. package/bin/lib/environmentProcess.mjs +44 -0
  22. package/bin/lib/fixtures/workflow-ultracode.jsonl +22 -0
  23. package/bin/lib/fleet.mjs +2 -0
  24. package/bin/lib/linuxDesktopBridge.mjs +62 -0
  25. package/bin/lib/managedBrowser.mjs +50 -10
  26. package/bin/lib/mergeBatch.mjs +1 -1
  27. package/bin/lib/nativeAgentTools.mjs +12 -5
  28. package/bin/lib/noWindowEnv.mjs +4 -2
  29. package/bin/lib/orchestrator.mjs +24 -7
  30. package/bin/lib/orchestratorClient.mjs +8 -2
  31. package/bin/lib/orchestratorPrompt.mjs +13 -5
  32. package/bin/lib/orchestratorProtocol.mjs +18 -0
  33. package/bin/lib/outsideCli.mjs +124 -0
  34. package/bin/lib/projectToolRuntimes.mjs +4 -2
  35. package/bin/lib/projectTools.mjs +2 -2
  36. package/bin/lib/prompts.mjs +7 -5
  37. package/bin/lib/repositoryPlaces.mjs +1 -1
  38. package/bin/lib/repositorySetup.mjs +15 -24
  39. package/bin/lib/reviewBridge.mjs +110 -0
  40. package/bin/lib/reviewDatabase.mjs +112 -0
  41. package/bin/lib/reviewDesktop.mjs +153 -0
  42. package/bin/lib/reviewEnvironment.mjs +286 -0
  43. package/bin/lib/reviewEnvironmentConfig.mjs +104 -0
  44. package/bin/lib/reviewEnvironmentMain.mjs +38 -0
  45. package/bin/lib/reviewImage.mjs +58 -0
  46. package/bin/lib/reviewMcpClient.mjs +69 -0
  47. package/bin/lib/reviewProcess.mjs +51 -0
  48. package/bin/lib/reviewTargets.mjs +50 -0
  49. package/bin/lib/robloxReview.mjs +234 -0
  50. package/bin/lib/robloxStudioBridge.mjs +68 -0
  51. package/bin/lib/runTurn.mjs +30 -8
  52. package/bin/lib/runtimeClaude.mjs +7 -3
  53. package/bin/lib/runtimeCodex.mjs +12 -6
  54. package/bin/lib/runtimeLimits.mjs +59 -1
  55. package/bin/lib/runtimeReview.mjs +185 -0
  56. package/bin/lib/splitGate.mjs +115 -0
  57. package/bin/lib/subagentBounds.mjs +26 -0
  58. package/bin/lib/subagentLedger.mjs +110 -0
  59. package/bin/lib/trace.mjs +134 -6
  60. package/bin/lib/turnDeclaration.mjs +4 -4
  61. package/bin/lib/turnGaps.mjs +6 -2
  62. package/bin/lib/turnSubagents.mjs +23 -9
  63. package/bin/lib/uiReview.mjs +5 -2
  64. package/bin/lib/wikiDraft.mjs +9 -0
  65. package/bin/lib/work.mjs +22 -1
  66. package/bin/lib/workAgentCheck.mjs +24 -1
  67. package/bin/lib/workAgentResume.mjs +11 -2
  68. package/bin/lib/workAgentTools.mjs +6 -4
  69. package/bin/lib/workAgentTurnExecution.mjs +77 -9
  70. package/bin/lib/workAgentTurnOutcome.mjs +11 -0
  71. package/bin/lib/workAgentTurnReports.mjs +5 -2
  72. package/package.json +1 -1
@@ -30,6 +30,7 @@ import { finishRepositoryDelivery, allResolutionsCommitted } from './repositoryD
30
30
  */
31
31
  import { MODEL, PROJECT_ID } from './config.mjs';
32
32
  import { setRuntimeLimit } from './daemonState.mjs';
33
+ import { limitResetAt } from './runtimeLimits.mjs';
33
34
  import { gitOrNull } from './git.mjs';
34
35
  import { assertWorktree } from './worktreeHealth.mjs';
35
36
  import { placeTurnLockedByLivePid, recordTurnLock } from './turnLock.mjs';
@@ -48,6 +49,7 @@ import { scrub as envScrub } from './uplinkScrub.mjs';
48
49
  import { canRun, runtimeById } from './runtimes.mjs';
49
50
  import { toolEventOf, CLAUDE_TOOL_PROSE_KINDS, scrubNativeText } from './runtimeEvents.mjs';
50
51
  import { makeTraceRelay } from './trace.mjs';
52
+ import { createOutsideRuns } from './outsideCli.mjs';
51
53
  import { createWebsiteTrace } from './websiteTrace.mjs';
52
54
  import { prepareCardTools } from './workAgentTools.mjs';
53
55
  import { runTurnResumingOnce } from './resumeLost.mjs';
@@ -62,6 +64,7 @@ import { agentTurnResume } from './workAgentResume.mjs';
62
64
  import { createDeliveryReporter } from './workDelivery.mjs';
63
65
  import { createLiveMessages } from './workLiveMessages.mjs';
64
66
  import { createOrchestrator } from './orchestrator.mjs';
67
+ import { crossCliFor } from './crossCliLaunch.mjs';
65
68
  import { claudeOrchestrationSafe } from './orchestratorHooks.mjs';
66
69
  import { ORCHESTRATED_POSTURES, orchestratesPosture } from './orchestratorPrompt.mjs';
67
70
  import { knownCodexModels } from './runtimeOfferings.mjs';
@@ -73,6 +76,12 @@ import { createVideoRender } from './videoRender.mjs';
73
76
  import { createManagedBrowser } from './managedBrowser.mjs';
74
77
  import { completeBrowserReview, browserHandoffPrompt } from './browserHandoff.mjs';
75
78
  import { withUiReview, appendUiReviewEvidence } from './uiReviewTurn.mjs';
79
+ import { setupRepository } from './repositorySetup.mjs';
80
+ import { readEnvironmentRecipe } from './reviewEnvironmentConfig.mjs';
81
+ import { environmentInstructions, environmentOverlay, holdWorktreeEnvironment } from './reviewEnvironment.mjs';
82
+ import { environmentCredentialNames } from './reviewDatabase.mjs';
83
+ import { createRuntimeReview } from './runtimeReview.mjs';
84
+ import { splitBrain, splitPolicy } from './splitGate.mjs';
76
85
 
77
86
  export function createAgentTurnExecution({
78
87
  admit,
@@ -105,6 +114,7 @@ export function createAgentTurnExecution({
105
114
  makeUiReview = createUiReview,
106
115
  makeVideoRender = createVideoRender,
107
116
  makeManagedBrowser = createManagedBrowser,
117
+ makeRuntimeReview = createRuntimeReview,
108
118
  }) {
109
119
  const reportDelivery = createDeliveryReporter();
110
120
  /** How long the final flush may hold the settle. Bounded because the settle
@@ -126,6 +136,7 @@ export function createAgentTurnExecution({
126
136
  * `interruptAgentTurns`); absent, nothing can interrupt it. */
127
137
  const runAgentTurnInPlace = async (job, { turnId, agentId, place, releaseSlot, postAgentTurn, measured, run = { interrupted: false } }) => {
128
138
  let repositoryRun = null;
139
+ const environmentReleases = [];
129
140
  const setupController = new AbortController();
130
141
  run.abortSetup = () => setupController.abort();
131
142
  if (run.interrupted) setupController.abort();
@@ -286,7 +297,7 @@ export function createAgentTurnExecution({
286
297
 
287
298
  // Which conversation this turn resumes, and the kept Codex home it is
288
299
  // filed under: the agent's resume across turns (workAgentResume.mjs).
289
- const agentResume = agentTurnResume({ rt, wt, agentId, sessionMetaPath });
300
+ const agentResume = agentTurnResume({ rt, wt, agentId, sessionMetaPath, turnId });
290
301
 
291
302
  /**
292
303
  * THE TURN'S FILES, ON DISK BEFORE THE PROMPT NAMES THEM (0.112.0) — the
@@ -363,7 +374,12 @@ export function createAgentTurnExecution({
363
374
  post: postAgentTrace,
364
375
  orchestrationVersion: orchestrating ? 2 : undefined,
365
376
  scrub: envScrub,
377
+ // Each child's CLI, and the turns a resumed one appeared in (S4.1).
378
+ cli: rt,
379
+ runsOf: agentResume.subagents.runsOf ?? undefined,
366
380
  });
381
+ // The lead's shell-outs to another CLI, shown as Outside rows (outsideCli.mjs).
382
+ const outsideRuns = createOutsideRuns(trace);
367
383
  // A live Send can choose its path as soon as the CLI starts, including a
368
384
  // silent solo turn. Do not leave a two-second first-flush race at spawn.
369
385
  if (orchestrating) await trace.flush(1_000);
@@ -398,6 +414,8 @@ export function createAgentTurnExecution({
398
414
  // run's children and contract: Claude always names --model (the daemon's
399
415
  // pin when the card has none); Codex's unpinned default is its own.
400
416
  const cardBrain = { model: brain.model ?? (rt === 'claude' ? MODEL : null), effort: brain.effort ?? null };
417
+ // The person's split answer or Always switch, when the server asks (splitGate.mjs).
418
+ const split = splitPolicy(job);
401
419
 
402
420
  let out = '';
403
421
  let child = null;
@@ -417,6 +435,8 @@ export function createAgentTurnExecution({
417
435
  let browserNote = '';
418
436
  let uiReviewNote = '';
419
437
  let uiReviewEvidence = '';
438
+ let environmentNote = '', reviewEnv = null;
439
+ let runtimeReview = null, runtimeNote = '', runtimeEvidence = '';
420
440
  // A video card's render tool (videoRender.mjs), and the words a turn
421
441
  // gets when it could not be held, so it stops rather than guesses.
422
442
  let videoRender = null;
@@ -430,6 +450,8 @@ export function createAgentTurnExecution({
430
450
  args = withUiReview(args, uiReviewNote, uiReview?.env);
431
451
  args = withUiReview(args, videoNote);
432
452
  args = withUiReview(args, browserNote, managedBrowser?.env);
453
+ args = withUiReview(args, environmentNote, reviewEnv);
454
+ args = withUiReview(args, runtimeNote, runtimeReview?.env);
433
455
  args.managedBrowser = Boolean(managedBrowser);
434
456
  return runTurnImpl(args);
435
457
  };
@@ -480,6 +502,22 @@ export function createAgentTurnExecution({
480
502
  let orchestrator = null;
481
503
  let intermediateDelivery = null;
482
504
  try {
505
+ if (posture === 'build') {
506
+ const sources = repositoryRun?.sources ?? [{ wt, repoRoot: worktreeRepo, baseRef: baseRef() }];
507
+ const administrativeNames = new Set();
508
+ for (const source of sources) {
509
+ const snapshot = readEnvironmentRecipe(source.repoRoot, source.baseRef);
510
+ if (!snapshot) continue;
511
+ const environment = await setupRepository(source.repoRoot, source.wt, { snapshot, signal: setupController.signal,
512
+ onSpawn: ch => { workChildren.set(ch, agentId); groupKillChildren?.add(ch); },
513
+ onClose: ch => { workChildren.delete(ch); groupKillChildren?.delete(ch); } });
514
+ environmentReleases.push(holdWorktreeEnvironment(environment));
515
+ environmentNote += '\n' + environmentInstructions(environment);
516
+ for (const name of environmentCredentialNames(snapshot.recipe)) administrativeNames.add(name);
517
+ if (source.wt === wt) reviewEnv = environmentOverlay(environment);
518
+ }
519
+ if (administrativeNames.size) reviewEnv = { ...reviewEnv, ...Object.fromEntries([...administrativeNames].map(name => [name, undefined])) };
520
+ }
483
521
  if (posture === 'build' && job.kind !== 'merge_resolve' && ['claude', 'codex'].includes(rt)) {
484
522
  try {
485
523
  managedBrowser = await makeManagedBrowser({ wt, turnId, signal: setupController.signal, onChild: ch => {
@@ -490,6 +528,14 @@ export function createAgentTurnExecution({
490
528
  } catch (error) { browserNote = `The managed browser could not start: ${envScrub(error.message)}. Report interactive browser checks unavailable.`; }
491
529
  }
492
530
  if (posture === 'build' && job.kind !== 'merge_resolve') {
531
+ try {
532
+ runtimeReview = await makeRuntimeReview({ wt, repoRoot: worktreeRepo, baseRef: baseRef(), sources: repositoryRun?.sources ?? [],
533
+ browser: managedBrowser, signal: setupController.signal, onChild: ch => {
534
+ workChildren.set(ch, agentId); groupKillChildren?.add(ch);
535
+ return () => { workChildren.delete(ch); groupKillChildren?.delete(ch); };
536
+ } });
537
+ runtimeNote = runtimeReview?.instructions ?? '';
538
+ } catch (error) { runtimeNote = `Configured runtime review is unavailable: ${envScrub(error.message)}. Use the repository’s existing checks and report unavailable runtime verification.`; }
493
539
  try {
494
540
  uiReview = await makeUiReview({ wt, signal: setupController.signal, onChild: ch => {
495
541
  workChildren.set(ch, agentId); groupKillChildren?.add(ch);
@@ -510,12 +556,19 @@ export function createAgentTurnExecution({
510
556
  assignmentContext: () => agentChildScope(job),
511
557
  // Codex's spawn takes only a catalog model (knownCodexModels, the
512
558
  // measured models_cache.json rows the roster already reports).
513
- children: { access: ORCHESTRATED_POSTURES[posture], ...cardBrain, ...(rt === 'codex' ? { catalog: knownCodexModels() } : {}) },
559
+ children: { access: ORCHESTRATED_POSTURES[posture], ...cardBrain, ...splitBrain(split, rt, cardBrain).brain, split, ...(rt === 'codex' ? { catalog: knownCodexModels() } : {}) },
514
560
  // Whether the words reached the CLI; Codex reads its inbox from hooks.
515
561
  notify: message => liveInput ? run.live?.inject(message) === true : false,
516
- taken: id => run.live?.onTaken(id) });
562
+ taken: id => run.live?.onTaken(id),
563
+ // Subagents on another CLI (crossCliLaunch.mjs), counted as live children while they run.
564
+ crossCli: crossCliFor({ rt, posture, wt, worktreeRepo, ledger: agentResume.subagents, trace, scrub: envScrub, onChild: ch => {
565
+ workChildren.set(ch, agentId); groupKillChildren?.add(ch);
566
+ return () => { workChildren.delete(ch); groupKillChildren?.delete(ch); };
567
+ } }) });
517
568
  }
518
- 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 });
569
+ 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,
570
+ runtimeConfig: runtimeReview?.config,
571
+ ...(reviewEnv ? { env: { ...process.env, ...reviewEnv } } : {}) });
519
572
  projectTools = tools.projectTools;
520
573
  localCleanup?.begin(turnId);
521
574
  const { toolsNote } = tools;
@@ -542,7 +595,7 @@ export function createAgentTurnExecution({
542
595
  intermediateFiles,
543
596
  researchIdentity: { projectId: PROJECT_ID, agentId, turnId, taskId: job.task?.id ?? job.repositoryTaskId },
544
597
  prompt: agentKickoff(job, { cardFiles: files.card, messageFiles: files.message, intermediateFiles }),
545
- system: agentTurnSystem({ taskKind, taskType: job.task?.taskType, runtimeLabel: runtimeById(rt)?.label, prerequisitesAccepted: job.prerequisitesAccepted === true, knowledgeDir: knowledgeDirFor(repoRoot), artifactsAccepted: getArtifactsAccepted(), toolsNote, artifactInstructions, toolInstructions: projectTools.instructions, repositoryRun, orchestration: orchestrator ? { posture, runtime: rt, ...cardBrain } : null }),
598
+ system: agentTurnSystem({ taskKind, taskType: job.task?.taskType, runtimeLabel: runtimeById(rt)?.label, prerequisitesAccepted: job.prerequisitesAccepted === true, knowledgeDir: knowledgeDirFor(repoRoot), artifactsAccepted: getArtifactsAccepted(), toolsNote, artifactInstructions, toolInstructions: projectTools.instructions, repositoryRun, orchestration: orchestrator ? { posture, runtime: rt, ...cardBrain, split, crossCli: orchestrator.crossCli() } : null }),
546
599
  agentTools: projectTools,
547
600
  writableDirs: repositoryRun?.writableDirs,
548
601
  ...((tools.envNeeded || repositoryRun?.tool || orchestrator) ? { mcpEnv: { ...tools.env, ...(repositoryRun?.tool?.env ?? {}), ...(orchestrator?.env ?? {}) } } : {}),
@@ -560,7 +613,9 @@ export function createAgentTurnExecution({
560
613
  // build for code, the fenced design/research postures otherwise.
561
614
  profile: posture,
562
615
  cardTurn: Boolean(orchestrator),
563
- onSubagent: (update) => orchestrator ? orchestrator.observe(update) : trace.agent(update),
616
+ onWorkflow: row => trace.workflow(row),
617
+ onSubagent: (update) => update.kind === 'workflow' || !orchestrator ? trace.agent(update) : orchestrator.observe(update),
618
+ ...(rt === 'codex' ? { knownSubagents: agentResume.subagents.codexTargets() } : {}),
564
619
  onSpawnResult: result => orchestrator?.spawnResult(result),
565
620
  cwd: wt,
566
621
  runtime: rt,
@@ -668,6 +723,7 @@ export function createAgentTurnExecution({
668
723
  }
669
724
  return;
670
725
  }
726
+ outsideRuns.observe(name, input, meta);
671
727
  brief?.append(meta?.result ? 'tool result' : 'tool call', meta?.result
672
728
  ? { id: meta.id, exitCode: meta.exitCode, isError: meta.isError, output: meta.output }
673
729
  : { name, input });
@@ -677,7 +733,7 @@ export function createAgentTurnExecution({
677
733
  trace.tool(toolEventOf(name, input, wt, envScrub));
678
734
  },
679
735
  // The CLI's own per-turn token counts, off the `result` event. One
680
- // result per CLI invocation. Internal-wait recovery combines those
736
+ // cumulative report per CLI invocation. Internal-wait recovery combines those
681
737
  // counts before this callback; project accounting still happens
682
738
  // SERVER-side, behind the settle's idempotent win,
683
739
  // because a retried settle must not charge the same turn twice.
@@ -794,12 +850,15 @@ export function createAgentTurnExecution({
794
850
  // owned by its review tool, then bind evidence to the final source
795
851
  // before the existing artifact upload and delivery settlement.
796
852
  try { await videoRender?.close(); } catch { /* a closed listener */ }
853
+ try { await runtimeReview?.close(); } catch { /* turn teardown continues */ }
797
854
  try { await managedBrowser?.close(); } catch { /* turn teardown continues */ }
798
855
  try { await uiReview?.close(); }
799
856
  finally {
800
857
  try { uiReviewEvidence = uiReview?.finalize() ?? uiReviewEvidence; }
801
858
  catch (error) { uiReviewEvidence = `Local UI review final source/evidence measurement unavailable: ${envScrub(error.message)}. Earlier checks do not establish the final source.`; }
802
859
  }
860
+ try { runtimeEvidence = runtimeReview?.finalize() ?? ''; }
861
+ catch { runtimeEvidence = 'Runtime review final source could not be measured; earlier results do not verify final source.'; }
803
862
  await run.live?.stop();
804
863
  await orchestrator?.close(run.interrupted);
805
864
  await brief?.stop();
@@ -910,6 +969,7 @@ export function createAgentTurnExecution({
910
969
  modelExports: intermediateDelivery?.modelExports,
911
970
  });
912
971
  appendUiReviewEvidence(body, uiReviewEvidence);
972
+ appendUiReviewEvidence(body, runtimeEvidence);
913
973
  // The agent's own account of what it did not deliver, for the pre-review.
914
974
  if (job.kind === 'task' && job.task && body.outcome === 'delivered' && res?.gaps)
915
975
  stashGaps(sessionMetaPath(wt, 'flowviant-agent-cards', agentId), job.task.id, res.gaps);
@@ -931,6 +991,9 @@ export function createAgentTurnExecution({
931
991
  if (body.outcome === 'delivered' && job.task?.taskType === 'tests' && res?.testFindings) {
932
992
  body.testFindings = measureTestFindings(res.testFindings, repositoryRun?.sourcesNow() ?? [{ wt, baseRef: baseRef(), id: job.repos?.length === 1 ? job.repos[0] : null }], envScrub);
933
993
  }
994
+ // The split this turn ran under, when the person approved it or Always is
995
+ // on: the batch's CLI, model and effort, as the job carried them.
996
+ if (orchestrator && split && split.mode !== 'ask') body.subagents = split;
934
997
  if (limit) {
935
998
  setRuntimeLimit(PROJECT_ID, rt, limit);
936
999
  // Every agent ON THIS CLI parks, because that account is shared: one
@@ -938,7 +1001,9 @@ export function createAgentTurnExecution({
938
1001
  // a machine can hold a Claude login and a Codex login, and a Codex
939
1002
  // limit is no reason to stop the Claude agents. The turn itself is
940
1003
  // reported as `nothing` — it did not deliver and it did not ask.
941
- await postAgentParked(limit, rt);
1004
+ // With the CLI's measured reset, when it said one (runtimeLimits.mjs).
1005
+ const codexEnv = agentResume.codexHome ? { ...process.env, CODEX_HOME: agentResume.codexHome } : undefined;
1006
+ await postAgentParked(limit, rt, await limitResetAt(rt, { threadId: agentResume.threadNow(), env: codexEnv }));
942
1007
  }
943
1008
  if (job.kind === 'merge_resolve') {
944
1009
  const resolution = () => repositoryRun
@@ -965,7 +1030,10 @@ export function createAgentTurnExecution({
965
1030
  // hold the lock on — see `runReviewEntry`. Only after the settle that may
966
1031
  // say delivered: the three `nothing` settles never opened it.
967
1032
  if (final && reply?.review === true) await runReviewEntry(agentId, wt, job.agentName, undefined, job);
968
- } finally { await repositoryRun?.tool?.close(); }
1033
+ } finally {
1034
+ for (const release of environmentReleases) release();
1035
+ await repositoryRun?.tool?.close();
1036
+ }
969
1037
  };
970
1038
 
971
1039
  return { runAgentTurnInPlace };
@@ -35,6 +35,7 @@ import { withoutTurnDeclaration } from './turnDeclaration.mjs';
35
35
  import { ANSWER_DELIVERED_USES, researchUseOf, withResearchHeader } from './researchUse.mjs';
36
36
  import { measuredModelExports } from './intermediateArtifacts.mjs';
37
37
  import { gapsMarkdown } from './turnGaps.mjs';
38
+ import { blockedAnswer } from './agentAsk.mjs';
38
39
 
39
40
  /** Materialization runs only for a declared delivery; settlement remains the
40
41
  * sole parser and the sole authority deciding whether it really delivered. */
@@ -130,6 +131,16 @@ export function agentTurnSettlement({ turnId, job, kind, out, lastAnswer, usage,
130
131
  */
131
132
  // Existing document deliveries default to the Markdown proof. Only an
132
133
  // explicit temporary-use declaration can deliver through the stored answer.
134
+ const required = res.gaps?.filter(g => g.disposition === 'required') ?? [];
135
+ if (res.outcome === 'delivered' && required.length && job.gapContinuationAccepted !== true) {
136
+ return report({ turnId, outcome: 'question',
137
+ answer: blockedAnswer('Required work remains. Should I finish it in this agent before delivery?', {
138
+ context: required.map(g => `${g.item}${g.why ? `: ${g.why}` : ''}`).join('\n\n'),
139
+ options: ['Finish the required work', 'Explain the remaining scope'],
140
+ }, envScrub),
141
+ ...(commits.length ? { commits } : {}), ...(usage ? { usage } : {}),
142
+ ...(res.progress ? { progress: envScrub(res.progress) } : {}), branch, worktree: wt });
143
+ }
133
144
  const briefUse = researchUseOf(job.task?.brief);
134
145
  const researchUse = kind.posture === 'research' ? briefUse ?? res.researchUse : null;
135
146
  const temporaryResearch = ANSWER_DELIVERED_USES.includes(researchUse);
@@ -148,7 +148,7 @@ export function createAgentTurnReports({ REJECT_RETRY_MS, baseDir = null }) {
148
148
  }
149
149
  };
150
150
 
151
- const postAgentParked = async (reason, runtime) => {
151
+ const postAgentParked = async (reason, runtime, resetsAt = null) => {
152
152
  try {
153
153
  await fetch(AGENT_PARKED_URL, {
154
154
  method: 'POST',
@@ -156,7 +156,10 @@ export function createAgentTurnReports({ REJECT_RETRY_MS, baseDir = null }) {
156
156
  signal: AbortSignal.timeout(15_000),
157
157
  // `runtime` scopes the park to that CLI's agents; a server that does
158
158
  // not know the key parks every agent, which is what it always did.
159
- body: JSON.stringify({ reason, ...(runtime ? { runtime } : {}) }),
159
+ // `resetsAt` is the CLI's measured reset (`limitResetAt`), sent only
160
+ // when measured: the server retries then, and an older server ignores
161
+ // the key and keeps its cooldown. A report, so no floor.
162
+ body: JSON.stringify({ reason, ...(runtime ? { runtime } : {}), ...(resetsAt ? { resetsAt } : {}) }),
160
163
  });
161
164
  } catch {
162
165
  /* the next turn will hit the same limit and try again */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flowviant",
3
- "version": "0.125.29",
3
+ "version": "0.125.31",
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": {