flowviant 0.125.5 → 0.125.7
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.
- package/README.md +4 -5
- package/bin/lib/agentInternalWait.mjs +1 -5
- package/bin/lib/agentPlan.mjs +13 -163
- package/bin/lib/agentRepositories.mjs +3 -1
- package/bin/lib/claudeInput.mjs +89 -10
- package/bin/lib/claudeMachineMcp.mjs +85 -0
- package/bin/lib/claudePersonal.mjs +6 -57
- package/bin/lib/claudePosture.mjs +6 -15
- package/bin/lib/claudeSettingsArg.mjs +67 -0
- package/bin/lib/claudeStream.mjs +30 -1
- package/bin/lib/claudeSubagentProgress.mjs +12 -28
- package/bin/lib/claudeSubagents.mjs +4 -2
- package/bin/lib/codexContract.mjs +52 -0
- package/bin/lib/codexFencedMount.mjs +275 -0
- package/bin/lib/codexPersonal.mjs +1 -265
- package/bin/lib/codexProjectRead.mjs +5 -4
- package/bin/lib/codexResearch.mjs +9 -3
- package/bin/lib/codexSubagentEvents.mjs +11 -7
- package/bin/lib/codexSubagents.mjs +55 -52
- package/bin/lib/codexToml.mjs +328 -0
- package/bin/lib/config.mjs +22 -1
- package/bin/lib/conversationIds.mjs +10 -0
- package/bin/lib/fleet.mjs +2 -2
- package/bin/lib/git.mjs +32 -0
- package/bin/lib/heldTool.mjs +49 -0
- package/bin/lib/hooks/mcpAllow.mjs +11 -5
- package/bin/lib/hooks/orchestrator.mjs +20 -10
- package/bin/lib/intakeDraft.mjs +1 -1
- package/bin/lib/jsonlTail.mjs +52 -0
- package/bin/lib/mcpInventory.mjs +11 -6
- package/bin/lib/mcpReasons.mjs +26 -0
- package/bin/lib/mcpReportBounds.mjs +10 -0
- package/bin/lib/mcpStdio.mjs +35 -0
- package/bin/lib/mergeBatch.mjs +6 -24
- package/bin/lib/mergeOrder.mjs +18 -0
- package/bin/lib/modelJson.mjs +173 -0
- package/bin/lib/nativeAgentTools.mjs +72 -0
- package/bin/lib/orchestrator.mjs +221 -133
- package/bin/lib/orchestratorClient.mjs +17 -21
- package/bin/lib/orchestratorHooks.mjs +21 -4
- package/bin/lib/orchestratorNative.mjs +58 -0
- package/bin/lib/orchestratorPrompt.mjs +7 -4
- package/bin/lib/orchestratorProtocol.mjs +25 -3
- package/bin/lib/postureTools.mjs +58 -13
- package/bin/lib/prWorkflow.mjs +13 -2
- package/bin/lib/processGroup.mjs +41 -0
- package/bin/lib/projectRepositories.mjs +2 -2
- package/bin/lib/projectToolRuntimes.mjs +92 -245
- package/bin/lib/projectTools.mjs +60 -19
- package/bin/lib/prompts.mjs +47 -18
- package/bin/lib/reportJournal.mjs +61 -0
- package/bin/lib/repositoryDelivery.mjs +6 -4
- package/bin/lib/repositoryMerge.mjs +8 -12
- package/bin/lib/repositoryPlaces.mjs +2 -2
- package/bin/lib/repositoryPrompts.mjs +42 -0
- package/bin/lib/repositoryTool.mjs +9 -32
- package/bin/lib/researchUse.mjs +39 -0
- package/bin/lib/resumeLost.mjs +21 -1
- package/bin/lib/resumeMarker.mjs +43 -0
- package/bin/lib/runTurn.mjs +55 -52
- package/bin/lib/runtimeAntigravity.mjs +1 -1
- package/bin/lib/runtimeCapabilities.mjs +13 -18
- package/bin/lib/runtimeClaude.mjs +68 -24
- package/bin/lib/runtimeCodex.mjs +12 -6
- package/bin/lib/runtimeEvents.mjs +13 -2
- package/bin/lib/runtimeOfferings.mjs +12 -5
- package/bin/lib/runtimes.mjs +3 -1
- package/bin/lib/startProject.mjs +10 -0
- package/bin/lib/subagentBounds.mjs +20 -0
- package/bin/lib/trace.mjs +30 -5
- package/bin/lib/turnDeclaration.mjs +1 -1
- package/bin/lib/turnDiagnostics.mjs +9 -12
- package/bin/lib/turnReceipts.mjs +64 -0
- package/bin/lib/turnSubagents.mjs +91 -0
- package/bin/lib/turnUsage.mjs +9 -6
- package/bin/lib/update.mjs +18 -4
- package/bin/lib/versionOrder.mjs +11 -1
- package/bin/lib/work.mjs +9 -9
- package/bin/lib/workAgentMerges.mjs +5 -7
- package/bin/lib/workAgentPrecheck.mjs +5 -5
- package/bin/lib/workAgentRecovery.mjs +2 -1
- package/bin/lib/workAgentResume.mjs +121 -0
- package/bin/lib/workAgentTools.mjs +111 -0
- package/bin/lib/workAgentTurnExecution.mjs +82 -246
- package/bin/lib/workAgentTurnOutcome.mjs +30 -3
- package/bin/lib/workAgentTurnReports.mjs +16 -0
- package/bin/lib/workAgentTurnShape.mjs +90 -0
- package/bin/lib/workAgentTurns.mjs +28 -12
- package/bin/lib/workArtifactReconcile.mjs +3 -2
- package/bin/lib/workAttachments.mjs +27 -5
- package/bin/lib/workMergeBatch.mjs +7 -5
- package/bin/lib/workNarration.mjs +32 -0
- package/bin/lib/workPlaceReports.mjs +3 -3
- package/bin/lib/workPlaces.mjs +14 -0
- package/bin/lib/workReportQueue.mjs +18 -1
- package/bin/lib/workSessionRuntime.mjs +2 -5
- package/bin/lib/workSessionTurns.mjs +68 -80
- package/bin/lib/workShip.mjs +8 -18
- package/bin/lib/workTurnShape.mjs +6 -4
- package/bin/lib/worktreeDiff.mjs +29 -5
- package/package.json +1 -1
|
@@ -1,15 +1,32 @@
|
|
|
1
1
|
import { fileURLToPath } from 'node:url';
|
|
2
2
|
import { hookCommand } from './claudePosture.mjs';
|
|
3
|
+
import { CLAUDE_POST_HOOK_MATCHER } from './nativeAgentTools.mjs';
|
|
4
|
+
import { toml } from './codexToml.mjs';
|
|
5
|
+
import { versionAtLeast } from './versionOrder.mjs';
|
|
6
|
+
|
|
7
|
+
/** Seconds. Above the client's own timeout (orchestratorClient.mjs). */
|
|
8
|
+
export const ORCHESTRATOR_HOOK_TIMEOUT_S = 20;
|
|
9
|
+
|
|
10
|
+
/** The Claude Code the gate was verified on (2026-10-17): hook `agent_id`,
|
|
11
|
+
* `updatedInput`, PostToolUseFailure, SubagentStop and background Agent.
|
|
12
|
+
* An older or unmeasured CLI runs the card solo. */
|
|
13
|
+
export const CLAUDE_ORCHESTRATION_MIN = '2.1.302';
|
|
14
|
+
export function claudeOrchestrationSafe(version) {
|
|
15
|
+
return versionAtLeast(String(version ?? '').trim().split(/\s+/)[0], CLAUDE_ORCHESTRATION_MIN);
|
|
16
|
+
}
|
|
3
17
|
|
|
4
18
|
const command = () => hookCommand('orchestrator', fileURLToPath(new URL('./hooks/orchestrator.mjs', import.meta.url)));
|
|
5
19
|
export function orchestratorHooks() {
|
|
6
|
-
const hooks = [{ type: 'command', command: command(), timeout:
|
|
7
|
-
|
|
20
|
+
const hooks = [{ type: 'command', command: command(), timeout: ORCHESTRATOR_HOOK_TIMEOUT_S }];
|
|
21
|
+
// Claude's post hooks matter only for native spawns; each run starts the
|
|
22
|
+
// daemon's binary. Bare names are an exact-name list (2.1.302).
|
|
23
|
+
const spawns = CLAUDE_POST_HOOK_MATCHER;
|
|
24
|
+
return { PreToolUse: [{ matcher: '.*', hooks }], PostToolUse: [{ matcher: spawns, hooks }], PostToolUseFailure: [{ matcher: spawns, hooks }], SubagentStop: [{ hooks }] };
|
|
8
25
|
}
|
|
9
26
|
|
|
10
27
|
export function codexOrchestratorArgs() {
|
|
11
|
-
|
|
12
|
-
const row = `{matcher=".*",hooks=[{type="command",command=${
|
|
28
|
+
// A Codex `-c` value is TOML, quoted by the one quoter (codexToml.mjs).
|
|
29
|
+
const row = `{matcher=".*",hooks=[{type="command",command=${toml(command())},timeout=${ORCHESTRATOR_HOOK_TIMEOUT_S}}]}`;
|
|
13
30
|
return ['--dangerously-bypass-hook-trust', '-c', 'features.hooks=true',
|
|
14
31
|
'-c', `hooks.PreToolUse=[${row}]`, '-c', `hooks.PostToolUse=[${row}]`, '-c', `hooks.SubagentStop=[${row}]`];
|
|
15
32
|
}
|
|
@@ -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
|
|
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", "
|
|
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.
|
|
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:
|
|
8
|
-
inputSchema: { type: 'object', properties: { messageId: { type: 'string' }, state: { enum: [
|
|
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.',
|
package/bin/lib/postureTools.mjs
CHANGED
|
@@ -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); `
|
|
69
|
-
*
|
|
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
|
-
|
|
74
|
-
const fromProject = unmounted.filter((u) => u.source !== 'machine');
|
|
75
|
-
const
|
|
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
|
|
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
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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}`;
|
package/bin/lib/prWorkflow.mjs
CHANGED
|
@@ -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
|
|
77
|
-
* while the agent lane scrubbed the same line (audit
|
|
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
|
|
package/bin/lib/processGroup.mjs
CHANGED
|
@@ -46,3 +46,44 @@ export function killGroup(target, signal = 'SIGKILL') {
|
|
|
46
46
|
}
|
|
47
47
|
return false;
|
|
48
48
|
}
|
|
49
|
+
|
|
50
|
+
/** How long a CLI has to stop on its own interrupt before its group is
|
|
51
|
+
* killed. Claude Code 2.1.303 exits ~130 ms after a SIGINT (measured). */
|
|
52
|
+
export const STOP_GRACE_MS = 3_000;
|
|
53
|
+
const STOPPING = new WeakSet();
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* STOP A CLI THE WAY A PERSON AT ITS TERMINAL WOULD, THEN MAKE SURE
|
|
57
|
+
* (2026-10-19) — the Terminal's and an agent's Stop.
|
|
58
|
+
*
|
|
59
|
+
* Stop used to SIGKILL the CLI's group at once. Measured offline on Claude
|
|
60
|
+
* Code 2.1.303, its Bash tool runs each command in a session of its own, so
|
|
61
|
+
* that kill reached only the CLI: the command it was running — a test
|
|
62
|
+
* suite, a build — kept running with nobody left to stop it, while a
|
|
63
|
+
* background dev server lived on anyway. A SIGINT is the CLI's own interrupt:
|
|
64
|
+
* it ends the turn (`error_during_execution`), stops the command it was
|
|
65
|
+
* running and leaves what it put in the background, as Ctrl-C at its prompt
|
|
66
|
+
* does. Still not CLOSED after `graceMs` — the CLI alive, or something in its
|
|
67
|
+
* group holding the pipes the turn reads to its end — it gets the old group
|
|
68
|
+
* kill, so Stop always ends the turn. A server that let go of those pipes
|
|
69
|
+
* (Claude's own, its `nohup … &`) is never in the way. Repeated calls (the
|
|
70
|
+
* roster re-asks every poll) start one grace, not many.
|
|
71
|
+
*/
|
|
72
|
+
export function interruptThenKill(child, { graceMs = STOP_GRACE_MS, kill = killGroup } = {}) {
|
|
73
|
+
if (!child || typeof child !== 'object' || STOPPING.has(child)) return;
|
|
74
|
+
STOPPING.add(child);
|
|
75
|
+
let closed = false;
|
|
76
|
+
const timer = setTimeout(() => {
|
|
77
|
+
if (!closed) kill(child);
|
|
78
|
+
}, graceMs);
|
|
79
|
+
timer.unref?.();
|
|
80
|
+
child.once?.('close', () => {
|
|
81
|
+
closed = true;
|
|
82
|
+
clearTimeout(timer);
|
|
83
|
+
});
|
|
84
|
+
try {
|
|
85
|
+
if (child.exitCode == null && child.signalCode == null) child.kill('SIGINT');
|
|
86
|
+
} catch {
|
|
87
|
+
/* gone already; the grace still reaps what holds its pipes */
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -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 {
|
|
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 ? (
|
|
45
|
+
baseRef: available ? baseRefFor(root, repo.baseBranch) : null };
|
|
46
46
|
});
|
|
47
47
|
};
|
|
48
48
|
const targets = (job) => {
|