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
@@ -0,0 +1,58 @@
1
+ /**
2
+ * WHAT A NATIVE SPAWN OR FOLLOW-UP HOOK PAYLOAD SAYS (2026-10-18 SOLID pass).
3
+ *
4
+ * The per-CLI field readings left orchestrator.mjs's run closure, where the
5
+ * 2026-10-17 review fixed four of them: Codex V1 `items`, V2 `task_name`
6
+ * receipts, Claude SendMessage as a follow-up, and collaborationspawn_agent.
7
+ * A vendor release changes these for a reason of its own, apart from the
8
+ * run's slot and note rules. Pure: no record, alias or slot is read here, so
9
+ * a CLI release that changes a field is a row in orchestratorNative.test.mjs.
10
+ * Words the model reads stay in orchestrator.mjs, and so do its refusals:
11
+ * these return fields, or null.
12
+ */
13
+ import { isBackgroundSpawnTool, isClaudeFollowupTool } from './nativeAgentTools.mjs';
14
+
15
+ /** A spawn's prompt and the fields a waiting record is matched and named by,
16
+ * or null when the gate cannot hold it (no call id or no text prompt). */
17
+ export function readSpawn(event) {
18
+ // A child stays in the card's worktree on this machine, inside the pool.
19
+ const { isolation, cwd, ...input } = event.tool_input ?? {}, callId = event.tool_use_id;
20
+ // Codex 0.163 V1 spawn_agent takes `items` in place of `message`.
21
+ const items = Array.isArray(input.items) ? input.items : null;
22
+ const key = ['prompt', 'message'].find(k => typeof input[k] === 'string') ?? (items ? 'items' : null);
23
+ if (!callId || !key) return null;
24
+ const brief = key === 'items' ? items.filter(i => i?.type === 'text' && typeof i.text === 'string').map(i => i.text).join('\n') : input[key];
25
+ return { callId, input, items, key, brief, relocated: isolation !== undefined || cwd !== undefined,
26
+ resume: typeof input.resume === 'string' ? input.resume : null,
27
+ title: input.description ?? input.task_name, kind: input.subagent_type ?? input.agent_type,
28
+ names: [input.name, input.task_name] };
29
+ }
30
+
31
+ /** The spawn's input as the gate rewrites it: the contract appended to its
32
+ * prompt, and a Claude child sent to the background. */
33
+ export const withChildContract = (event, spawn, contract) => ({ ...spawn.input, ...(isBackgroundSpawnTool(event.tool_name) ? { run_in_background: true } : {}),
34
+ ...(spawn.key === 'items' ? { items: [...spawn.items, { type: 'text', text: contract.trimStart() }] } : { [spawn.key]: spawn.input[spawn.key] + contract }) });
35
+
36
+ /** Every native id a spawn or follow-up's receipt names, and whether it says
37
+ * the call failed. */
38
+ export function spawnReceipt(event) {
39
+ let output = event.tool_response;
40
+ try { if (typeof output === 'string') output = JSON.parse(output); } catch { /* native plain output */ }
41
+ if (Array.isArray(output)) output = output.map(b => b.text ?? '').join('');
42
+ try { if (typeof output === 'string') output = JSON.parse(output); } catch { /* native prose */ }
43
+ const blocks = Array.isArray(output?.content) ? output.content.map(b => { try { return JSON.parse(b.text); } catch { return null; } }) : [];
44
+ const ids = [];
45
+ // Every id the receipt names: Codex V1 agent_id, V2's canonical task_name, Claude's agentId.
46
+ for (const receipt of [output, ...blocks]) for (const key of ['thread_id', 'agent_path', 'agent_id', 'agentId', 'task_name'])
47
+ if (typeof receipt?.[key] === 'string') ids.push(receipt[key]);
48
+ return { ids, failed: Boolean(output?.is_error || output?.isError || event.error) };
49
+ }
50
+
51
+ /** Which child a follow-up addresses (null when it names none in bounds), and
52
+ * whether it is Claude's SendMessage. */
53
+ export function followupTarget(event) {
54
+ const input = event.tool_input ?? {}, claude = isClaudeFollowupTool(event.tool_name);
55
+ // Measured Claude 2.1.302 PreToolUse SendMessage input: {to, message, recipient, content, …}.
56
+ const native = claude ? input.to ?? input.recipient ?? input.target ?? input.id : input.target ?? input.id;
57
+ return { claude, native: typeof native === 'string' && native.length <= 160 ? native : null };
58
+ }
@@ -1,14 +1,17 @@
1
+ import { SAFE_POINT_AFTER_EDIT } from './orchestratorProtocol.mjs';
2
+
1
3
  export const ORCHESTRATOR_CONTRACT = `CARD ORCHESTRATOR — this applies to this card's one CLI and worktree.
2
4
  You are the model the person chose and remain responsible for the card and its final JSON.
3
- Work yourself when the card spans one area. Only split work spanning several areas into native in-process subagents by kind (Backend/UI/Tests…). This is authorization to use those native agents on this card. Give each a concrete title, prompt and kind, and post short updates of your own. Do not create extra branches or Flowviant agents. Use a native agent type that can call card_orchestrator MCP (Claude general-purpose); keep the work yourself if none can. After spawning, use describe_agent to report the kind and title you chose when its native role is generic.
5
+ Work yourself when the card spans one area. Only split work spanning several areas into native in-process subagents by kind (Backend/UI/Tests…). This is authorization to use those native agents on this card. Give each a concrete title, prompt and kind, and post short updates of your own. Do not create extra branches or Flowviant agents. Use a native agent type that can call card_orchestrator MCP (Claude general-purpose); keep the work yourself if none can. Never give a child isolation or a different cwd: children share this card's worktree on this machine. After spawning, use describe_agent to report the kind and title you chose when its native role is generic.
4
6
 
5
- The daemon gates every native spawn against this project's Turns at once pool, shared with its other turns. Claude children run in the background so you remain available for messages and their questions; Codex spawns are already asynchronous. A refused spawn with a waiting id has not started. Keep working yourself; do not wait for it. When notified of a free slot, retry its native spawn with the same prompt. Never bypass this gate with another CLI process. The orchestrator itself also holds a slot. If no slot becomes available, finish the work yourself.
7
+ The daemon gates every native spawn against this project's Turns at once pool, shared with its other turns. Claude children run in the background so you remain available for messages and their questions; Codex spawns are already asynchronous. A refused spawn with a waiting id has not started. Keep working yourself; do not wait for it. When notified of a free slot, retry its native spawn (the same or a reworded prompt) or its native follow-up; the slot is held for about a minute, then offered again later. Never bypass this gate with another CLI process. The orchestrator itself also holds a slot. If no slot becomes available, finish the work yourself.
8
+ On Claude you may end your turn while native children run: Claude keeps this process alive and wakes you on each child's completion and on the person's notes. Follow-ups and answers to a Claude child go through SendMessage to its agent id. Write the card's final JSON only after every child has finished or been stopped; your last answer is the card's answer.
6
9
 
7
10
  The card_orchestrator MCP tools report routing and child questions. A live person’s note has a Message id. Report each note using route_message:
8
11
  - Handle it yourself: taken.
9
- - Delegate it: queued, with the native agent id, its kind, and the safePoint chosen FOR THIS NOTE: "when it finishes", "after its current edit", or a precise stated checkpoint.
12
+ - Delegate it: queued, with the native agent id, its kind, and the safePoint chosen FOR THIS NOTE: "when it finishes", "${SAFE_POINT_AFTER_EDIT}", or a precise stated checkpoint.
10
13
  - When it finishes: wait for that child's completion, then put the note in its next native task/follow-up.
11
- - After its current edit: the gate pauses further tool use at the next boundary. Use the native stop/interrupt tool, preserve its work so far, and resume it or start a follow-up with that context and the note. Do not interrupt in the middle of an edit.
14
+ - After its current edit: the gate pauses further tool use at the next boundary and tells you. Deliver the note with a native follow-up (Claude: SendMessage to its agent id, which arrives at its next tool round; Codex: stop/interrupt it, then resume it or follow up with its work so far and the note). Do not interrupt in the middle of an edit.
12
15
  - Only after native delivery at that checkpoint: handed with that agent id/kind/safePoint. This appends "From {orchestrator} …" to its stream. Never report a handoff before doing it.
13
16
  Use the tools' agent record id when one is supplied by a native spawn gate. Keep a later native id with that record.
14
17
 
@@ -1,12 +1,34 @@
1
+ /** The one safe point the gate itself enforces: orchestrator.mjs's checkpoint
2
+ * pauses a child at its next tool boundary when a note's safePoint says these
3
+ * words. route_message and the contract (orchestratorPrompt.mjs) say them in
4
+ * the same words (2026-10-18 SOLID pass). */
5
+ export const SAFE_POINT_AFTER_EDIT = 'after its current edit';
6
+
7
+ /** The name a card turn mounts the orchestrator's server under, so the prefix
8
+ * its tools carry (`mcp__card_orchestrator__…` on Claude, `card_orchestrator.…`
9
+ * on Codex). The checkpoint exempts the server's own calls by it and the mount
10
+ * (workAgentTools.mjs) names it with it; the prompts say it in their own words
11
+ * (2026-10-18, the SOLID pass). */
12
+ export const ORCHESTRATOR_SERVER = 'card_orchestrator';
13
+
14
+ /** A note's route states and a route report's field caps: copies of the shared
15
+ * `agentOrchestration.ts` ones (2026-10-18, the SOLID pass), held there by
16
+ * the release parity gate (scripts/check-app-parity.mjs, rule 40). A machine
17
+ * reports only these three; the server refuses a route over a cap. */
18
+ export const MACHINE_MESSAGE_ROUTE_STATES = ['queued', 'handed', 'taken'];
19
+ export const OPEN_MESSAGE_ROUTE_STATES = ['deciding', 'queued'];
20
+ export const MESSAGE_ROUTE_FIELD_MAX = { id: 160, kind: 80, safePoint: 200 };
21
+
1
22
  /** The run-scoped MCP vocabulary; safe to load in a credential-free client. */
23
+
2
24
  export const ORCHESTRATOR_TOOLS = [
3
25
  { name: 'register_agent', description: 'A native child calls this first with the agent record id in its prompt, before working. It binds native events to that record.',
4
26
  inputSchema: { type: 'object', properties: { agentId: { type: 'string' } }, required: ['agentId'], additionalProperties: false } },
5
27
  { name: 'describe_agent', description: 'The orchestrator reports the kind and title it chose for a child when the native role is generic. Use Backend, UI, Tests or the actual area of work.',
6
28
  inputSchema: { type: 'object', properties: { agentId: { type: 'string' }, kind: { type: 'string' }, title: { type: 'string' } }, required: ['agentId', 'kind', 'title'], additionalProperties: false } },
7
- { name: 'route_message', description: 'Report where you routed a person’s live note. Choose its safe point per note. Report handed only after native delivery at that checkpoint; taken means you handled it yourself.',
8
- inputSchema: { type: 'object', properties: { messageId: { type: 'string' }, state: { enum: ['queued', 'handed', 'taken'] },
9
- agentId: { type: 'string' }, kind: { type: 'string' }, safePoint: { type: 'string' } }, required: ['messageId', 'state'], additionalProperties: false } },
29
+ { name: 'route_message', description: `Report where you routed a person’s live note. Choose its safe point per note: "when it finishes" or "${SAFE_POINT_AFTER_EDIT}" (the gate pauses the child at its next tool boundary), or a precise stated checkpoint. Report handed only after native delivery at that checkpoint; taken means you handled it yourself.`,
30
+ inputSchema: { type: 'object', properties: { messageId: { type: 'string' }, state: { enum: [...MACHINE_MESSAGE_ROUTE_STATES] },
31
+ agentId: { type: 'string' }, kind: { type: 'string' }, safePoint: { type: 'string', description: `"when it finishes", "${SAFE_POINT_AFTER_EDIT}", or a precise stated checkpoint.` } }, required: ['messageId', 'state'], additionalProperties: false } },
10
32
  { name: 'agent_question', description: 'A child asks its orchestrator before asking the person. Call again to read an answer. The orchestrator answers from the card/code/earlier notes or escalates a real person-only decision.',
11
33
  inputSchema: { type: 'object', properties: { agentId: { type: 'string' }, question: { type: 'string' } }, required: ['agentId', 'question'], additionalProperties: false } },
12
34
  { name: 'answer_agent', description: 'Answer a child question from known facts, or mark it for escalation to the person. An escalation returns the attribution to put on the existing blocked question card.',
@@ -60,29 +60,74 @@ Delegation is disabled by policy on fenced turns; tool names may still be listed
60
60
 
61
61
  const list = (names) => names.join(', ');
62
62
 
63
+ /** Claude may list an MCP server's tools by name only until loaded (tool
64
+ * search). A card that did not look once said Flowviant gave it no MCP tools
65
+ * while its servers were mounted (2026-10-17). */
66
+ const DEFERRED = {
67
+ claude: ' A mounted server’s tools may be deferred: when one is not in your tool list, load it with ToolSearch (its server name, or select:mcp__<server>__<tool>) before concluding it is absent.',
68
+ codex: '',
69
+ };
70
+
71
+ /** The machine's servers by their configured names — configuration, never a
72
+ * claim that one connects. Null names (the CLI's file could not be read)
73
+ * say nothing; an empty list keeps the general sentence. */
74
+ const named = (runtime, names) => (Array.isArray(names) && names.length
75
+ ? `${runtime === 'claude' ? ' This machine’s Claude Code servers on this card' : ' This machine’s Codex servers on this card'}: ${list(names)}.`
76
+ : '');
77
+
63
78
  /**
64
79
  * The note for a turn, or '' when its posture withholds nothing to say.
65
80
  * `mounted` is the names of the base `.mcp.json` servers this turn mounted;
66
81
  * `machine` whether the machine's own servers were mounted beside them (a
67
82
  * Claude turn whose `.mcp.json` walk could not be bounded mounts the base copy
68
- * alone); `unmounted` is `[{ name, reason, source }]` for the servers it
69
- * could not mount — `source` 'project' (the base `.mcp.json`) or 'machine'.
83
+ * alone); `machineNames` those servers' names as this machine's CLI config
84
+ * lists them (null when unread — unknown, so unnamed); `unmounted` is
85
+ * `[{ name, reason, source }]` for the servers it could not mount — `source`
86
+ * 'project' (the base `.mcp.json`) or 'machine' — and, with `partial: true`,
87
+ * a machine server mounted with a limit (tools the person set to ask).
88
+ * `orchestrated` a code card whose contract authorizes native children
89
+ * (ORCHESTRATOR_CONTRACT), which the build note then does not contradict.
90
+ *
91
+ * EACH SENTENCE STANDS ALONE (2026-10-17). "The base branch’s .mcp.json lists
92
+ * no MCP server. So are the MCP servers …" read as the machine's servers
93
+ * being absent too; a base entry that could not mount was listed as missing
94
+ * while the machine's server of that name was mounted in its place; and "a
95
+ * server added to .mcp.json only in this worktree is not mounted" read as
96
+ * covering a machine server of that name, which stays mounted (measured,
97
+ * claudeMachineMcp.test.mjs). Codex's list is the servers actually mounted,
98
+ * so an empty one says none is, and a code card is told which base servers
99
+ * it could not mount, as a fenced card is (the review, same day).
70
100
  */
71
- export function postureToolsNote({ posture, runtime, mounted = [], machine = true, unmounted = [] }) {
101
+ export function postureToolsNote({ posture, runtime, mounted = [], machine = true, machineNames, unmounted = [], orchestrated = false }) {
72
102
  const row = POSTURES[posture]?.[runtime];
73
- if (!row) return posture === 'build' ? `TOOLS ON THIS CARD. Use the tools actually exposed by this CLI and its configuration. ${SUPPORTING_CAPABILITIES.replace('Delegation is disabled by policy on fenced turns; tool names may still be listed. Complete the work in this turn.', 'Delegate only when the person explicitly requests it.')} ` : '';
74
- const fromProject = unmounted.filter((u) => u.source !== 'machine');
75
- const fromMachine = unmounted.filter((u) => u.source === 'machine');
103
+ const own = machine && Array.isArray(machineNames) ? new Set(machineNames) : new Set();
104
+ const fromProject = unmounted.filter((u) => u.source !== 'machine' && !own.has(u.name));
105
+ const replaced = unmounted.filter((u) => u.source !== 'machine' && own.has(u.name));
106
+ const fromMachine = unmounted.filter((u) => u.source === 'machine' && !u.partial);
107
+ const limited = unmounted.filter((u) => u.source === 'machine' && u.partial);
108
+ const why = (rows) => list(rows.map(({ name, reason }) => `${name} (${reason})`));
109
+ const missing = (rows, where) => (rows.length ? ` ${where}, but not mounted here: ${why(rows)}.` : '');
110
+ const instead = replaced.length ? ` Listed in the base branch’s .mcp.json but not mountable from it, so mounted from this machine’s configuration instead: ${why(replaced)}.` : '';
111
+ const base = `${missing(fromProject, 'Listed in the base branch’s .mcp.json')}${instead}`;
112
+ if (!row) return posture === 'build' ? `TOOLS ON THIS CARD. Use the tools actually exposed by this CLI and its configuration.${named(runtime, machineNames)}${base}${DEFERRED[runtime] ?? ''} ${SUPPORTING_CAPABILITIES.replace('\nDelegation is disabled by policy on fenced turns; tool names may still be listed. Complete the work in this turn.', orchestrated ? '' : '\nDelegate only when the person explicitly requests it.')} ` : '';
76
113
  const project = mounted.length
77
114
  ? `The project’s MCP servers from the base branch’s .mcp.json are mounted: ${list(mounted)}.`
78
- : fromProject.length
79
- ? 'None of the project’s MCP servers is mounted on this machine.'
115
+ : fromProject.length || replaced.length
116
+ ? 'None of the project’s MCP servers from the base branch’s .mcp.json is mounted on this turn.'
80
117
  : 'The base branch’s .mcp.json lists no MCP server.';
81
- const own = machine
82
- ? ` So are ${MACHINE[runtime]}.`
83
- : ` ${MACHINE[runtime][0].toUpperCase()}${MACHINE[runtime].slice(1)} are not mounted on this turn.`;
84
- const missing = (rows, where) => (rows.length ? ` ${where}, but not mounted here: ${list(rows.map(({ name, reason }) => `${name} (${reason})`))}.` : '');
85
- return `TOOLS ON THIS CARD. You have ${row.has}. You have ${row.lacks}. ${project}${own}${missing(fromProject, 'Listed in the base branch’s .mcp.json')}${missing(fromMachine, `In ${MACHINE[runtime]}`)} A server added to .mcp.json only in this worktree is not mounted; the base branch’s file is the one that counts.
118
+ const Machine = `${MACHINE[runtime][0].toUpperCase()}${MACHINE[runtime].slice(1)}`;
119
+ const mine = machine && runtime === 'codex' && Array.isArray(machineNames) && !machineNames.length
120
+ ? ' No MCP server from this machine’s Codex config is mounted on this turn.'
121
+ : machine && machineNames !== null
122
+ ? ` ${Machine} are also mounted on this turn (configured; not proof of a live connection).${named(runtime, machineNames)}`
123
+ : machine
124
+ ? runtime === 'claude'
125
+ ? ' This machine’s Claude Code configuration (.claude.json) could not be read, so its user and local servers are not mounted on this turn.'
126
+ : ' This machine’s Codex configuration could not be read, so its servers are not mounted on this turn.'
127
+ : ` ${Machine} are not mounted on this turn.`;
128
+ const limits = limited.length ? ` In ${MACHINE[runtime]}, mounted with a limit on this turn: ${why(limited)}.` : '';
129
+ const worktree = machine ? ' is not mounted from that file (a machine server of the same name still is);' : ' is not mounted;';
130
+ return `TOOLS ON THIS CARD. You have ${row.has}. You have ${row.lacks}. ${project}${mine}${base}${missing(fromMachine, `In ${MACHINE[runtime]}`)}${limits}${machine ? DEFERRED[runtime] ?? '' : ''} A server added to .mcp.json only in this worktree${worktree} the base branch’s file is the one that counts.
86
131
  There is no Flowviant setting that adds a tool, so never invent one. A missing
87
132
  server can be configured in the base branch's .mcp.json or the machine's CLI.
88
133
  ${SUPPORTING_CAPABILITIES}`;
@@ -73,11 +73,22 @@ export const ghFirstLine = (e) =>
73
73
  * A FAILURE LINE FIT TO LEAVE THIS MACHINE — userinfo stripped and every
74
74
  * known secret scrubbed. Every git or gh failure relayed to a team-visible
75
75
  * record (a card's activity, the tab chat, an agent's mergeError) goes
76
- * through here: the session PR lane relayed git's push stderr unscrubbed
77
- * while the agent lane scrubbed the same line (audit 2026-10-10, F153/F167).
76
+ * through here or `relayedText`: the session PR lane relayed git's push
77
+ * stderr unscrubbed while the agent lane scrubbed the same line (audit
78
+ * 2026-10-10, F153/F167).
78
79
  */
79
80
  export const relayedFailure = (e) => scrub(ghFirstLine(e));
80
81
 
82
+ /**
83
+ * A WHOLE TEXT fit to leave this machine — userinfo stripped, then every
84
+ * known secret scrubbed. The door for any git or gh output a lane relays
85
+ * whole or line by line; `relayedFailure` is its first-line form for an
86
+ * error. Each caller keeps its own line choice, joining and cap (2026-10-18,
87
+ * the SOLID pass: the composition was spelled by hand at six sites in three
88
+ * lanes, and F153 was one of those copies missing its first half).
89
+ */
90
+ export const relayedText = (text) => scrub(stripUrlUserinfo(text));
91
+
81
92
  /** A pull request URL worth storing. Anything else gh printed is not one. */
82
93
  export const PR_URL_RE = /^https:\/\/github\.com\/[^\s/]+\/[^\s/]+\/pull\/\d+$/;
83
94
 
@@ -3,7 +3,7 @@ import { existsSync, realpathSync } from 'node:fs';
3
3
  import { printableLine } from './printable.mjs';
4
4
  import { listStoredProjects } from './credentials.mjs';
5
5
  import { checkoutIdentity } from './checkoutIdentity.mjs';
6
- import { detectBaseRef, gitOrNull, usableBaseRef, baseBranchName } from './git.mjs';
6
+ import { baseRefFor, gitOrNull } from './git.mjs';
7
7
 
8
8
  export function storedProject(token) {
9
9
  return listStoredProjects().find((entry) => entry.fleetToken === token) ?? null;
@@ -42,7 +42,7 @@ export function createProjectRepositories({ entry, repoRoot, getEntry = () => en
42
42
  const checkout = root && gitOrNull(['rev-parse', '--show-toplevel'], root);
43
43
  const available = checkout && realpathSync(checkout) === realpathSync(root);
44
44
  return { ...repo, repoRoot: available ? root : null,
45
- baseRef: available ? (repo.baseBranch ? usableBaseRef(root, `origin/${baseBranchName(repo.baseBranch)}`) : detectBaseRef(root)) : null };
45
+ baseRef: available ? baseRefFor(root, repo.baseBranch) : null };
46
46
  });
47
47
  };
48
48
  const targets = (job) => {