flowviant 0.125.9 → 0.125.13

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.
@@ -43,7 +43,7 @@ export function createWorkAgentMerges({
43
43
  baseDir,
44
44
  gitNet,
45
45
  baseRef,
46
- runReviewEntry,
46
+ runMergeCheck,
47
47
  onRepoChanged,
48
48
  landed,
49
49
  agentRemoteAt,
@@ -157,7 +157,7 @@ export function createWorkAgentMerges({
157
157
  if (job.repositoryMerge) {
158
158
  if (!repositoryPlaces) throw new Error('Project repository worktrees are unavailable.');
159
159
  const result = await mergeRepositoryPlace({ job, entries: repositoryPlaces.entries(place), baseDir,
160
- check: (r) => runReviewEntry(agentId, r.wt, job.agentName, r),
160
+ check: (r) => runMergeCheck(agentId, r.wt, r),
161
161
  progress: async (states, retry) => {
162
162
  const response = await fleetRequest(fleetEndpoint('agent-merge-progress', FLEET_URL), {
163
163
  body: { agentId, instance: DAEMON_INSTANCE, states, retry } });
@@ -195,12 +195,12 @@ export function createWorkAgentMerges({
195
195
  });
196
196
  return;
197
197
  }
198
- // The branch changed, so the previous check — and the previous
199
- // pre-review — answered about a different tree. Re-read it before
200
- // anything merges: a failed merge sends the agent BACK to review, which
201
- // is a review-entry beat like any other, and a stale reading standing
202
- // over a rebased branch is exactly what `precheckSha` exists to void.
203
- const check = await runReviewEntry(agentId, wt, job.agentName);
198
+ // The branch changed, so the previous check answered about a different
199
+ // tree. Re-run it before anything merges: a failed check sends the
200
+ // agent BACK to review. The CHECK ALONE (`runMergeCheck`): the earlier
201
+ // pre-review now reads as stale by `precheckSha`, and a fresh one would
202
+ // hold this approved merge behind a model call (2026-10-21).
203
+ const check = await runMergeCheck(agentId, wt);
204
204
  if (check?.status === 'failed') {
205
205
  await report({ agentId, ok: false, detail: check.text || check.output || 'The repository check failed after folding base.' });
206
206
  return;
@@ -19,6 +19,8 @@ export function createWorkAgentReview({ runCheck, runPrecheck }) {
19
19
  * `runCheck` directly; a second thing to run at the same moment is a second
20
20
  * thing three call sites can forget.
21
21
  *
22
+ * THE MERGE PATH IS NOT A REVIEW ENTRY (2026-10-21) — see `runMergeCheck`.
23
+ *
22
24
  * THE CHECK FIRST, ALWAYS. It is a local command whose answer the board wants
23
25
  * on the row immediately; the pre-review is a model call that may take
24
26
  * minutes. Ordering them the other way would put a label behind a label.
@@ -42,5 +44,26 @@ export function createWorkAgentReview({ runCheck, runPrecheck }) {
42
44
  return result;
43
45
  };
44
46
 
45
- return { runReviewEntry };
47
+ /**
48
+ * THE CHECK ALONE, for the merge path (2026-10-21): the stale fold before an
49
+ * approved merge, each repository of a coordinated merge, and a merge
50
+ * resolve's settle. The check gates there — a failed one sends the agent
51
+ * back to Review — so it still runs. The pre-review does not: it labels and
52
+ * never blocks, yet run here it sat inside the place's writer lock ahead of
53
+ * the push and held an approved merge for up to its five-minute cap. One
54
+ * merge measured 13:40 check → 13:43 resolve settled → 13:43 check → 13:48
55
+ * landed, both waits a pre-review, the second wedged and posting nothing.
56
+ * A person already approved this work; a reading of it nobody is waiting on
57
+ * cannot be what the landing waits for. A merge that fails back to Review
58
+ * keeps its earlier reading, which the server marks stale by `precheckSha`.
59
+ */
60
+ const runMergeCheck = async (agentId, wt, repository) => {
61
+ try {
62
+ return (await runCheck(agentId, wt, repository)) ?? { status: 'unknown' };
63
+ } catch {
64
+ return { status: 'unknown' };
65
+ }
66
+ };
67
+
68
+ return { runReviewEntry, runMergeCheck };
46
69
  }
@@ -1,3 +1,6 @@
1
+ import { imageRevisionInstructions } from './artifactIteration.mjs';
2
+ import { measureTestFindings } from './testFindings.mjs';
3
+ import { fetchResultInputs } from './resultInputs.mjs';
1
4
  import { agentWork } from './workActivity.mjs';
2
5
  import { relayedText } from './prWorkflow.mjs';
3
6
  import { measuredMergeResolution } from './agentMergeResolution.mjs';
@@ -44,7 +47,7 @@ import { runAgentWithInternalWaits } from './agentInternalWait.mjs';
44
47
  import { brainFor } from './workBrain.mjs';
45
48
  import { refreshRestartBase } from './workAgentRecovery.mjs';
46
49
  import { begunTurnRefusal } from './workAgentTurnBegun.mjs';
47
- import { agentTurnSettlement, interruptedTurnSettlement } from './workAgentTurnOutcome.mjs';
50
+ import { agentDeclaredDelivery, agentTurnSettlement, interruptedTurnSettlement } from './workAgentTurnOutcome.mjs';
48
51
  import { ensureArtifactDir, ensureFenceScratch } from './artifacts.mjs';
49
52
  import { agentBrowserHome } from './noWindowEnv.mjs';
50
53
  import { agentTurnResume } from './workAgentResume.mjs';
@@ -56,6 +59,7 @@ import { ORCHESTRATED_POSTURES, orchestratesPosture } from './orchestratorPrompt
56
59
  import { knownCodexModels } from './runtimeOfferings.mjs';
57
60
  import { codexSubagentsSafe } from './runtimeCodex.mjs';
58
61
  import { measuredRuntimeVersion } from './runtimeDetection.mjs';
62
+ import { createIntermediateArtifacts } from './intermediateArtifacts.mjs';
59
63
 
60
64
  export function createAgentTurnExecution({
61
65
  admit,
@@ -73,6 +77,7 @@ export function createAgentTurnExecution({
73
77
  reportSessionWorktree,
74
78
  artifacts,
75
79
  runReviewEntry,
80
+ runMergeCheck,
76
81
  workChildren,
77
82
  agentChildren,
78
83
  postAgentTrace,
@@ -81,6 +86,8 @@ export function createAgentTurnExecution({
81
86
  postAgentParked,
82
87
  fetchAgentFiles,
83
88
  makeLiveMessages = createLiveMessages,
89
+ runTurnImpl = runTurn,
90
+ exportModel,
84
91
  }) {
85
92
  const reportDelivery = createDeliveryReporter();
86
93
  /** How long the final flush may hold the settle. Bounded because the settle
@@ -272,6 +279,11 @@ export function createAgentTurnExecution({
272
279
  * No key is no call: a turn without files touches nothing here, and its
273
280
  * prompt is byte-for-byte what it was.
274
281
  */
282
+ await fetchResultInputs(wt, job.resultInputs);
283
+ const intermediate = createIntermediateArtifacts({ wt, agentId,
284
+ path: sessionMetaPath(wt, 'flowviant-agent-artifacts', agentId),
285
+ ...(exportModel ? { exportPage: exportModel } : {}) });
286
+ const intermediateFiles = intermediate.inputs();
275
287
  const files =
276
288
  Array.isArray(job.attachments) && job.attachments.length > 0
277
289
  ? await fetchAgentFiles(wt, job.attachments)
@@ -376,7 +388,7 @@ export function createAgentTurnExecution({
376
388
  const spawnedChildren = new Set();
377
389
  /** The CLI, unless a person already asked to stop this turn (Interrupt
378
390
  * now): then nothing starts — no fresh retry, no continuation. */
379
- const runCli = (args) => (run.interrupted ? Promise.resolve('') : runTurn(args));
391
+ const runCli = (args) => (run.interrupted ? Promise.resolve('') : runTurnImpl(args));
380
392
  /**
381
393
  * WHAT THIS TURN SPENT, AS THE CLI COUNTED IT (2026-09-19).
382
394
  *
@@ -408,10 +420,15 @@ export function createAgentTurnExecution({
408
420
  /** The agent's artifact directory as it stood before this turn — the
409
421
  * tab lane's rule, in the agent's own worktree (2026-09-22). */
410
422
  const artifactsBefore = beforeArtifacts(wt);
411
- const artifactInstructions = getArtifactsAccepted()
423
+ let artifactInstructions = getArtifactsAccepted()
412
424
  ? await artifacts.prepare?.({ placeDir: wt, before: artifactsBefore, agentId }) ?? '' : '';
425
+ if (job.imageRevision) {
426
+ artifactsBefore.imageRevision = job.imageRevision;
427
+ artifactInstructions += '\n\n' + imageRevisionInstructions(job.imageRevision, job.resultInputs);
428
+ }
413
429
  let projectTools = null;
414
430
  let orchestrator = null;
431
+ let intermediateDelivery = null;
415
432
  try {
416
433
  if (orchestrating) {
417
434
  orchestrator = await createOrchestrator({ turnId, runtime: rt, trace, admit,
@@ -433,15 +450,17 @@ export function createAgentTurnExecution({
433
450
  format: async (followup) => {
434
451
  orchestrator?.note(followup);
435
452
  const attached = followup.attachments?.length ? await fetchAgentFiles(wt, followup.attachments.map((file) => ({ ...file, from: 'message' }))) : { message: [], card: [] };
436
- return agentFollowupKickoff(job, followup, { messageFiles: attached.message, cardFiles: files.card, orchestrated: Boolean(orchestrator) });
453
+ return agentFollowupKickoff(job, followup, { messageFiles: attached.message, cardFiles: files.card, intermediateFiles, orchestrated: Boolean(orchestrator) });
437
454
  },
438
455
  })) } : {}),
439
456
  // The kickoff and the system prompt, composed by the agent lane's
440
457
  // shape (workAgentTurnShape.mjs) from what this run measured.
441
458
  researchHandoff: job.researchHandoff,
459
+ resultInputs: job.resultInputs,
460
+ intermediateFiles,
442
461
  researchIdentity: { projectId: PROJECT_ID, agentId, turnId, taskId: job.task?.id ?? job.repositoryTaskId },
443
- prompt: agentKickoff(job, { cardFiles: files.card, messageFiles: files.message }),
444
- system: agentTurnSystem({ taskKind, 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 }),
462
+ prompt: agentKickoff(job, { cardFiles: files.card, messageFiles: files.message, intermediateFiles }),
463
+ 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 }),
445
464
  agentTools: projectTools,
446
465
  writableDirs: repositoryRun?.writableDirs,
447
466
  ...((tools.envNeeded || repositoryRun?.tool || orchestrator) ? { mcpEnv: { ...tools.env, ...(repositoryRun?.tool?.env ?? {}), ...(orchestrator?.env ?? {}) } } : {}),
@@ -724,7 +743,18 @@ export function createAgentTurnExecution({
724
743
  void reportSessionWorktree(place).catch(() => {});
725
744
  // A file card delivers uploaded bytes, not an in-memory retry. Otherwise
726
745
  // its dependent card can start while Review has nothing to show.
727
- await artifacts.report({ placeDir: wt, before: artifactsBefore, agentId, turnId, requireStored: kind.artifact != null });
746
+ if (!run.interrupted && agentDeclaredDelivery(out, lastAnswer)) {
747
+ try { intermediateDelivery = await intermediate.prepare({ job, turnId, taskKind, before: artifactsBefore, signal: setupController.signal }); }
748
+ catch (error) {
749
+ if (run.interrupted) throw error;
750
+ // The failed page still has its preview/files for repair. Reporting
751
+ // those bytes cannot turn a failed export into a delivered card.
752
+ try { await artifacts.report({ placeDir: wt, before: artifactsBefore, agentId, turnId, requireStored: kind.artifact != null }); }
753
+ catch (uploadError) { throw new Error(`${error.message}; artifact reporting also failed: ${uploadError.message}`); }
754
+ throw error;
755
+ }
756
+ }
757
+ await artifacts.report({ placeDir: wt, before: intermediateDelivery?.uploadBefore ?? artifactsBefore, agentId, turnId, requireStored: kind.artifact != null });
728
758
  }
729
759
 
730
760
 
@@ -757,8 +787,13 @@ export function createAgentTurnExecution({
757
787
  artifactsBefore,
758
788
  branch,
759
789
  wt,
790
+ modelExports: intermediateDelivery?.modelExports,
760
791
  });
792
+ if (body.outcome === 'delivered') intermediateDelivery?.commit();
761
793
  if (res) setRuntimeLimit(PROJECT_ID, rt, null);
794
+ if (body.outcome === 'delivered' && job.task?.taskType === 'tests' && res?.testFindings) {
795
+ body.testFindings = measureTestFindings(res.testFindings, repositoryRun?.sourcesNow() ?? [{ wt, baseRef: baseRef(), id: job.repos?.length === 1 ? job.repos[0] : null }], envScrub);
796
+ }
762
797
  if (limit) {
763
798
  setRuntimeLimit(PROJECT_ID, rt, limit);
764
799
  // Every agent ON THIS CLI parks, because that account is shared: one
@@ -777,7 +812,7 @@ export function createAgentTurnExecution({
777
812
  const rows = repositoryRun?.sourcesNow() ?? [{ wt }];
778
813
  for (const r of rows) {
779
814
  const head = gitOrNull(['rev-parse', 'HEAD'], r.wt);
780
- const check = await runReviewEntry(agentId, r.wt, job.agentName, repositoryRun ? r : undefined);
815
+ const check = await runMergeCheck(agentId, r.wt, repositoryRun ? r : undefined);
781
816
  if (check?.status === 'failed') {
782
817
  body.mergeResolved = false;
783
818
  body.mergeDetail = relayedText(check.text || check.output || 'The repository check failed during merge recovery.').slice(0, 2000);
@@ -33,8 +33,14 @@ import { scrub as envScrub } from './uplinkScrub.mjs';
33
33
  import { usableImageArtifact } from './imageArtifact.mjs';
34
34
  import { withoutTurnDeclaration } from './turnDeclaration.mjs';
35
35
  import { ANSWER_DELIVERED_USES, researchUseOf, withResearchHeader } from './researchUse.mjs';
36
+ import { measuredModelExports } from './intermediateArtifacts.mjs';
36
37
 
37
- export function agentTurnSettlement({ turnId, job, kind, out, lastAnswer, usage, model = null, commits, artifactsBefore, branch, wt }) {
38
+ /** Materialization runs only for a declared delivery; settlement remains the
39
+ * sole parser and the sole authority deciding whether it really delivered. */
40
+ export const agentDeclaredDelivery = (out, lastAnswer) =>
41
+ ((lastAnswer && parseTurnResult(lastAnswer)) || parseTurnResult(out))?.outcome === 'delivered';
42
+
43
+ export function agentTurnSettlement({ turnId, job, kind, out, lastAnswer, usage, model = null, commits, artifactsBefore, branch, wt, modelExports = [] }) {
38
44
  const res = (lastAnswer && parseTurnResult(lastAnswer)) || parseTurnResult(out);
39
45
  /**
40
46
  * A LIMIT IS ONLY A LIMIT WHEN THE TURN PRODUCED NOTHING.
@@ -94,7 +100,7 @@ export function agentTurnSettlement({ turnId, job, kind, out, lastAnswer, usage,
94
100
  * Its product is under `.flowviant/artifacts/`, and the kind's table
95
101
  * (agentTaskKinds.mjs) says which file proves it: a `.html` mockup for
96
102
  * design, a `.html` deck, a `.md` write-up for research, and a `.html`
97
- * page for a 3D model (the model is built in it, 0.107.0). A turn that
103
+ * page for a 3D model plus its measured page-bound local GLB export. A turn that
98
104
  * says "delivered" without having written one would land the agent in Review
99
105
  * with nothing to look at and a summary describing a page that does not
100
106
  * exist — so it settles `nothing` with the measured sentence, and the
@@ -135,7 +141,8 @@ export function agentTurnSettlement({ turnId, job, kind, out, lastAnswer, usage,
135
141
  && (kind.posture !== 'image' || usableImageArtifact(e)));
136
142
  const standing = scanArtifacts(wt);
137
143
  const wrote = has(changedArtifacts(artifactsBefore, standing));
138
- const present = wrote || ((job.kind !== 'task' || job.redo === true) && has(standing));
144
+ const exported = kind.exportModel && measuredModelExports(wt, modelExports);
145
+ const present = wrote || exported || ((job.kind !== 'task' || job.redo === true) && has(standing));
139
146
  if (!present) {
140
147
  return report({
141
148
  turnId,
@@ -147,6 +154,11 @@ export function agentTurnSettlement({ turnId, job, kind, out, lastAnswer, usage,
147
154
  worktree: wt,
148
155
  });
149
156
  }
157
+ if (kind.exportModel && !exported) {
158
+ return report({ turnId, outcome: 'nothing',
159
+ answer: 'The model page has no verified local .glb export. Retry this producer card to export its exact page before dependent work starts; no manual download or Library keeping is required.',
160
+ ...(usage ? { usage } : {}), branch, worktree: wt });
161
+ }
150
162
  }
151
163
  const prerequisite = res.prerequisite;
152
164
  const cannotRequest = prerequisite && job.prerequisitesAccepted !== true
@@ -159,14 +171,23 @@ export function agentTurnSettlement({ turnId, job, kind, out, lastAnswer, usage,
159
171
  turnId,
160
172
  ...(prerequisite && !cannotRequest ? { prerequisite } : {}),
161
173
  outcome: res.outcome,
162
- answer: cannotRequest ?? envScrub(kind.posture === 'research' && researchUse
163
- ? withResearchHeader(researchUse, res.answer ?? '') : res.answer ?? ''),
174
+ answer: cannotRequest ?? envScrub((kind.posture === 'research' && researchUse
175
+ ? withResearchHeader(researchUse, res.answer ?? '') : res.answer ?? '') +
176
+ (job.task?.taskType === 'tests' && res.raised?.length ? '\n\nAdditional CLI-reported findings (not filed as fixes):\n' + res.raised.map(r => `${r.title}\n${r.brief ?? ''}`).join('\n\n') : '')),
177
+ // CLI-authored evidence travels separately from the short answer and
178
+ // measured commits/checks. Older turns simply send no report.
179
+ ...(kind.posture === 'build' && res.outcome === 'delivered' && res.verification
180
+ ? { verification: envScrub(res.verification) } : {}),
181
+ ...(kind.posture === 'build' && res.outcome === 'delivered' && res.handover
182
+ ? { handover: envScrub(res.handover) } : {}),
183
+ ...(kind.posture === 'build' && res.outcome === 'delivered' && res.automation
184
+ ? { automation: envScrub(res.automation) } : {}),
164
185
  ...(commits.length ? { commits } : {}),
165
186
  // SCRUBBED like the answer beside it: a raised card lands in the
166
187
  // project doc every member reads, and a brief quoting the `.env` the
167
188
  // agent just read ("value sk_live_… is logged in pay.ts") went out
168
189
  // verbatim while the same value in the summary was redacted.
169
- ...(res.raised?.length
190
+ ...(job.task?.taskType !== 'tests' && res.raised?.length
170
191
  ? {
171
192
  raised: res.raised.map((r) => ({
172
193
  title: envScrub(r.title).slice(0, 300),
@@ -25,6 +25,7 @@
25
25
  import { AGENT_TASK_KICKOFF, AGENT_HUMAN_KICKOFF, SYSTEM_AGENT_FOR, withProjectContext } from './prompts.mjs';
26
26
  import { INTERNAL_WAIT_CONTRACT } from './agentInternalWait.mjs';
27
27
  import { orchestratorContract } from './orchestratorPrompt.mjs';
28
+ import { intermediateArtifactContext } from './intermediateArtifacts.mjs';
28
29
 
29
30
  /** What every kickoff of this job is handed: the agent, the card and its
30
31
  * discussion, the job's context handoffs, the card's files, the place in
@@ -35,6 +36,7 @@ const shared = (job, cardFiles) => ({
35
36
  cardThread: job.cardThread,
36
37
  investigationContext: job.investigationContext,
37
38
  researchContext: job.researchContext,
39
+ resultContext: job.resultContext,
38
40
  cardFiles,
39
41
  position: job.position ?? 1,
40
42
  total: job.total ?? 1,
@@ -42,8 +44,8 @@ const shared = (job, cardFiles) => ({
42
44
 
43
45
  /** The turn's first prompt: the card, on a task turn; the person's words
44
46
  * otherwise. `cardFiles` and `messageFiles` are the files already on disk. */
45
- export const agentKickoff = (job, { cardFiles, messageFiles }) =>
46
- job.kind === 'task' && job.task
47
+ export const agentKickoff = (job, { cardFiles, messageFiles, intermediateFiles }) =>
48
+ (job.kind === 'task' && job.task
47
49
  ? AGENT_TASK_KICKOFF({
48
50
  // The card's discussion (0.106.0), printed inside THE CARD by
49
51
  // the same builder the run's stash wrote down — and the files
@@ -62,31 +64,31 @@ export const agentKickoff = (job, { cardFiles, messageFiles }) =>
62
64
  // The files (0.112.0): the person's inside their words, the
63
65
  // card's at the foot of its discussion.
64
66
  messageFiles,
65
- });
67
+ })) + intermediateArtifactContext(intermediateFiles);
66
68
 
67
69
  /** A live message sent while the turn runs, as the CLI is handed it — on an
68
70
  * orchestrated card with its id, so the route it takes is reported. */
69
- export const agentFollowupKickoff = (job, followup, { cardFiles, messageFiles, orchestrated }) =>
71
+ export const agentFollowupKickoff = (job, followup, { cardFiles, messageFiles, intermediateFiles, orchestrated }) =>
70
72
  AGENT_HUMAN_KICKOFF({
71
73
  ...shared(job, cardFiles),
72
74
  message: followup.body + (followup.libraryPrompt ?? '') + (orchestrated ? `\nMessage id: ${followup.id}. Choose and report its route with card_orchestrator.route_message.` : ''),
73
75
  askedByName: followup.askedByName,
74
76
  messageFiles,
75
- });
77
+ }) + intermediateArtifactContext(intermediateFiles);
76
78
 
77
79
  /** The turn's system prompt, in its fixed section order. `orchestration` is
78
80
  * null on a solo run, else what the contract names: `{ posture, runtime,
79
81
  * model, effort }` (orchestratorPrompt.mjs). */
80
- export function agentTurnSystem({ taskKind, runtimeLabel, prerequisitesAccepted, knowledgeDir, artifactsAccepted, toolsNote, artifactInstructions, toolInstructions, repositoryRun, orchestration = null }) {
82
+ export function agentTurnSystem({ taskKind, taskType, runtimeLabel, prerequisitesAccepted, knowledgeDir, artifactsAccepted, toolsNote, artifactInstructions, toolInstructions, repositoryRun, orchestration = null }) {
81
83
  // The knowledge paragraph, when this box holds a library — the same
82
84
  // composer the tabs use, so an agent and a tab can never be told two
83
85
  // different things about the same directory.
84
86
  // Spoken to the CLI that runs it (0.115.0): a mockup on Codex is
85
87
  // told it is Codex, never Claude.
86
- return withProjectContext(SYSTEM_AGENT_FOR(taskKind, runtimeLabel, { prerequisitesAccepted }), {
88
+ return withProjectContext(SYSTEM_AGENT_FOR(taskKind, runtimeLabel, { prerequisitesAccepted, taskType }), {
87
89
  knowledgeDir,
88
90
  // ARTIFACTS (0.94.0), while the server can show one — an agent's
89
91
  // land on its page, under the facts row.
90
92
  artifacts: artifactsAccepted,
91
- }) + (toolsNote ? `\n\n${toolsNote}` : '') + (artifactInstructions ? `\n\n${artifactInstructions}` : '') + `\n\n${INTERNAL_WAIT_CONTRACT}` + (toolInstructions ? `\n\n${toolInstructions}` : '') + (repositoryRun ? `\n\n${repositoryRun.note}\nRepository command argv: ${JSON.stringify([repositoryRun.tool?.config.command, ...(repositoryRun.tool?.config.args.slice(0, -1) ?? []), "__repository", "<name>"])}` : '') + (orchestration ? `\n\n${orchestratorContract(orchestration)}` : '');
93
+ }) + (toolsNote ? `\n\n${toolsNote}` : '') + (artifactInstructions ? `\n\n${artifactInstructions}` : '') + `\n\n${INTERNAL_WAIT_CONTRACT}` + (toolInstructions ? `\n\n${toolInstructions}` : '') + (repositoryRun ? `\n\n${repositoryRun.note}\nRepository command argv: ${JSON.stringify([repositoryRun.tool?.config.command, ...(repositoryRun.tool?.config.args.slice(0, -1) ?? []), "__repository", "<name>"])}` : '') + (orchestration ? `\n\n${orchestratorContract({ ...orchestration, taskType })}` : '');
92
94
  }
@@ -48,6 +48,7 @@ export function createWorkAgentTurns({
48
48
  reportSessionWorktree,
49
49
  artifacts,
50
50
  runReviewEntry,
51
+ runMergeCheck,
51
52
  publishAgentBranch,
52
53
  stopAgentPublishing = () => {},
53
54
  admit,
@@ -59,6 +60,7 @@ export function createWorkAgentTurns({
59
60
  makeBrief = createAgentBriefRunner({ admit, workChildren }),
60
61
  /** The run's live-message relay (workLiveMessages.mjs); a test's stand-in. */
61
62
  makeLiveMessages,
63
+ exportModel,
62
64
  }) {
63
65
  const reportDelivery = createDeliveryReporter();
64
66
  const {
@@ -133,6 +135,8 @@ export function createWorkAgentTurns({
133
135
  reportSessionWorktree,
134
136
  artifacts,
135
137
  runReviewEntry,
138
+ runMergeCheck,
139
+ exportModel,
136
140
  workChildren,
137
141
  agentChildren,
138
142
  postAgentTrace,
@@ -17,7 +17,7 @@ import { excludeInWorktree } from './git.mjs';
17
17
  import { FLOWVIANT_OWN_PATHS } from './knowledgeLibrary.mjs';
18
18
  import { safeFileName } from './safeFileName.mjs';
19
19
  import { fleetEndpoint } from './fleetWire.mjs';
20
- import { readAgentFiles } from './agentFiles.mjs';
20
+ import { AGENT_FILES_PER_MESSAGE_MAX, readAgentFiles } from './agentFiles.mjs';
21
21
 
22
22
  /**
23
23
  * ONE FILE'S CEILING, for both lanes (0.113.0, the owner 2026-09-29: "20 MB
@@ -27,6 +27,8 @@ import { readAgentFiles } from './agentFiles.mjs';
27
27
  * is what 0.112.0's 10 MB did to anything bigger, and why the server floors a
28
28
  * file over 10 MB at this release (`DAEMON_LARGE_FILES_MIN`).
29
29
  */
30
+ export const SESSION_ATTACHMENTS_PER_TURN_MAX = AGENT_FILES_PER_MESSAGE_MAX;
31
+
30
32
  export const ATTACHMENT_MAX_BYTES = 20 * 1024 * 1024;
31
33
 
32
34
  export function createWorkAttachments() {
@@ -206,7 +208,7 @@ export function createWorkAttachments() {
206
208
  */
207
209
  const fetchTabFiles = async (wt, attachments) => {
208
210
  if (!Array.isArray(attachments) || attachments.length === 0) return [];
209
- const list = attachments.slice(0, 8);
211
+ const list = attachments.slice(0, SESSION_ATTACHMENTS_PER_TURN_MAX);
210
212
  const landed = await landFiles(wt, list, 'attachment', false);
211
213
  return list.map((a, i) => (landed[i] ? { path: landed[i] } : { missed: safeUploadName(a?.name) }));
212
214
  };
@@ -6,12 +6,12 @@ import { readFileSync, writeFileSync, renameSync, rmSync } from 'node:fs';
6
6
  import { randomUUID } from 'node:crypto';
7
7
  import { fleetRequest } from './fleetPost.mjs';
8
8
  import { fleetEndpoint, isConsideredRefusal } from './fleetWire.mjs';
9
- import { DAEMON_INSTANCE, FLEET_URL } from './config.mjs';
9
+ import { DAEMON_INSTANCE, FLEET_URL, VERSION } from './config.mjs';
10
10
  import { messageIdsForSession } from './claudeSessions.mjs';
11
11
 
12
12
  export function createLiveMessages({ scope, turnId, attempt, cwd, sessionMetaPath, format, observe = () => {}, interrupted = () => false,
13
13
  request = fleetRequest, nativeIds = messageIdsForSession, setTimer = setInterval, clearTimer = clearInterval, finalFlushMs = 1_000 }) {
14
- const identity = { scope, turnId, instance: DAEMON_INSTANCE, ...(attempt !== undefined ? { attempt } : {}) };
14
+ const identity = { scope, turnId, daemonVersion: VERSION, instance: DAEMON_INSTANCE, ...(attempt !== undefined ? { attempt } : {}) };
15
15
  const file = sessionMetaPath(cwd, 'flowviant-live-messages', turnId);
16
16
  let sessionId = null;
17
17
  let timer = null;
@@ -1,3 +1,4 @@
1
+ import { fetchResultInputs } from './resultInputs.mjs';
1
2
  import { sessionWork, reportWorkOutcome } from './workActivity.mjs';
2
3
  /**
3
4
  * A WORKBENCH TAB'S TURN — offered, admitted, placed, adopted, pinned to its
@@ -693,6 +694,7 @@ export function createWorkSessionTurns({
693
694
  // Files first, then the message that references them: the agent
694
695
  // must be able to open what it is being told about. Only the ones
695
696
  // that actually landed are named.
697
+ await fetchResultInputs(dir.wt, job.resultInputs);
696
698
  const filesNote = tabFilesNote(await fetchTabFiles(dir.wt, job.attachments));
697
699
  const message = [job.body, filesNote, adoptNote, carryNote]
698
700
  .filter(Boolean)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flowviant",
3
- "version": "0.125.9",
3
+ "version": "0.125.13",
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": {