flowviant 0.125.4 → 0.125.6

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 (86) hide show
  1. package/bin/lib/agentInternalWait.mjs +1 -5
  2. package/bin/lib/agentPlan.mjs +13 -163
  3. package/bin/lib/agentRepositories.mjs +3 -1
  4. package/bin/lib/claudeInput.mjs +28 -9
  5. package/bin/lib/claudeMachineMcp.mjs +85 -0
  6. package/bin/lib/claudePersonal.mjs +6 -6
  7. package/bin/lib/claudePosture.mjs +6 -15
  8. package/bin/lib/claudeSettingsArg.mjs +67 -0
  9. package/bin/lib/claudeSubagentProgress.mjs +12 -28
  10. package/bin/lib/claudeSubagents.mjs +4 -2
  11. package/bin/lib/codexFencedMount.mjs +275 -0
  12. package/bin/lib/codexPersonal.mjs +1 -265
  13. package/bin/lib/codexProjectRead.mjs +5 -4
  14. package/bin/lib/codexResearch.mjs +9 -3
  15. package/bin/lib/codexSubagentEvents.mjs +11 -7
  16. package/bin/lib/codexSubagents.mjs +55 -52
  17. package/bin/lib/codexToml.mjs +328 -0
  18. package/bin/lib/conversationIds.mjs +10 -0
  19. package/bin/lib/fleet.mjs +2 -2
  20. package/bin/lib/git.mjs +32 -0
  21. package/bin/lib/heldTool.mjs +49 -0
  22. package/bin/lib/hooks/mcpAllow.mjs +11 -5
  23. package/bin/lib/hooks/orchestrator.mjs +20 -10
  24. package/bin/lib/intakeDraft.mjs +1 -1
  25. package/bin/lib/jsonlTail.mjs +52 -0
  26. package/bin/lib/mcpInventory.mjs +9 -5
  27. package/bin/lib/mcpReasons.mjs +20 -0
  28. package/bin/lib/mcpReportBounds.mjs +10 -0
  29. package/bin/lib/mcpStdio.mjs +35 -0
  30. package/bin/lib/mergeBatch.mjs +6 -24
  31. package/bin/lib/mergeOrder.mjs +18 -0
  32. package/bin/lib/modelJson.mjs +173 -0
  33. package/bin/lib/nativeAgentTools.mjs +72 -0
  34. package/bin/lib/orchestrator.mjs +212 -133
  35. package/bin/lib/orchestratorClient.mjs +17 -21
  36. package/bin/lib/orchestratorHooks.mjs +21 -4
  37. package/bin/lib/orchestratorNative.mjs +58 -0
  38. package/bin/lib/orchestratorPrompt.mjs +7 -4
  39. package/bin/lib/orchestratorProtocol.mjs +25 -3
  40. package/bin/lib/postureTools.mjs +58 -13
  41. package/bin/lib/prWorkflow.mjs +13 -2
  42. package/bin/lib/projectRepositories.mjs +2 -2
  43. package/bin/lib/projectToolRuntimes.mjs +92 -245
  44. package/bin/lib/projectTools.mjs +59 -19
  45. package/bin/lib/repositoryDelivery.mjs +6 -4
  46. package/bin/lib/repositoryMerge.mjs +8 -12
  47. package/bin/lib/repositoryPlaces.mjs +2 -2
  48. package/bin/lib/repositoryPrompts.mjs +42 -0
  49. package/bin/lib/repositoryTool.mjs +9 -32
  50. package/bin/lib/researchUse.mjs +39 -0
  51. package/bin/lib/resumeMarker.mjs +43 -0
  52. package/bin/lib/runTurn.mjs +38 -52
  53. package/bin/lib/runtimeAntigravity.mjs +1 -1
  54. package/bin/lib/runtimeCapabilities.mjs +6 -16
  55. package/bin/lib/runtimeClaude.mjs +62 -19
  56. package/bin/lib/runtimeCodex.mjs +9 -6
  57. package/bin/lib/runtimeEvents.mjs +13 -2
  58. package/bin/lib/runtimeOfferings.mjs +9 -4
  59. package/bin/lib/runtimes.mjs +3 -1
  60. package/bin/lib/subagentBounds.mjs +20 -0
  61. package/bin/lib/trace.mjs +30 -5
  62. package/bin/lib/turnDeclaration.mjs +1 -1
  63. package/bin/lib/turnDiagnostics.mjs +9 -12
  64. package/bin/lib/turnReceipts.mjs +64 -0
  65. package/bin/lib/turnSubagents.mjs +91 -0
  66. package/bin/lib/turnUsage.mjs +9 -6
  67. package/bin/lib/versionOrder.mjs +11 -1
  68. package/bin/lib/work.mjs +6 -6
  69. package/bin/lib/workAgentMerges.mjs +5 -7
  70. package/bin/lib/workAgentPrecheck.mjs +5 -5
  71. package/bin/lib/workAgentRecovery.mjs +2 -1
  72. package/bin/lib/workAgentResume.mjs +121 -0
  73. package/bin/lib/workAgentTools.mjs +111 -0
  74. package/bin/lib/workAgentTurnExecution.mjs +62 -246
  75. package/bin/lib/workAgentTurnOutcome.mjs +30 -3
  76. package/bin/lib/workAgentTurnShape.mjs +90 -0
  77. package/bin/lib/workAgentTurns.mjs +24 -10
  78. package/bin/lib/workArtifactReconcile.mjs +3 -2
  79. package/bin/lib/workMergeBatch.mjs +7 -5
  80. package/bin/lib/workPlaceReports.mjs +3 -3
  81. package/bin/lib/workPlaces.mjs +14 -0
  82. package/bin/lib/workSessionRuntime.mjs +2 -5
  83. package/bin/lib/workSessionTurns.mjs +12 -63
  84. package/bin/lib/workShip.mjs +8 -18
  85. package/bin/lib/workTurnShape.mjs +6 -4
  86. package/package.json +1 -1
@@ -21,10 +21,12 @@
21
21
  */
22
22
  import { existsSync } from 'node:fs';
23
23
  import { isSafePathSegment } from './git.mjs';
24
+ import { AGENT_PLACE_PREFIX } from './workPlaces.mjs';
24
25
  import { c, note } from './ui.mjs';
25
26
  import { createAgentTurnReports } from './workAgentTurnReports.mjs';
26
27
  import { createAgentTurnExecution } from './workAgentTurnExecution.mjs';
27
- import { failedTurnSettlement } from './workAgentTurnOutcome.mjs';
28
+ import { failedTurnSettlement, interruptedTurnSettlement } from './workAgentTurnOutcome.mjs';
29
+ import { turnCommits } from './turnReceipts.mjs';
28
30
  import { killGroup } from './processGroup.mjs';
29
31
  import { createDeliveryReporter } from './workDelivery.mjs';
30
32
  import { createAgentBriefRunner } from './workAgentBriefRunner.mjs';
@@ -54,6 +56,8 @@ export function createWorkAgentTurns({
54
56
  /** The turn's files, fetched into its worktree (workAttachments.mjs, 0.112.0). */
55
57
  fetchAgentFiles,
56
58
  makeBrief = createAgentBriefRunner({ admit, workChildren }),
59
+ /** The run's live-message relay (workLiveMessages.mjs); a test's stand-in. */
60
+ makeLiveMessages,
57
61
  }) {
58
62
  const reportDelivery = createDeliveryReporter();
59
63
  const {
@@ -111,7 +115,7 @@ export function createWorkAgentTurns({
111
115
  * read before the stop's settle landed. Taken once, for a little while. */
112
116
  const stoppedFor = new Map(); // agentId -> when
113
117
 
114
- const { runAgentTurnInPlace, commitsBetween } = createAgentTurnExecution({
118
+ const { runAgentTurnInPlace } = createAgentTurnExecution({
115
119
  admit,
116
120
  agentRepositories,
117
121
  makeBrief,
@@ -133,6 +137,7 @@ export function createWorkAgentTurns({
133
137
  postAgentActivity,
134
138
  postAgentParked,
135
139
  fetchAgentFiles,
140
+ makeLiveMessages,
136
141
  });
137
142
 
138
143
  /** `releaseSlot` hands back the admission reservation the caller took on this
@@ -183,33 +188,42 @@ export function createWorkAgentTurns({
183
188
  * branch, the spend, the branch and worktree. Through the same held-body
184
189
  * wire, so a failed POST is re-sent on the next offer and the CLI is not
185
190
  * run again.
191
+ *
192
+ * The catch carries what the run's own settles carry, the live receipts
193
+ * included (2026-10-18): a note the CLI already took is folded into this
194
+ * run only when a settle names it, and the relay's own flush is bounded,
195
+ * so a catch that posted without them left the note pending — offered
196
+ * later as its own turn, a second paid run.
186
197
  */
187
198
  const measured = { wt: null, before: null, branch: null, usage: null, model: null };
188
199
  let settled = false;
200
+ const withLiveReceipts = (body) => {
201
+ const liveMessages = agentRuns.get(turnId)?.live?.receipts();
202
+ return { ...body, ...(liveMessages?.ids.length ? { liveMessages } : {}) };
203
+ };
189
204
  const settleOnce = (body) => {
190
205
  settled = true;
191
- const liveMessages = agentRuns.get(turnId)?.live?.receipts();
192
- return postAgentTurn({ ...body, ...(liveMessages?.ids.length ? { liveMessages } : {}) });
206
+ return postAgentTurn(withLiveReceipts(body));
193
207
  };
194
208
 
195
209
  // A WRITER on the agent's own place — see the lane header. Only `a-<id>`
196
210
  // places: those are agents' by construction, and anything else here would
197
211
  // be a tab's directory, where a turn is a reader by the product's own law.
198
212
  const run = agentRuns.get(turnId) ?? { place, interrupted: false };
199
- await inPlace(place, place.startsWith('a-'), async () => {
213
+ await inPlace(place, place.startsWith(AGENT_PLACE_PREFIX), async () => {
200
214
  run.waitingReason = null;
201
215
  try {
202
216
  await runAgentTurnInPlace(job, { turnId, agentId, place, releaseSlot, postAgentTurn: settleOnce, measured, run });
203
217
  if (run.interrupted && measured.ranCli) stoppedFor.set(agentId, Date.now());
204
218
  } catch (error) {
205
219
  if (settled) return;
206
- const commits = measured.repositoryReceipts ? [...new Set(measured.repositoryReceipts().map((r) => r.sha))].slice(0, 50) : measured.wt ? commitsBetween(measured.wt, measured.before) : [];
220
+ const commits = turnCommits({ repositoryReceipts: measured.repositoryReceipts, wt: measured.wt, before: measured.before, baseRef });
207
221
  if (run.interrupted) {
208
- await postAgentTurn({ turnId, outcome: 'nothing', interrupted: true, ran: measured.ranCli === true, commits, ...(measured.usage ? { usage: measured.usage } : {}) });
222
+ await postAgentTurn(withLiveReceipts(interruptedTurnSettlement({ turnId, ran: measured.ranCli === true, commits, usage: measured.usage, model: measured.model, branch: measured.branch, wt: measured.wt })));
209
223
  return;
210
224
  }
211
225
  await postAgentTurn(
212
- failedTurnSettlement({
226
+ withLiveReceipts(failedTurnSettlement({
213
227
  turnId,
214
228
  error,
215
229
  commits,
@@ -217,7 +231,7 @@ export function createWorkAgentTurns({
217
231
  model: measured.model,
218
232
  branch: measured.branch,
219
233
  wt: measured.wt,
220
- })
234
+ }))
221
235
  );
222
236
  }
223
237
  }, () => {
@@ -299,7 +313,7 @@ export function createWorkAgentTurns({
299
313
  const place = String(job.placeId || '');
300
314
  const wt = typeof held.body.worktree === 'string' ? held.body.worktree : null;
301
315
  if (reply?.review === true && isSafePathSegment(place) && wt && existsSync(wt)) {
302
- await inPlace(place, place.startsWith('a-'), () =>
316
+ await inPlace(place, place.startsWith(AGENT_PLACE_PREFIX), () =>
303
317
  runReviewEntry(String(job.agentId || ''), wt, job.agentName)
304
318
  );
305
319
  }
@@ -15,6 +15,7 @@
15
15
  * number of times; the stored files stay listed, which is the truth.
16
16
  */
17
17
  import { existsSync } from 'node:fs';
18
+ import { agentIdFromPlace } from './workPlaces.mjs';
18
19
 
19
20
  /** Attempts per agent per process, and the wait between two of them. */
20
21
  const MAX_TRIES = 4;
@@ -37,7 +38,7 @@ export function createWorkArtifactReconcile({
37
38
  const reconcileOne = async (place) => {
38
39
  const wt = placeDir(place);
39
40
  if (!wt || !existsSync(wt)) return;
40
- const agentId = place.slice(2);
41
+ const agentId = agentIdFromPlace(place);
41
42
  try {
42
43
  await inPlace(place, true, () => artifacts.reconcile({ placeDir: wt, before: beforeArtifacts(wt), agentId }));
43
44
  done.add(place);
@@ -58,7 +59,7 @@ export function createWorkArtifactReconcile({
58
59
  for (const id of done) if (!live.has(id)) done.delete(id);
59
60
  for (const id of tries.keys()) if (!live.has(id)) tries.delete(id);
60
61
  const due = activeIds.filter((place) => {
61
- if (typeof place !== 'string' || !place.startsWith('a-') || place.length <= 2) return false;
62
+ if (agentIdFromPlace(place) === null) return false;
62
63
  if (done.has(place) || placeLocks.has(place)) return false; // a turn or a merge is there
63
64
  const t = tries.get(place);
64
65
  return !t || (t.n < MAX_TRIES && now() - t.at >= RETRY_MS);
@@ -1,17 +1,19 @@
1
+ /** The daemon holds every selected place while one personal CLI integrates it. */
1
2
  import { trackedRepositorySetup } from './repositorySetup.mjs';
2
3
  import { git } from './git.mjs';
3
4
  import { knowledgeDirFor } from './knowledgeFiles.mjs';
4
- /** The daemon holds every selected place while one personal CLI integrates it. */
5
- import { integrateMergeBatch, mergeBatchPrompt } from './mergeBatch.mjs';
5
+ import { integrateMergeBatch } from './mergeBatch.mjs';
6
+ import { mergeBatchPrompt, MERGE_BATCH_SYSTEM } from './repositoryPrompts.mjs';
6
7
  import { runTurn } from './runTurn.mjs';
7
8
  import { runCappedTurn } from './workCappedTurn.mjs';
8
- import { candidateObjects } from './agentPlan.mjs';
9
+ import { candidateObjects } from './modelJson.mjs';
9
10
  import { pickRuntimeFor } from './runtimeDetection.mjs';
10
11
  import { fleetRequest } from './fleetPost.mjs';
11
12
  import { fleetEndpoint } from './fleetWire.mjs';
12
13
  import { FLEET_URL, DAEMON_INSTANCE } from './config.mjs';
13
14
  import { worktreePath } from './workPlaces.mjs';
14
15
  import { scrub } from './uplinkScrub.mjs';
16
+ import { relayedText } from './prWorkflow.mjs';
15
17
  import { isSafePathSegment, stripUrlUserinfo } from './git.mjs';
16
18
  import { killGroup } from './processGroup.mjs';
17
19
 
@@ -63,7 +65,7 @@ export function createWorkMergeBatch({ repositoryPlaces, repoRoot, baseDir, inPl
63
65
  const result = await runCappedTurn({ runTurn: runCli, workChildren, releaseSlot: release,
64
66
  transcriptCwd: context.repositories[0].integrationWorktree, capMs: 20 * 60_000, killChild: killGroup,
65
67
  onSpawn: (ch) => { child = ch; groupKillChildren?.add(ch); },
66
- turnArgs: { prompt: mergeBatchPrompt(context), system: 'You are the person’s local merge coordinator. Integrate the whole authorized selection and preserve every card’s intent.',
68
+ turnArgs: { prompt: mergeBatchPrompt(context), system: MERGE_BATCH_SYSTEM,
67
69
  runtime, profile: 'build', knowledgeDir: knowledgeDirFor(repoRoot), model: leader.model || undefined, effort: leader.effort || undefined,
68
70
  cwd: context.repositories[0].integrationWorktree, writableDirs: context.repositories.filter((r) => !r.alreadyLanded).map((r) => r.integrationWorktree),
69
71
  answerFromResult: true, label: '[batch merge]' } });
@@ -88,7 +90,7 @@ export function createWorkMergeBatch({ repositoryPlaces, repoRoot, baseDir, inPl
88
90
  // After a successful push/report attempt, leave the lease for replay: a transport
89
91
  // failure says nothing about whether the server committed the final receipt.
90
92
  if (claimed && !reporting) {
91
- const detail = scrub(stripUrlUserinfo([e?.stdout, e?.stderr, e?.message].filter(Boolean).join('\n'))).slice(0, 2000);
93
+ const detail = relayedText([e?.stdout, e?.stderr, e?.message].filter(Boolean).join('\n')).slice(0, 2000);
92
94
  await request('agent-merge-batch-done', { batchId: spec.id, ok: false, detail }).catch(() => {});
93
95
  }
94
96
  } finally {
@@ -20,7 +20,7 @@ import { RESUME_MARKERS } from './conversationIds.mjs';
20
20
  import { worktreeDiff } from './worktreeDiff.mjs';
21
21
  import { myPubB64 } from './boxIdentity.mjs';
22
22
  import { fleetEndpoint } from './fleetWire.mjs';
23
- import { isValidPlace } from './workPlaces.mjs';
23
+ import { isValidPlace, AGENT_PLACE_PREFIX, agentIdFromPlace } from './workPlaces.mjs';
24
24
 
25
25
  export function createWorkPlaceReports({
26
26
  repositoryPlaces,
@@ -176,7 +176,7 @@ export function createWorkPlaceReports({
176
176
  * TABS GET NOTHING. A tab's place is shared by design and its work is a
177
177
  * human's own directory; attributing one would be a fact with no reader.
178
178
  */
179
- const pub = sessionId.startsWith('a-') && sessionId.length > 2 ? myPubB64() : null;
179
+ const pub = agentIdFromPlace(sessionId) !== null ? myPubB64() : null;
180
180
  /**
181
181
  * …AND WHAT THIS MACHINE PUSHED OF IT (0.86.0).
182
182
  *
@@ -193,7 +193,7 @@ export function createWorkPlaceReports({
193
193
  * Mutually exclusive without a rule of its own, because the state it reads
194
194
  * holds a sha or an error and never both.
195
195
  */
196
- const pushed = sessionId.startsWith('a-') ? agentPublished.get(sessionId) : null;
196
+ const pushed = sessionId.startsWith(AGENT_PLACE_PREFIX) ? agentPublished.get(sessionId) : null;
197
197
  return {
198
198
  sessionId,
199
199
  ...d,
@@ -29,6 +29,20 @@ export const REPO_PLACE = 'repo';
29
29
  * audit F163). */
30
30
  export const PERSON_PLACE_PREFIX = 'u-';
31
31
 
32
+ /** An AGENT's place is `a-<agentId>` — the shared AGENT_PLACE_PREFIX /
33
+ * agentPlaceId; a wire value held by the release parity gate (2026-10-18,
34
+ * the SOLID pass, as F163 holds the other two). Its writer lock, its envpub
35
+ * and the artifact reconcile all key on it. */
36
+ export const AGENT_PLACE_PREFIX = 'a-';
37
+
38
+ /** The agent a place names, or null for every other place shape — the shared
39
+ * `agentIdFromPlace`, mirrored: the prefix alone names nothing. */
40
+ export function agentIdFromPlace(place) {
41
+ return typeof place === 'string' && place.startsWith(AGENT_PLACE_PREFIX) && place.length > AGENT_PLACE_PREFIX.length
42
+ ? place.slice(AGENT_PLACE_PREFIX.length)
43
+ : null;
44
+ }
45
+
32
46
  /**
33
47
  * A PLACE THIS MACHINE WILL RESOLVE: the checkout, or a safe path segment.
34
48
  *
@@ -8,17 +8,14 @@
8
8
  * THE ID SHAPES MOVED to a leaf (audit 2026-10-10, F157): conversationIds.mjs
9
9
  * owns every CLI's conversation-id guard and marker name, because the
10
10
  * machine readers below the lanes (runtimeLimits, the session scanners) need
11
- * them too and a lane module is not theirs to import. The two guards the
12
- * agent lane imports from here are re-exported unchanged.
11
+ * them too and a lane module is not theirs to import.
13
12
  */
14
13
  import { existsSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
15
14
  import { join } from 'node:path';
16
15
  import { homedir } from 'node:os';
17
16
  import { canRun, RUNTIMES, runtimeById, runtimeLabel } from './runtimes.mjs';
18
17
  import { detectRuntimes } from './runtimeDetection.mjs';
19
- import { AGY_CONV_RE, CODEX_THREAD_RE } from './conversationIds.mjs';
20
-
21
- export { AGY_CONV_RE, CODEX_THREAD_RE };
18
+ import { AGY_CONV_RE } from './conversationIds.mjs';
22
19
 
23
20
  export function createWorkSessionRuntime({ sessionMetaPath }) {
24
21
  /**
@@ -16,7 +16,7 @@
16
16
  * what the turn IS, plain, plan, capture or build, with its prompt pair
17
17
  * (workTurnShape.mjs, audit 2026-10-10 F159).
18
18
  */
19
- import { existsSync, readFileSync, realpathSync, rmSync, statSync, writeFileSync } from 'node:fs';
19
+ import { existsSync, realpathSync, rmSync, statSync, writeFileSync } from 'node:fs';
20
20
  import { git } from './git.mjs';
21
21
  import { measureRepositoryBaseline } from './repositoryBaseline.mjs';
22
22
  import { REPOSITORY_BASELINE_PARAGRAPH } from './prompts.mjs';
@@ -38,6 +38,7 @@ import { killGroup } from './processGroup.mjs';
38
38
  import { printable } from './printable.mjs';
39
39
  import { brainFor } from './workBrain.mjs';
40
40
  import { AGY_CONV_RE, CLAUDE_SESSION_RE, CODEX_THREAD_RE, RESUME_MARKERS } from './conversationIds.mjs';
41
+ import { pinId, readPinnedId } from './resumeMarker.mjs';
41
42
  import { sessionTurnKickoff, sessionTurnShape, sessionTurnSystem } from './workTurnShape.mjs';
42
43
  import { createToolLog } from './workToolLog.mjs';
43
44
  import { createCommandAudit } from './workCommandAudit.mjs';
@@ -584,48 +585,16 @@ export function createWorkSessionTurns({
584
585
  * directory belongs to this tab alone, which is the one case it was
585
586
  * ever right for.
586
587
  */
587
- let claudeResumeId = null;
588
- if (rt.id === 'claude') {
589
- const convMarker = sessionMetaPath(dir.wt, RESUME_MARKERS.claude, job.sessionId);
590
- if (convMarker && existsSync(convMarker)) {
591
- try {
592
- const v = readFileSync(convMarker, 'utf8').trim();
593
- if (CLAUDE_SESSION_RE.test(v)) claudeResumeId = v;
594
- } catch {
595
- /* unreadable marker — run fresh */
596
- }
597
- }
598
- }
599
- let codexResumeId = null;
600
- if (rt.id === 'codex') {
601
- const threadMarker = sessionMetaPath(dir.wt, RESUME_MARKERS.codex, job.sessionId);
602
- if (threadMarker && existsSync(threadMarker)) {
603
- try {
604
- const v = readFileSync(threadMarker, 'utf8').trim();
605
- if (CODEX_THREAD_RE.test(v)) codexResumeId = v;
606
- } catch {
607
- /* unreadable marker — run fresh */
608
- }
609
- }
610
- }
588
+ // An unreadable or misshapen marker is unknown: run fresh (resumeMarker.mjs).
589
+ const claudeResumeId = rt.id === 'claude' ? readPinnedId(sessionMetaPath(dir.wt, RESUME_MARKERS.claude, job.sessionId), CLAUDE_SESSION_RE) : null;
590
+ const codexResumeId = rt.id === 'codex' ? readPinnedId(sessionMetaPath(dir.wt, RESUME_MARKERS.codex, job.sessionId), CODEX_THREAD_RE) : null;
611
591
  // AGY RESUMES BY CONVERSATION ID, learned once and pinned beside the
612
592
  // runtime marker: an adopted tab knows it from the adopt hint; a new
613
593
  // tab learns it from agy's own cwd registry after its first turn.
614
594
  // The marker beats `--continue` because it is the tab's OWN identity
615
595
  // — the registry maps a cwd to whatever ran there LAST, and a
616
596
  // dispatch sharing the machine could overwrite that between turns.
617
- let agyConvId = null;
618
- if (rt.id === 'antigravity' && !adopting) {
619
- const convMarker = sessionMetaPath(dir.wt, RESUME_MARKERS.antigravity, job.sessionId);
620
- if (convMarker && existsSync(convMarker)) {
621
- try {
622
- const v = readFileSync(convMarker, 'utf8').trim();
623
- if (AGY_CONV_RE.test(v)) agyConvId = v;
624
- } catch {
625
- /* unreadable marker — run fresh */
626
- }
627
- }
628
- }
597
+ const agyConvId = rt.id === 'antigravity' && !adopting ? readPinnedId(sessionMetaPath(dir.wt, RESUME_MARKERS.antigravity, job.sessionId), AGY_CONV_RE) : null;
629
598
  // Resume iff a conversation is known to live in THIS directory. For
630
599
  // Claude that proof is the server's sessionRef — only ever a path
631
600
  // some turn actually SPOKE from (see the settle below), and it must
@@ -716,7 +685,7 @@ export function createWorkSessionTurns({
716
685
  .filter(Boolean)
717
686
  .join('\n\n');
718
687
  const turnArgs = {
719
- ...(rt.id === 'claude' ? { liveMessages: (run.live = makeLiveMessages({
688
+ ...(shape.liveInput ? { liveMessages: (run.live = makeLiveMessages({
720
689
  scope: 'session', turnId: job.id, attempt: job.attempt, cwd: dir.wt, sessionMetaPath,
721
690
  interrupted: () => run.interrupted,
722
691
  format: async (followup) => {
@@ -815,10 +784,7 @@ export function createWorkSessionTurns({
815
784
  // same conversation and recover its native message UUIDs.
816
785
  if (typeof i.sessionId === 'string' && i.sessionId.trim()) {
817
786
  seenClaudeSession = i.sessionId.trim();
818
- if (CLAUDE_SESSION_RE.test(seenClaudeSession)) {
819
- const marker = sessionMetaPath(dir.wt, RESUME_MARKERS.claude, job.sessionId);
820
- if (marker) try { writeFileSync(marker, seenClaudeSession); } catch { /* best effort */ }
821
- }
787
+ if (CLAUDE_SESSION_RE.test(seenClaudeSession)) pinId(sessionMetaPath(dir.wt, RESUME_MARKERS.claude, job.sessionId), CLAUDE_SESSION_RE, seenClaudeSession);
822
788
  }
823
789
  },
824
790
  cwd: dir.wt,
@@ -838,7 +804,7 @@ export function createWorkSessionTurns({
838
804
  },
839
805
  onSpawn: (ch) => {
840
806
  if (!ch) return;
841
- if (rt.id !== 'claude') reportDelivery('session', job, 'taken');
807
+ if (!shape.liveInput) reportDelivery('session', job, 'taken');
842
808
  spawned.push(ch);
843
809
  run.spawned.push(ch);
844
810
  // Asked to stop between the check and the spawn.
@@ -930,16 +896,7 @@ export function createWorkSessionTurns({
930
896
  // before it ever touches disk — it later rides in argv as
931
897
  // `resume <id>` — and best-effort, like the runtime pin: an
932
898
  // unwritable marker just means the tab runs fresh next turn.
933
- if (rt.id === 'codex' && seenThreadId && CODEX_THREAD_RE.test(seenThreadId)) {
934
- const threadMarker = sessionMetaPath(dir.wt, RESUME_MARKERS.codex, job.sessionId);
935
- if (threadMarker) {
936
- try {
937
- writeFileSync(threadMarker, seenThreadId);
938
- } catch {
939
- /* best-effort */
940
- }
941
- }
942
- }
899
+ if (rt.id === 'codex' && seenThreadId && CODEX_THREAD_RE.test(seenThreadId)) pinId(sessionMetaPath(dir.wt, RESUME_MARKERS.codex, job.sessionId), CODEX_THREAD_RE, seenThreadId);
943
900
  /**
944
901
  * A PERSON INTERRUPTED IT. Whatever the CLI printed was cut off, so
945
902
  * it is not an answer: the turn settles `interrupted`, with what it
@@ -984,16 +941,8 @@ export function createWorkSessionTurns({
984
941
  // the registry is never trusted again.
985
942
  if (rt.id === 'antigravity' && answer.length > 0) {
986
943
  const convMarker = sessionMetaPath(dir.wt, RESUME_MARKERS.antigravity, job.sessionId);
987
- if (convMarker && !existsSync(convMarker)) {
988
- const learned = adopting ? job.adopt.id : agyRegistryLookup(dir.wt);
989
- if (learned && AGY_CONV_RE.test(learned)) {
990
- try {
991
- writeFileSync(convMarker, learned);
992
- } catch {
993
- /* best-effort — an unpinned tab resumes via --continue's cwd key */
994
- }
995
- }
996
- }
944
+ // Best-effort — an unpinned tab resumes via --continue's cwd key.
945
+ if (convMarker && !existsSync(convMarker)) pinId(convMarker, AGY_CONV_RE, adopting ? job.adopt.id : agyRegistryLookup(dir.wt));
997
946
  }
998
947
  /**
999
948
  * NO WORDS, SAID AS MEASURED (2026-09-29). Claude's init event means
@@ -1,11 +1,12 @@
1
1
  /** Session branch shipping and settlement. */
2
2
  import { existsSync } from 'node:fs';
3
3
  import { join } from 'node:path';
4
- import { git, baseBranchName, isSafePathSegment, sessionBranch, stripUrlUserinfo } from './git.mjs';
4
+ import { git, baseBranchName, isSafePathSegment, sessionBranch } from './git.mjs';
5
5
  import { mergeOutward as shipMergeOutward } from './shipMerge.mjs';
6
6
  import { gitMergeIn } from './gitMergeIdentity.mjs';
7
7
  import { c, note, ok, warn } from './ui.mjs';
8
8
  import { scrub as envScrub } from './uplinkScrub.mjs';
9
+ import { relayedText } from './prWorkflow.mjs';
9
10
  import { REPO_PLACE } from './workPlaces.mjs';
10
11
  import { placeTurnLockHolder } from './turnLock.mjs';
11
12
  import { printable } from './printable.mjs';
@@ -272,15 +273,10 @@ export function createWorkShipper({
272
273
  'conflicts with main — the tab is closed, so open a new session from this branch to resolve them, then ship again',
273
274
  });
274
275
  } else {
275
- // Userinfo stripped as well as scrubbed (2026-10-10, F153): the
276
- // outward merge pushes to `origin`, and a push names the remote.
277
- const line = envScrub(
278
- stripUrlUserinfo(
279
- String(detail)
280
- .split('\n')
281
- .find((l) => l.trim()) ?? 'git merge failed'
282
- )
283
- );
276
+ // Userinfo stripped as well as scrubbed (2026-10-10, F153), by
277
+ // relayedText: the outward merge pushes to `origin`, and a push
278
+ // names the remote.
279
+ const line = relayedText(String(detail).split('\n').find((l) => l.trim()) ?? 'git merge failed');
284
280
  await done({ ok: false, error: `the merge failed: ${line.slice(0, 300)}` });
285
281
  }
286
282
  return;
@@ -331,13 +327,7 @@ export function createWorkShipper({
331
327
  } else {
332
328
  // An honest error beats a fabricated conflict — the human can
333
329
  // only fix what they are told about.
334
- const line = envScrub(
335
- stripUrlUserinfo(
336
- String(detail)
337
- .split('\n')
338
- .find((l) => l.trim()) ?? 'git merge failed'
339
- )
340
- );
330
+ const line = relayedText(String(detail).split('\n').find((l) => l.trim()) ?? 'git merge failed');
341
331
  await done({ ok: false, error: `the merge failed: ${line.slice(0, 300)}` });
342
332
  }
343
333
  return;
@@ -368,7 +358,7 @@ export function createWorkShipper({
368
358
  warn(`ship failed: ${e?.message ?? e}`);
369
359
  await done({
370
360
  ok: false,
371
- error: envScrub(stripUrlUserinfo(e?.message ?? 'the merge failed')).slice(0, 500),
361
+ error: relayedText(e?.message ?? 'the merge failed').slice(0, 500),
372
362
  });
373
363
  } finally {
374
364
  if (!settled && !deferred) {
@@ -15,8 +15,8 @@ import { discoveryGuide } from './discoveryContract.mjs';
15
15
  * other answer off it; the prompt pair is composed here too.
16
16
  *
17
17
  * The runtime facts come off the row (runtime{Claude,Codex,Antigravity}.mjs):
18
- * `tab` ('plain' runs no MCP), `planTurns` (the CLI can spell plan mode) and
19
- * `adopt` ('fork' | 'move' | null). The LIFECYCLE stays the lane's — its
18
+ * `tab` ('plain' runs no MCP), `planTurns` (the CLI can spell plan mode),
19
+ * `adopt` ('fork' | 'move' | null) and `liveInput` (follow-ups on stdin). The LIFECYCLE stays the lane's — its
20
20
  * refusal order is the contract — and only this composition moved.
21
21
  */
22
22
  import { runtimeById } from './runtimes.mjs';
@@ -55,7 +55,7 @@ export const PLAN_TURN_SENTENCE =
55
55
  * verdict that this first turn adopts a terminal session.
56
56
  *
57
57
  * Returns `{ refusal?, plain, plan, capture, kickoff, systemBase,
58
- * planSentence, artifacts, profile, mountMcp, display }`:
58
+ * planSentence, artifacts, profile, mountMcp, display, liveInput }`:
59
59
  * refusal — the sentence the turn settles in, unrun, when it has one;
60
60
  * kickoff /
61
61
  * systemBase — which contract the prompt pair runs: 'plain' | 'capture' | 'work';
@@ -65,7 +65,8 @@ export const PLAN_TURN_SENTENCE =
65
65
  * one, as the lane reads it at spawn);
66
66
  * profile — the permission profile: 'build' | 'plan' | 'plan-mode';
67
67
  * mountMcp — the session's MCP config is mounted;
68
- * display — the CLI keeps its environment's display.
68
+ * display — the CLI keeps its environment's display;
69
+ * liveInput — follow-ups reach the running CLI on stdin.
69
70
  */
70
71
  export function sessionTurnShape({ runtime, adopting = false, job = null }) {
71
72
  const row = runtimeById(runtime);
@@ -137,6 +138,7 @@ export function sessionTurnShape({ runtime, adopting = false, job = null }) {
137
138
  // only reads and stages cards, and the person talking to it may be on a
138
139
  // phone (noWindowEnv.mjs).
139
140
  display: !isCapture,
141
+ liveInput: row?.liveInput === true,
140
142
  };
141
143
  }
142
144
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flowviant",
3
- "version": "0.125.4",
3
+ "version": "0.125.6",
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": {