flowviant 0.125.7 → 0.125.12

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 (65) hide show
  1. package/README.md +80 -1
  2. package/bin/cli.mjs +7 -1
  3. package/bin/lib/activityFeed.mjs +69 -0
  4. package/bin/lib/agentMergeReports.mjs +46 -0
  5. package/bin/lib/agentMergeResolution.mjs +48 -0
  6. package/bin/lib/agentPlan.mjs +14 -2
  7. package/bin/lib/agentTaskTypes.mjs +11 -7
  8. package/bin/lib/artifactContracts.mjs +8 -0
  9. package/bin/lib/artifactIteration.mjs +18 -0
  10. package/bin/lib/artifactReporter.mjs +16 -3
  11. package/bin/lib/claudePosture.mjs +21 -0
  12. package/bin/lib/claudeStream.mjs +25 -4
  13. package/bin/lib/codexResearch.mjs +35 -5
  14. package/bin/lib/commandSpecs.mjs +8 -5
  15. package/bin/lib/daemonJournal.mjs +102 -0
  16. package/bin/lib/daemonLogging.mjs +22 -18
  17. package/bin/lib/fleet.mjs +16 -6
  18. package/bin/lib/fleetReports.mjs +12 -5
  19. package/bin/lib/hooks/orchestrator.mjs +3 -1
  20. package/bin/lib/logReplay.mjs +133 -0
  21. package/bin/lib/nativeAgentTools.mjs +20 -3
  22. package/bin/lib/observedLines.mjs +63 -0
  23. package/bin/lib/orchestrator.mjs +40 -7
  24. package/bin/lib/orchestratorNative.mjs +133 -3
  25. package/bin/lib/orchestratorPrompt.mjs +51 -4
  26. package/bin/lib/postureTools.mjs +14 -6
  27. package/bin/lib/printable.mjs +1 -1
  28. package/bin/lib/prompts.mjs +122 -7
  29. package/bin/lib/repositoryDelivery.mjs +1 -1
  30. package/bin/lib/repositoryMerge.mjs +1 -1
  31. package/bin/lib/researchHandoff.mjs +144 -0
  32. package/bin/lib/researchHandoffText.mjs +3 -0
  33. package/bin/lib/researchKnowledge.mjs +60 -0
  34. package/bin/lib/researchRestoration.mjs +38 -0
  35. package/bin/lib/resultInputs.mjs +83 -0
  36. package/bin/lib/runTurn.mjs +76 -43
  37. package/bin/lib/runtimeClaude.mjs +9 -4
  38. package/bin/lib/runtimeCodex.mjs +51 -22
  39. package/bin/lib/runtimeEvents.mjs +17 -7
  40. package/bin/lib/testFindings.mjs +60 -0
  41. package/bin/lib/testFindingsContract.mjs +2 -0
  42. package/bin/lib/turnDiagnostics.mjs +2 -1
  43. package/bin/lib/turnOutput.mjs +63 -0
  44. package/bin/lib/turnRecord.mjs +11 -0
  45. package/bin/lib/turnSubagents.mjs +13 -3
  46. package/bin/lib/ui.mjs +2 -2
  47. package/bin/lib/uplinkScrub.mjs +49 -0
  48. package/bin/lib/views.mjs +49 -58
  49. package/bin/lib/wikiRunner.mjs +2 -2
  50. package/bin/lib/work.mjs +2 -2
  51. package/bin/lib/workActivity.mjs +65 -0
  52. package/bin/lib/workAgentBriefRunner.mjs +2 -2
  53. package/bin/lib/workAgentMerges.mjs +57 -52
  54. package/bin/lib/workAgentPlans.mjs +76 -27
  55. package/bin/lib/workAgentPrecheck.mjs +1 -1
  56. package/bin/lib/workAgentTurnExecution.mjs +55 -9
  57. package/bin/lib/workAgentTurnOutcome.mjs +12 -3
  58. package/bin/lib/workAgentTurnReports.mjs +17 -3
  59. package/bin/lib/workAgentTurnShape.mjs +8 -5
  60. package/bin/lib/workAgentTurns.mjs +8 -6
  61. package/bin/lib/workIntake.mjs +8 -2
  62. package/bin/lib/workMergeBatch.mjs +7 -1
  63. package/bin/lib/workPlanRecovery.mjs +50 -0
  64. package/bin/lib/workSessionTurns.mjs +7 -2
  65. package/package.json +1 -1
@@ -14,11 +14,13 @@
14
14
  * Daemon→server reports, every one of them: no version floor, and a permanent
15
15
  * 4xx is the server saying it will never take the body (see each POST).
16
16
  */
17
+ import { join } from 'node:path';
18
+ import { createReportJournal } from './reportJournal.mjs';
17
19
  import { FLEET_URL } from './config.mjs';
18
20
  import { fleetEndpoint, isConsideredRefusal } from './fleetWire.mjs';
19
21
  import { fleetHeaders, postFleetJson } from './fleetPost.mjs';
20
22
 
21
- export function createAgentTurnReports({ REJECT_RETRY_MS }) {
23
+ export function createAgentTurnReports({ REJECT_RETRY_MS, baseDir = null }) {
22
24
  const AGENT_TURN_DONE_URL = fleetEndpoint('agent-turn-done', FLEET_URL);
23
25
  const AGENT_TRACE_URL = fleetEndpoint('agent-trace', FLEET_URL);
24
26
  const AGENT_ACTIVITY_URL = fleetEndpoint('agent-activity', FLEET_URL);
@@ -40,8 +42,17 @@ export function createAgentTurnReports({ REJECT_RETRY_MS }) {
40
42
  * BOUNDED by the roster itself: a held body's clock is refreshed while the
41
43
  * server keeps offering its turn, and once offering stops — settled by the
42
44
  * re-POST, or expired server-side — the grace below is all that keeps it.
45
+ *
46
+ * EVERY HELD BODY IS JOURNALED (2026-10-20), not only a merge recovery's.
47
+ * Any agent turn whose body was held in memory alone lost it to a restart
48
+ * between the answer and the server's 200, and the server's re-offer to the
49
+ * successor ran the turn again. The settle is bound to the machine
50
+ * credential, not the process, so the successor's re-POST lands it.
43
51
  */
44
- const agentReported = new Map(); // turnId -> { body, at }
52
+ const journal = baseDir ? createReportJournal(join(baseDir, 'agent-recovery-reports')) : null;
53
+ const agentReported = new Map((journal?.load().work ?? []).filter(([id, held]) =>
54
+ held?.body?.turnId === id && typeof held.at === 'number'));
55
+ const persistRecovery = () => journal?.save(new Map(agentReported), new Map()); // turnId -> { body, at }
45
56
  const agentRejectedUntil = new Map(); // turnId -> earliest re-POST of a refused body
46
57
  const AGENT_REPORT_GRACE_MS = 30 * 60_000;
47
58
 
@@ -53,6 +64,7 @@ export function createAgentTurnReports({ REJECT_RETRY_MS }) {
53
64
  const postAgentTurn = async (body) => {
54
65
  const turnId = String(body.turnId);
55
66
  agentReported.set(turnId, { body, at: Date.now() });
67
+ persistRecovery();
56
68
  try {
57
69
  const res = await postFleetJson(AGENT_TURN_DONE_URL, body, { timeoutMs: 30_000 });
58
70
  const j = await res.json().catch(() => null);
@@ -68,9 +80,10 @@ export function createAgentTurnReports({ REJECT_RETRY_MS }) {
68
80
  * class. So a refused body stays HELD — it is the skip-guard — and its
69
81
  * re-POST backs off instead of hammering a body the server just refused.
70
82
  */
71
- if (res.ok) {
83
+ if (res.ok && (body.mergeResolved === undefined || typeof j?.data?.settled === 'boolean')) {
72
84
  agentReported.delete(turnId);
73
85
  agentRejectedUntil.delete(turnId);
86
+ persistRecovery();
74
87
  } else if (isConsideredRefusal(res.status)) {
75
88
  agentRejectedUntil.set(turnId, Date.now() + REJECT_RETRY_MS);
76
89
  }
@@ -160,6 +173,7 @@ export function createAgentTurnReports({ REJECT_RETRY_MS }) {
160
173
  else if (now - held.at > AGENT_REPORT_GRACE_MS) {
161
174
  agentReported.delete(id);
162
175
  agentRejectedUntil.delete(id);
176
+ persistRecovery();
163
177
  }
164
178
  }
165
179
  };
@@ -24,7 +24,7 @@
24
24
  */
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
- import { ORCHESTRATOR_CONTRACT } from './orchestratorPrompt.mjs';
27
+ import { orchestratorContract } from './orchestratorPrompt.mjs';
28
28
 
29
29
  /** What every kickoff of this job is handed: the agent, the card and its
30
30
  * discussion, the job's context handoffs, the card's files, the place in
@@ -35,6 +35,7 @@ const shared = (job, cardFiles) => ({
35
35
  cardThread: job.cardThread,
36
36
  investigationContext: job.investigationContext,
37
37
  researchContext: job.researchContext,
38
+ resultContext: job.resultContext,
38
39
  cardFiles,
39
40
  position: job.position ?? 1,
40
41
  total: job.total ?? 1,
@@ -74,17 +75,19 @@ export const agentFollowupKickoff = (job, followup, { cardFiles, messageFiles, o
74
75
  messageFiles,
75
76
  });
76
77
 
77
- /** The turn's system prompt, in its fixed section order. */
78
- export function agentTurnSystem({ taskKind, runtimeLabel, prerequisitesAccepted, knowledgeDir, artifactsAccepted, toolsNote, artifactInstructions, toolInstructions, repositoryRun, orchestrated }) {
78
+ /** The turn's system prompt, in its fixed section order. `orchestration` is
79
+ * null on a solo run, else what the contract names: `{ posture, runtime,
80
+ * model, effort }` (orchestratorPrompt.mjs). */
81
+ export function agentTurnSystem({ taskKind, taskType, runtimeLabel, prerequisitesAccepted, knowledgeDir, artifactsAccepted, toolsNote, artifactInstructions, toolInstructions, repositoryRun, orchestration = null }) {
79
82
  // The knowledge paragraph, when this box holds a library — the same
80
83
  // composer the tabs use, so an agent and a tab can never be told two
81
84
  // different things about the same directory.
82
85
  // Spoken to the CLI that runs it (0.115.0): a mockup on Codex is
83
86
  // told it is Codex, never Claude.
84
- return withProjectContext(SYSTEM_AGENT_FOR(taskKind, runtimeLabel, { prerequisitesAccepted }), {
87
+ return withProjectContext(SYSTEM_AGENT_FOR(taskKind, runtimeLabel, { prerequisitesAccepted, taskType }), {
85
88
  knowledgeDir,
86
89
  // ARTIFACTS (0.94.0), while the server can show one — an agent's
87
90
  // land on its page, under the facts row.
88
91
  artifacts: artifactsAccepted,
89
- }) + (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>"])}` : '') + (orchestrated ? `\n\n${ORCHESTRATOR_CONTRACT}` : '');
92
+ }) + (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 })}` : '');
90
93
  }
@@ -1,3 +1,4 @@
1
+ import { reportAgentOutcome } from './workActivity.mjs';
1
2
  /**
2
3
  * THE AGENT-TURN LANE: which offered turns run on this machine, and that each
3
4
  * one is settled exactly once.
@@ -69,7 +70,7 @@ export function createWorkAgentTurns({
69
70
  pruneHeldReports,
70
71
  agentReported,
71
72
  agentRejectedUntil,
72
- } = createAgentTurnReports({ REJECT_RETRY_MS });
73
+ } = createAgentTurnReports({ REJECT_RETRY_MS, baseDir });
73
74
 
74
75
  // ── AGENT TURNS: one task per prompt ───────────────────────────────────────
75
76
  //
@@ -205,7 +206,8 @@ export function createWorkAgentTurns({
205
206
  };
206
207
  const settleOnce = (body) => {
207
208
  settled = true;
208
- return postAgentTurn(withLiveReceipts(body));
209
+ reportAgentOutcome(job, body);
210
+ return postAgentTurn(withLiveReceipts(job.kind === 'merge_resolve' ? { ...body, mergeResolved: body.mergeResolved ?? false } : body));
209
211
  };
210
212
 
211
213
  // A WRITER on the agent's own place — see the lane header. Only `a-<id>`
@@ -221,11 +223,11 @@ export function createWorkAgentTurns({
221
223
  if (settled) return;
222
224
  const commits = turnCommits({ repositoryReceipts: measured.repositoryReceipts, wt: measured.wt, before: measured.before, baseRef });
223
225
  if (run.interrupted) {
224
- await postAgentTurn(withLiveReceipts(interruptedTurnSettlement({ turnId, ran: measured.ranCli === true, commits, usage: measured.usage, model: measured.model, branch: measured.branch, wt: measured.wt })));
226
+ await settleOnce(interruptedTurnSettlement({ turnId, ran: measured.ranCli === true, commits, usage: measured.usage, model: measured.model, branch: measured.branch, wt: measured.wt }));
225
227
  return;
226
228
  }
227
- await postAgentTurn(
228
- withLiveReceipts(failedTurnSettlement({
229
+ await settleOnce(
230
+ failedTurnSettlement({
229
231
  turnId,
230
232
  error,
231
233
  commits,
@@ -233,7 +235,7 @@ export function createWorkAgentTurns({
233
235
  model: measured.model,
234
236
  branch: measured.branch,
235
237
  wt: measured.wt,
236
- }))
238
+ })
237
239
  );
238
240
  }
239
241
  }, () => {
@@ -1,3 +1,4 @@
1
+ import { reportWorkOutcome } from './workActivity.mjs';
1
2
  /** One leased incoming ticket, drafted by this machine's read-only CLI. */
2
3
  import { FLEET_URL, DAEMON_INSTANCE } from './config.mjs';
3
4
  import { fleetEndpoint } from './fleetWire.mjs';
@@ -39,12 +40,17 @@ export function createWorkIntake({
39
40
  const claim = (id) =>
40
41
  claimFleetLease(claiming, { id }, { won: (r) => r.status === 200 && r.json?.ok === true });
41
42
 
42
- const report = async (body) => {
43
+ const sendReport = async (body) => {
43
44
  await postBestEffort(done, { ...body, instance: DAEMON_INSTANCE });
44
45
  };
45
46
 
46
47
  const run = async (job, releaseSlot) => {
47
48
  const id = String(job.id);
49
+ const work = { kind: 'intake', id, name: job.title || 'Incoming ticket', turnId: id };
50
+ const report = body => {
51
+ reportWorkOutcome(work, { outcome: body.outcome === INTAKE_OUTCOMES.drafted ? 'drafted' : 'nothing', answer: body.error });
52
+ return sendReport(body);
53
+ };
48
54
  // No stand-down check here, as in the plan lane: a standing-down loop stops
49
55
  // processing the roster, and the one spawn gate is runTurn's own
50
56
  // (standDownGate.mjs; its pin keeps the askers few).
@@ -80,7 +86,7 @@ export function createWorkIntake({
80
86
  cwd: repoRoot,
81
87
  runtime,
82
88
  answerFromResult: true,
83
- label: c.cyan('[intake]'),
89
+ work,
84
90
  // There is no MCP principal for a ticket, and no model or effort pin:
85
91
  // this one-shot reader uses the machine's consult defaults.
86
92
  onUsage: (u) => { usage = addUsage(usage, { ...u, runtime }); },
@@ -1,3 +1,5 @@
1
+ import { reportWorkOutcome } from './workActivity.mjs';
2
+ import { createTurnOutput } from './turnOutput.mjs';
1
3
  /** The daemon holds every selected place while one personal CLI integrates it. */
2
4
  import { trackedRepositorySetup } from './repositorySetup.mjs';
3
5
  import { git } from './git.mjs';
@@ -25,6 +27,7 @@ export function createWorkMergeBatch({ repositoryPlaces, repoRoot, baseDir, inPl
25
27
  };
26
28
  return async (job) => {
27
29
  const spec = job.batch.spec;
30
+ const work = { kind: 'batch merge', id: spec.id, name: 'Selected merge', turnId: spec.id };
28
31
  const hold = admit?.('churn');
29
32
  if (hold) return;
30
33
  const release = admit?.reserve?.() ?? (() => {});
@@ -33,6 +36,7 @@ export function createWorkMergeBatch({ repositoryPlaces, repoRoot, baseDir, inPl
33
36
  const claim = await request('agent-merge-batch-claim', { batchId: spec.id });
34
37
  if (!claim?.claimed) return;
35
38
  claimed = true;
39
+ createTurnOutput({ work }).event('Merge started', 'start');
36
40
  if (spec.members.some((m) => !isSafePathSegment(m.placeId))) throw new Error('A selected agent has no valid worktree identity.');
37
41
  // Renewal covers a long coordinator or validation command, not just Git beats.
38
42
  timer = setInterval(() => { void request('agent-merge-batch-progress', { batchId: spec.id, progress: {} }).catch(() => {}); }, 60_000);
@@ -68,7 +72,7 @@ export function createWorkMergeBatch({ repositoryPlaces, repoRoot, baseDir, inPl
68
72
  turnArgs: { prompt: mergeBatchPrompt(context), system: MERGE_BATCH_SYSTEM,
69
73
  runtime, profile: 'build', knowledgeDir: knowledgeDirFor(repoRoot), model: leader.model || undefined, effort: leader.effort || undefined,
70
74
  cwd: context.repositories[0].integrationWorktree, writableDirs: context.repositories.filter((r) => !r.alreadyLanded).map((r) => r.integrationWorktree),
71
- answerFromResult: true, label: '[batch merge]' } });
75
+ answerFromResult: true, work } });
72
76
  if (result.wedged) throw new Error('The coordinating CLI ran out of time. Retry the batch to continue with its saved integration work.');
73
77
  return candidateObjects(String(result.out ?? '')).find((r) => r.status === 'delivered' || r.status === 'blocked');
74
78
  } finally { if (child) groupKillChildren?.delete(child); }
@@ -84,9 +88,11 @@ export function createWorkMergeBatch({ repositoryPlaces, repoRoot, baseDir, inPl
84
88
  reporting = true;
85
89
  const done = await request('agent-merge-batch-done', { batchId: spec.id, ok: true });
86
90
  if (!done?.settled) throw new Error('The combined merge result has not been acknowledged. Retry will remeasure base.');
91
+ reportWorkOutcome(work, { outcome: 'merged' });
87
92
  onRepoChanged(); landed.observe();
88
93
  });
89
94
  } catch (e) {
95
+ if (claimed) reportWorkOutcome(work, { outcome: 'nothing', answer: relayedText([e?.stdout, e?.stderr, e?.message].filter(Boolean).join('\n')) });
90
96
  // After a successful push/report attempt, leave the lease for replay: a transport
91
97
  // failure says nothing about whether the server committed the final receipt.
92
98
  if (claimed && !reporting) {
@@ -0,0 +1,50 @@
1
+ /** The person's own CLI diagnoses planning, under the ordinary consult fence. */
2
+ import { candidateObjects } from './modelJson.mjs';
3
+ import { scrub } from './uplinkScrub.mjs';
4
+ import { fleetRequest } from './fleetPost.mjs';
5
+ import { DAEMON_INSTANCE } from './config.mjs';
6
+
7
+ export const PLAN_RECOVERY_SYSTEM = `You are recovering the person's retained Plan press.
8
+ Read the original selection, failure, retained evidence, answers and repository.
9
+ Establish the cause before claiming a repair. Diagnose/debug the planning failure
10
+ with the available read-only tools. Repair the planning approach, context or output
11
+ instructions. Do not change repository files, start agents or approve work.
12
+ Retry count is never a reason to ask the person to click Plan again.
13
+ Only ask for an answer, credential action or changed-scope decision that reading
14
+ and diagnosis cannot supply. Ask one specific question and explain what it settles.
15
+ Return one JSON object: {"status":"repaired","summary":"cause and corrected approach"}
16
+ or {"status":"question","question":"specific question","context":"findings"}.
17
+ A repaired result is followed automatically by a fresh planning turn on the same
18
+ selection and chosen CLI/model/effort. The result is a proposal for human review. When recovery.start is present, the person
19
+ already approved only that Start group: diagnose its admission or pickup failure,
20
+ preserve its exact ordered selection, CLI and creation/attachment choice, and
21
+ repair only its planning context. A valid same-scope repair continues that approval
22
+ automatically; any genuine scope decision needs a specific question first.`;
23
+
24
+ export function parsePlanDiagnosis(out) {
25
+ for (const v of candidateObjects(String(out ?? ''))) {
26
+ if (v.status === 'repaired' && typeof v.summary === 'string' && v.summary.trim())
27
+ return { status: 'repaired', summary: scrub(v.summary) };
28
+ if (v.status === 'question' && typeof v.question === 'string' && v.question.trim())
29
+ return { status: 'question', question: scrub(v.question), ...(typeof v.context === 'string' ? { context: scrub(v.context) } : {}) };
30
+ }
31
+ return null;
32
+ }
33
+
34
+ /** Unknown transport state never means Stop. Only an answered holder read does. */
35
+ export async function watchPlanAttempt(url, job) {
36
+ const controller = new AbortController();
37
+ let busy = false;
38
+ const check = async () => {
39
+ if (busy) return;
40
+ busy = true;
41
+ try {
42
+ const r = await fleetRequest(url, { body: { id: job.id, attempt: job.attempt, instance: DAEMON_INSTANCE }, timeoutMs: 15_000 });
43
+ if (r.ok && r.json?.data?.active === false) controller.abort();
44
+ } finally { busy = false; }
45
+ };
46
+ await check();
47
+ const timer = setInterval(check, 5_000);
48
+ timer.unref?.();
49
+ return { signal: controller.signal, check, stop: () => clearInterval(timer) };
50
+ }
@@ -1,3 +1,5 @@
1
+ import { fetchResultInputs } from './resultInputs.mjs';
2
+ import { sessionWork, reportWorkOutcome } from './workActivity.mjs';
1
3
  /**
2
4
  * A WORKBENCH TAB'S TURN — offered, admitted, placed, adopted, pinned to its
3
5
  * CLI, credentialed, resumed, run, persisted and settled.
@@ -149,6 +151,8 @@ export function createWorkSessionTurns({
149
151
  return settledHere.has(turnId);
150
152
  };
151
153
  const settleTurn = (turnId, payload) => {
154
+ const run = runs.get(turnId);
155
+ if (run?.job) reportWorkOutcome(sessionWork(run.job), { ...payload, outcome: payload.ok ? 'replied' : 'nothing' });
152
156
  const at = now();
153
157
  settledHere.delete(turnId); // re-inserted at the end: the map stays in time order
154
158
  settledHere.set(turnId, at);
@@ -223,7 +227,7 @@ export function createWorkSessionTurns({
223
227
  const releaseSlot = admit.reserve();
224
228
  lastDeferSaid.delete(job.sessionId);
225
229
  workAnswering.add(job.id);
226
- const run = { interrupted: false, spawned: [], sessionId: job.sessionId, live: null };
230
+ const run = { interrupted: false, spawned: [], sessionId: job.sessionId, live: null, job };
227
231
  runs.set(job.id, run);
228
232
  /** The execution generation the server handed, echoed on every settle
229
233
  * so the server can tell this execution's answer from an older one's. */
@@ -690,6 +694,7 @@ export function createWorkSessionTurns({
690
694
  // Files first, then the message that references them: the agent
691
695
  // must be able to open what it is being told about. Only the ones
692
696
  // that actually landed are named.
697
+ await fetchResultInputs(dir.wt, job.resultInputs);
693
698
  const filesNote = tabFilesNote(await fetchTabFiles(dir.wt, job.attachments));
694
699
  const message = [job.body, filesNote, adoptNote, carryNote]
695
700
  .filter(Boolean)
@@ -819,7 +824,7 @@ export function createWorkSessionTurns({
819
824
  // chat does not (workTurnShape.mjs).
820
825
  display: shape.display,
821
826
  runtime: rt.id,
822
- label: c.cyan('[tab]'),
827
+ work: sessionWork(job),
823
828
  // Only codex announces one (thread.started); held here so the id
824
829
  // this turn actually SPOKE under is what gets persisted after it
825
830
  // ends. Last write wins on purpose: a failed resume that fell
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flowviant",
3
- "version": "0.125.7",
3
+ "version": "0.125.12",
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": {