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.
- 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 +28 -9
- package/bin/lib/claudeMachineMcp.mjs +85 -0
- package/bin/lib/claudePersonal.mjs +6 -6
- package/bin/lib/claudePosture.mjs +6 -15
- package/bin/lib/claudeSettingsArg.mjs +67 -0
- package/bin/lib/claudeSubagentProgress.mjs +12 -28
- package/bin/lib/claudeSubagents.mjs +4 -2
- 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/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 +9 -5
- package/bin/lib/mcpReasons.mjs +20 -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 +212 -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/projectRepositories.mjs +2 -2
- package/bin/lib/projectToolRuntimes.mjs +92 -245
- package/bin/lib/projectTools.mjs +59 -19
- 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/resumeMarker.mjs +43 -0
- package/bin/lib/runTurn.mjs +38 -52
- package/bin/lib/runtimeAntigravity.mjs +1 -1
- package/bin/lib/runtimeCapabilities.mjs +6 -16
- package/bin/lib/runtimeClaude.mjs +62 -19
- package/bin/lib/runtimeCodex.mjs +9 -6
- package/bin/lib/runtimeEvents.mjs +13 -2
- package/bin/lib/runtimeOfferings.mjs +9 -4
- package/bin/lib/runtimes.mjs +3 -1
- 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/versionOrder.mjs +11 -1
- package/bin/lib/work.mjs +6 -6
- 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 +62 -246
- package/bin/lib/workAgentTurnOutcome.mjs +30 -3
- package/bin/lib/workAgentTurnShape.mjs +90 -0
- package/bin/lib/workAgentTurns.mjs +24 -10
- package/bin/lib/workArtifactReconcile.mjs +3 -2
- package/bin/lib/workMergeBatch.mjs +7 -5
- package/bin/lib/workPlaceReports.mjs +3 -3
- package/bin/lib/workPlaces.mjs +14 -0
- package/bin/lib/workSessionRuntime.mjs +2 -5
- package/bin/lib/workSessionTurns.mjs +12 -63
- package/bin/lib/workShip.mjs +8 -18
- package/bin/lib/workTurnShape.mjs +6 -4
- package/package.json +1 -1
package/bin/lib/work.mjs
CHANGED
|
@@ -55,9 +55,9 @@
|
|
|
55
55
|
* (and, split earlier: workDiffs, workPreviews, workPullRequests, workShip,
|
|
56
56
|
* workAgentPlans, workAgentCheck, workAgentPrecheck, workAgentReview,
|
|
57
57
|
* workAgentTurns, workAgentRecovery, workAgentMerges, workMergeBatch,
|
|
58
|
-
* workProcesses;
|
|
59
|
-
* SOLID
|
|
60
|
-
*
|
|
58
|
+
* workProcesses; out of workAgentTurns, SOLID F036: workAgentTurnReports,
|
|
59
|
+
* workAgentTurnExecution, workAgentTurnBegun, workAgentTurnOutcome; out of that run (SOLID, 2026-10-18): workAgentTools, workAgentTurnShape, workAgentResume;
|
|
60
|
+
* repositoryPlaces + agentRepositories: multi-repo places; the live brief: workAgentBrief, workAgentBriefRunner)
|
|
61
61
|
*/
|
|
62
62
|
|
|
63
63
|
import { trackedRepositorySetup } from './repositorySetup.mjs';
|
|
@@ -65,6 +65,7 @@ import { createRepositoryPlaces } from './repositoryPlaces.mjs';
|
|
|
65
65
|
import { createAgentRepositories } from './agentRepositories.mjs';
|
|
66
66
|
import { createWorkProcesses } from './workProcesses.mjs';
|
|
67
67
|
import { createWorkAgentMerges } from './workAgentMerges.mjs';
|
|
68
|
+
import { createWorkMergeBatch } from './workMergeBatch.mjs';
|
|
68
69
|
import { createWorkAgentTurns } from './workAgentTurns.mjs';
|
|
69
70
|
import { createAgentRecovery } from './workAgentRecovery.mjs';
|
|
70
71
|
import { createWorkAgentCheck } from './workAgentCheck.mjs';
|
|
@@ -391,9 +392,8 @@ export function createWorkManager({
|
|
|
391
392
|
fetchAgentFiles,
|
|
392
393
|
});
|
|
393
394
|
const { processAgentMergeJobs, agentMerges } = createWorkAgentMerges({
|
|
394
|
-
runCheck, workChildren, groupKillChildren, admit,
|
|
395
|
-
repositoryPlaces,
|
|
396
|
-
repoRoot,
|
|
395
|
+
runBatch: createWorkMergeBatch({ repositoryPlaces, repoRoot, baseDir, inPlace, runCheck, workChildren, groupKillChildren, admit, onRepoChanged, landed }),
|
|
396
|
+
repositoryPlaces, repoRoot,
|
|
397
397
|
inPlace,
|
|
398
398
|
baseDir,
|
|
399
399
|
gitNet,
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import { createWorkMergeBatch } from './workMergeBatch.mjs';
|
|
2
1
|
import { mergeRepositoryPlace } from './repositoryMerge.mjs';
|
|
3
2
|
import { fleetRequest } from './fleetPost.mjs';
|
|
4
3
|
/** Agent merge claims, pull-request approval, and branch cleanup. */
|
|
5
|
-
import { ensureOpenPr, ghReady, mergeAndVerifyTip, PR_URL_RE, relayedFailure } from './prWorkflow.mjs';
|
|
4
|
+
import { ensureOpenPr, ghReady, mergeAndVerifyTip, PR_URL_RE, relayedFailure, relayedText } from './prWorkflow.mjs';
|
|
6
5
|
import { existsSync } from 'node:fs';
|
|
7
6
|
import { execFileSync } from 'node:child_process';
|
|
8
7
|
import { join } from 'node:path';
|
|
9
8
|
import { FLEET_URL, DAEMON_INSTANCE } from './config.mjs';
|
|
10
|
-
import { git, gitOrNull, gitNet as gitNetIn, baseBranchName, isSafePathSegment, sessionBranch
|
|
9
|
+
import { git, gitOrNull, gitNet as gitNetIn, baseBranchName, isSafePathSegment, sessionBranch } from './git.mjs';
|
|
11
10
|
import { isPublishRef, publishPushArgs, publishDeleteArgs, publishErrorText } from './agentPublish.mjs';
|
|
12
11
|
import { mergeOutward as shipMergeOutward } from './shipMerge.mjs';
|
|
13
12
|
import { gitMergeIn } from './gitMergeIdentity.mjs';
|
|
@@ -29,11 +28,11 @@ export function agentMergeFailureDetail(error) {
|
|
|
29
28
|
.map((value) => String(value ?? '').trim())
|
|
30
29
|
.filter(Boolean)
|
|
31
30
|
.join('\n');
|
|
32
|
-
return
|
|
31
|
+
return relayedText(output || String(error?.message || error)).slice(0, 2000);
|
|
33
32
|
}
|
|
34
33
|
|
|
35
34
|
export function createWorkAgentMerges({
|
|
36
|
-
|
|
35
|
+
runBatch,
|
|
37
36
|
repositoryPlaces,
|
|
38
37
|
repoRoot,
|
|
39
38
|
inPlace,
|
|
@@ -116,7 +115,7 @@ export function createWorkAgentMerges({
|
|
|
116
115
|
const report = async (body) => {
|
|
117
116
|
reported = true;
|
|
118
117
|
if (body?.ok === true) landedRef = job.publishedRef ?? null;
|
|
119
|
-
await postAgentMerge({ ...body, ...(body.detail ? { detail:
|
|
118
|
+
await postAgentMerge({ ...body, ...(body.detail ? { detail: relayedText(String(body.detail)).slice(0, 2000) } : {}) });
|
|
120
119
|
};
|
|
121
120
|
|
|
122
121
|
// A WRITER on the place, exactly as a ship is. It folds base in and pushes
|
|
@@ -436,7 +435,6 @@ export function createWorkAgentMerges({
|
|
|
436
435
|
}
|
|
437
436
|
};
|
|
438
437
|
|
|
439
|
-
const runBatch = createWorkMergeBatch({ repositoryPlaces, repoRoot, baseDir, inPlace, runCheck, workChildren, groupKillChildren, admit, onRepoChanged, landed });
|
|
440
438
|
const processAgentMergeJobs = (jobs) => {
|
|
441
439
|
if (!Array.isArray(jobs) || jobs.length === 0) return;
|
|
442
440
|
for (const job of jobs.slice(0, 2)) {
|
|
@@ -106,11 +106,11 @@ export function createWorkAgentPrecheck({ baseRef, admit, sessionMetaPath, workC
|
|
|
106
106
|
/**
|
|
107
107
|
* THE BRANCH'S OWN COMMITS, subject + trailers, and the card ids they name.
|
|
108
108
|
*
|
|
109
|
-
* MEASURED, never asserted — the same reason `
|
|
110
|
-
* trailer ids are what let the prompt say "N earlier
|
|
111
|
-
* this box" honestly: the difference between the
|
|
112
|
-
* the specs this disk holds is a fact, and a box
|
|
113
|
-
* mid-run is exactly the case where it is non-zero.
|
|
109
|
+
* MEASURED, never asserted — the same reason `commitsSince` exists
|
|
110
|
+
* (turnReceipts.mjs). The trailer ids are what let the prompt say "N earlier
|
|
111
|
+
* cards' specs are not on this box" honestly: the difference between the
|
|
112
|
+
* cards this branch claims and the specs this disk holds is a fact, and a box
|
|
113
|
+
* that adopted the agent mid-run is exactly the case where it is non-zero.
|
|
114
114
|
*
|
|
115
115
|
* IT CANNOT THROW. `baseRef()` is free text an owner typed and may name no
|
|
116
116
|
* ref at all; an unreadable range costs the reviewer its context, never the
|
|
@@ -4,6 +4,7 @@ import { isSafePathSegment, sessionBranchRef, gitNetAsync } from './git.mjs';
|
|
|
4
4
|
import { begunTurnRefusal } from './workAgentTurnBegun.mjs';
|
|
5
5
|
import { fleetEndpoint } from './fleetWire.mjs';
|
|
6
6
|
import { fleetRequest } from './fleetPost.mjs';
|
|
7
|
+
import { AGENT_PLACE_PREFIX } from './workPlaces.mjs';
|
|
7
8
|
|
|
8
9
|
/** Inspect before any cut or CLI. A failed request waits for the next poll. */
|
|
9
10
|
export function createAgentRecovery({
|
|
@@ -32,7 +33,7 @@ export function createAgentRecovery({
|
|
|
32
33
|
for (const job of [...jobs.slice(next), ...jobs.slice(0, next)]) {
|
|
33
34
|
if (!job || !isSafePathSegment(job.agentId) || !isSafePathSegment(job.placeId) ||
|
|
34
35
|
sessionBranchRef(job.placeId) === null || typeof job.sourceKey !== 'string' ||
|
|
35
|
-
!job.sourceBoxId || job.sourceBoxId === id || job.placeId !==
|
|
36
|
+
!job.sourceBoxId || job.sourceBoxId === id || job.placeId !== `${AGENT_PLACE_PREFIX}${job.agentId}`) continue;
|
|
36
37
|
const key = `${job.sourceBoxId}:${job.sourceKey}`;
|
|
37
38
|
if (ready.get(job.agentId) === key) { ready.delete(job.agentId); continue; }
|
|
38
39
|
blocked.add(job.agentId);
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AN AGENT'S RESUME ACROSS TURNS — which conversation its next turn speaks
|
|
3
|
+
* in, and the kept Codex home that conversation is filed under (2026-10-18,
|
|
4
|
+
* the SOLID pass).
|
|
5
|
+
*
|
|
6
|
+
* The seam ARCHITECTURE named for workAgentTurnExecution.mjs ("the
|
|
7
|
+
* resume-marker read and write is its seam"), taken. Resume mechanics have
|
|
8
|
+
* changed four times — the Codex thread pin (2026-09-24), the ran marker only
|
|
9
|
+
* a Claude turn writes (2026-09-24), the kept Codex home (2026-09-29), the
|
|
10
|
+
* per-agent Claude session marker (2026-10-14) — and each time across six
|
|
11
|
+
* separated sites of the run: the reads and the resume decision, the home,
|
|
12
|
+
* the session pinned at init, the thread seen mid-turn, the fresh retry's
|
|
13
|
+
* reset, and the writes after the turn. They sit together here, the per-CLI
|
|
14
|
+
* arms moved verbatim, so neither the decision nor the write order can shift;
|
|
15
|
+
* giving another CLI an agent resume by id is an edit to this file. The read
|
|
16
|
+
* and write procedure is resumeMarker.mjs's, and the id shapes and the
|
|
17
|
+
* Claude marker name are conversationIds.mjs's; the agent's own marker names
|
|
18
|
+
* are spelled here alone.
|
|
19
|
+
*
|
|
20
|
+
* THE RUN KEEPS THE ORDERING: it calls `agentTurnResume` where the reads
|
|
21
|
+
* stood, before the turn's files are fetched, and `persist()` where the
|
|
22
|
+
* writes stood — after the CLI's children are dropped and the trace tail is
|
|
23
|
+
* flushed, before the worktree is re-measured.
|
|
24
|
+
*/
|
|
25
|
+
import { existsSync, writeFileSync } from 'node:fs';
|
|
26
|
+
import { CLAUDE_SESSION_RE, CODEX_THREAD_RE, RESUME_MARKERS } from './conversationIds.mjs';
|
|
27
|
+
import { pinId, readPinnedId } from './resumeMarker.mjs';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* One agent turn's resume. `sessionMetaPath` is the lane's (a path in the
|
|
31
|
+
* worktree's private git dir, or null). Returns the turn's `resume` and
|
|
32
|
+
* `resumeThreadId`, the kept `codexHome`, and what the run tells it as the
|
|
33
|
+
* CLI speaks: `sawSession` (Claude's init), `sawThread` (Codex's
|
|
34
|
+
* `thread.started`), `forgetThread` (a lost resume retried fresh), `threadNow`
|
|
35
|
+
* and, once the turn is over, `persist`.
|
|
36
|
+
*/
|
|
37
|
+
export function agentTurnResume({ rt, wt, agentId, sessionMetaPath }) {
|
|
38
|
+
// ONE AGENT IS ONE DIRECTORY, so the CLI's own cwd-keyed resume is exactly
|
|
39
|
+
// right here — the ambiguity that forced per-tab session pinning in the
|
|
40
|
+
// Workbench (many tabs, one place) cannot arise. The marker is what
|
|
41
|
+
// distinguishes the first turn from every later one across restarts.
|
|
42
|
+
//
|
|
43
|
+
// IT MEANS "CLAUDE HAS SPOKEN HERE", and since 2026-09-24 only a Claude
|
|
44
|
+
// turn writes it: an agent can be switched between CLIs, and a marker a
|
|
45
|
+
// codex turn left behind would send the first Claude turn to
|
|
46
|
+
// `--continue` a directory holding no Claude conversation at all.
|
|
47
|
+
const ranMarker = sessionMetaPath(wt, 'flowviant-agent-ran');
|
|
48
|
+
/**
|
|
49
|
+
* CODEX RESUMES BY ITS OWN THREAD ID, PINNED PER AGENT (2026-09-24).
|
|
50
|
+
*
|
|
51
|
+
* Resume was Claude-only, because codex's `resume --last` is
|
|
52
|
+
* MACHINE-GLOBAL and would cross-resume whichever conversation spoke
|
|
53
|
+
* last anywhere on the box. The cost was that every codex card started
|
|
54
|
+
* a stranger: no memory of the card before it, and — worse — an answer
|
|
55
|
+
* to the agent's own question arrived at a codex that never asked it.
|
|
56
|
+
* The Workbench solved the same problem in 0.69.0 by pinning the id
|
|
57
|
+
* `thread.started` announces; this is that, keyed by the AGENT rather
|
|
58
|
+
* than a tab. The id is shape-checked before it rides argv.
|
|
59
|
+
*/
|
|
60
|
+
const codexThreadMarker =
|
|
61
|
+
rt === 'codex' ? sessionMetaPath(wt, 'flowviant-agent-codex-thread', agentId) : null;
|
|
62
|
+
// An unreadable or misshapen marker is unknown: run fresh (resumeMarker.mjs).
|
|
63
|
+
const codexResumeId = readPinnedId(codexThreadMarker, CODEX_THREAD_RE);
|
|
64
|
+
let seenThreadId = null;
|
|
65
|
+
const claudeMarker = rt === 'claude' ? sessionMetaPath(wt, RESUME_MARKERS.claude, agentId) : null;
|
|
66
|
+
const claudeResumeId = readPinnedId(claudeMarker, CLAUDE_SESSION_RE);
|
|
67
|
+
const resume =
|
|
68
|
+
rt === 'codex'
|
|
69
|
+
? Boolean(codexResumeId)
|
|
70
|
+
: rt === 'claude' && (Boolean(claudeResumeId) || Boolean(ranMarker && existsSync(ranMarker)));
|
|
71
|
+
/**
|
|
72
|
+
* …AND THE HOME THAT THREAD IS FILED UNDER, KEPT PER AGENT (2026-09-29).
|
|
73
|
+
*
|
|
74
|
+
* Codex's thread index records each rollout under the CODEX_HOME the turn
|
|
75
|
+
* ran with, as that path — not the personal store the link leads to — so
|
|
76
|
+
* the per-turn home in /tmp every turn used to get died with its turn and
|
|
77
|
+
* took the pinned thread with it: the next card's `resume` failed "no
|
|
78
|
+
* rollout found" and the agent went to Stuck. Beside the thread marker,
|
|
79
|
+
* in the worktree's private git dir: outside the working tree (never
|
|
80
|
+
* committed, never seen by `git add -A`), refreshed by every turn and
|
|
81
|
+
* removed only with the worktree, when the agent retires and its threads
|
|
82
|
+
* stop mattering. One agent runs one turn at a time (its place's writer
|
|
83
|
+
* lock), so no two turns refresh it at once. Null (no git dir to hold it)
|
|
84
|
+
* is the per-turn home again, and a lost resume then runs once fresh.
|
|
85
|
+
*/
|
|
86
|
+
const codexAgentHome =
|
|
87
|
+
rt === 'codex' ? sessionMetaPath(wt, 'flowviant-agent-codex-home', agentId) : null;
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
resume,
|
|
91
|
+
resumeThreadId: codexResumeId || claudeResumeId || undefined,
|
|
92
|
+
codexHome: codexAgentHome,
|
|
93
|
+
/** Claude's session id from its init, pinned at once (a Claude turn only). */
|
|
94
|
+
sawSession(id) {
|
|
95
|
+
pinId(claudeMarker, CLAUDE_SESSION_RE, id);
|
|
96
|
+
},
|
|
97
|
+
/** The thread Codex announced; a blank one keeps the last. */
|
|
98
|
+
sawThread(id) {
|
|
99
|
+
seenThreadId = String(id ?? '').trim() || seenThreadId;
|
|
100
|
+
},
|
|
101
|
+
/** A lost resume runs fresh: the thread it had seen is not its own. */
|
|
102
|
+
forgetThread() {
|
|
103
|
+
seenThreadId = null;
|
|
104
|
+
},
|
|
105
|
+
threadNow: () => seenThreadId,
|
|
106
|
+
persist() {
|
|
107
|
+
if (ranMarker && rt === 'claude') {
|
|
108
|
+
try {
|
|
109
|
+
writeFileSync(ranMarker, '1');
|
|
110
|
+
} catch {
|
|
111
|
+
/* a missing marker only costs one un-resumed turn */
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// The thread THIS turn spoke under, pinned so the next card, answer
|
|
115
|
+
// or merge-resolve resumes it. After the turn, like the tab lane: an
|
|
116
|
+
// id learned mid-turn is only true once the turn that learned it ends.
|
|
117
|
+
// Best-effort — the next turn runs fresh.
|
|
118
|
+
pinId(codexThreadMarker, CODEX_THREAD_RE, seenThreadId);
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WHAT A CARD TURN MOUNTS, AND WHAT IT IS TOLD (2026-10-18, the SOLID pass).
|
|
3
|
+
*
|
|
4
|
+
* Split out of workAgentTurnExecution.mjs, where it had become a second
|
|
5
|
+
* vocabulary inside one agent turn's run: the base-branch snapshot read across
|
|
6
|
+
* the card's repository sources, the daemon's own turn servers, Claude's
|
|
7
|
+
* machine servers beside the base copy or Codex's fenced mount, and the TOOLS
|
|
8
|
+
* note that says all of it to the agent. Those change together — three
|
|
9
|
+
* 2026-10-17 commits (703750746, 2cc52e42c, 65051ac42) edited this block and
|
|
10
|
+
* nothing else of the run — and for reasons the run does not share. Whether a
|
|
11
|
+
* CLI takes MCP at all is asked of the tool table (`runnerToolCapabilities`,
|
|
12
|
+
* projectToolRuntimes.mjs) rather than listed again here, and the daemon's
|
|
13
|
+
* server names are spelled once (`CARD_TURN_SERVERS`), so a base `.mcp.json`
|
|
14
|
+
* that reuses one is still never told to the agent as a project server.
|
|
15
|
+
*
|
|
16
|
+
* THE RUN KEEPS THE ORDERING: it calls this after `createOrchestrator` (whose
|
|
17
|
+
* config is one of the turn servers), inside the try whose finally cleans the
|
|
18
|
+
* prepared tools up, and merges the returned `env` into the CLI's `mcpEnv`
|
|
19
|
+
* ahead of the repository tool's and the orchestrator's. Until this returns
|
|
20
|
+
* the run holds no handle to clean, so a throw after the tools are prepared
|
|
21
|
+
* cleans them up here.
|
|
22
|
+
*/
|
|
23
|
+
import { mcpJsonServerNamesAbove, readBaseTools, prepareAgentTools } from './projectTools.mjs';
|
|
24
|
+
import { runnerToolCapabilities } from './projectToolRuntimes.mjs';
|
|
25
|
+
import { TURN_PROFILES } from './turnProfile.mjs';
|
|
26
|
+
import { postureToolsNote } from './postureTools.mjs';
|
|
27
|
+
import { machineMcpServers } from './claudeMachineMcp.mjs';
|
|
28
|
+
import { ORCHESTRATOR_SERVER } from './orchestratorProtocol.mjs';
|
|
29
|
+
|
|
30
|
+
/** The servers the daemon itself mounts on a card turn, by the names the
|
|
31
|
+
* agent calls them: a multi-repository card's add_repository, and an
|
|
32
|
+
* orchestrated card's routing. Never a project server's, whatever the base
|
|
33
|
+
* `.mcp.json` names. */
|
|
34
|
+
export const CARD_TURN_SERVERS = Object.freeze({ repositories: 'card_repositories', orchestrator: ORCHESTRATOR_SERVER });
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The card turn's tools, prepared, and what it is told of them.
|
|
38
|
+
*
|
|
39
|
+
* `baseRef` is the lane's own function, read only when the card's worktree is
|
|
40
|
+
* none of its repository sources. `env` is the environment the turn expands
|
|
41
|
+
* from and `machineServers` claudeMachineMcp.mjs's reader: the process's own in
|
|
42
|
+
* the lane, a scratch home in a test. Returns `{ projectTools, toolsNote, env,
|
|
43
|
+
* envNeeded, mountArgs }` — the prepared tools (the caller cleans them up
|
|
44
|
+
* from here), the note, the mount's part of the CLI's `mcpEnv` and whether it
|
|
45
|
+
* needs one, and the keys the runner takes the mount as.
|
|
46
|
+
*/
|
|
47
|
+
export function prepareCardTools({ rt, posture, wt, repoRoot, baseRef, sources = [], repositoryTool = null, orchestratorConfig = null, orchestrated = false, codexHome = null, env = process.env, machineServers = machineMcpServers }) {
|
|
48
|
+
// A turn reads the base ref afresh. Changes the agent has made to its
|
|
49
|
+
// own worktree cannot silently change the next turn's tools.
|
|
50
|
+
const source = sources.find((r) => r.wt === wt);
|
|
51
|
+
const snapshot = readBaseTools(source?.repoRoot ?? repoRoot, source?.baseRef ?? baseRef());
|
|
52
|
+
for (const other of sources.filter((r) => r !== source)) {
|
|
53
|
+
const base = readBaseTools(other.repoRoot, other.baseRef);
|
|
54
|
+
snapshot.instructions.push(...[...base.instructions, ...base.skillFiles].map((file) => ({ ...file, path: `repositories/${other.folder}/${file.path}` })));
|
|
55
|
+
}
|
|
56
|
+
const projectTools = prepareAgentTools(snapshot, rt, env, wt, { codexHome, fenced: posture !== 'build', turnServers: {
|
|
57
|
+
...(repositoryTool ? { [CARD_TURN_SERVERS.repositories]: repositoryTool.config } : {}),
|
|
58
|
+
...(orchestratorConfig ? { [CARD_TURN_SERVERS.orchestrator]: orchestratorConfig } : {}),
|
|
59
|
+
} });
|
|
60
|
+
try {
|
|
61
|
+
const mountsProjectMcp = TURN_PROFILES[posture]?.projectMcp === true && runnerToolCapabilities(rt).mcp;
|
|
62
|
+
// A fenced posture on a runner that ignores its own file there (Codex's
|
|
63
|
+
// `--ignore-user-config`) takes the servers as `fencedMount` instead.
|
|
64
|
+
const fencedMount = mountsProjectMcp && posture !== 'build' ? projectTools.fencedMount : null;
|
|
65
|
+
// THE MACHINE'S OWN SERVERS ON CLAUDE (2026-10-11, the owner): the turn
|
|
66
|
+
// is not strict, and every `.mcp.json` name from the worktree up is
|
|
67
|
+
// disabled so only the base copy speaks for the project. Null (an
|
|
68
|
+
// unboundable walk) mounts strictly: the base copy alone.
|
|
69
|
+
const claudeDisabled = mountsProjectMcp && rt === 'claude' ? mcpJsonServerNamesAbove(wt) : null;
|
|
70
|
+
// The machine's servers by name, as its CLI's own config lists them
|
|
71
|
+
// (2026-10-17): a card that is told only "the machine's servers" and
|
|
72
|
+
// finds none in a deferred tool list concludes it has no MCP at all.
|
|
73
|
+
// A Codex code card's are the ones its kept config.toml mounts
|
|
74
|
+
// (2026-10-18), never the fenced mount's narrower list.
|
|
75
|
+
const baseNames = projectTools.mcpServers.filter((name) => !Object.values(CARD_TURN_SERVERS).includes(name));
|
|
76
|
+
const claudeMachine = claudeDisabled != null ? machineServers({ user: true, checkout: source?.repoRoot ?? repoRoot, env }) : {};
|
|
77
|
+
const machineNames = !mountsProjectMcp ? [] : rt === 'claude'
|
|
78
|
+
? (claudeMachine == null ? null : Object.keys(claudeMachine).filter((name) => !projectTools.mcpServers.includes(name)))
|
|
79
|
+
: posture !== 'build' ? (projectTools.fencedMount ? projectTools.fencedMount.machine : []) : projectTools.codexMachineServers;
|
|
80
|
+
// What this card's posture withholds, said to the agent — with the
|
|
81
|
+
// servers it did and did not mount, and why — so it relays the fence
|
|
82
|
+
// instead of inventing a setting (postureTools.mjs). Empty for a code card.
|
|
83
|
+
const toolsNote = postureToolsNote({
|
|
84
|
+
posture,
|
|
85
|
+
runtime: rt,
|
|
86
|
+
orchestrated,
|
|
87
|
+
mounted: !mountsProjectMcp ? [] : fencedMount ? fencedMount.mounted : baseNames,
|
|
88
|
+
machine: mountsProjectMcp && (rt === 'codex' || claudeDisabled != null),
|
|
89
|
+
machineNames,
|
|
90
|
+
unmounted: [...projectTools.mcpUnmounted.map((u) => ({ ...u, source: 'project' })), ...(fencedMount ? fencedMount.unmounted : [])],
|
|
91
|
+
});
|
|
92
|
+
return {
|
|
93
|
+
projectTools,
|
|
94
|
+
toolsNote,
|
|
95
|
+
env: { ...(projectTools.codexHome ? { CODEX_HOME: projectTools.codexHome } : {}), ...(fencedMount?.env ?? {}) },
|
|
96
|
+
envNeeded: Boolean(projectTools.codexHome || fencedMount),
|
|
97
|
+
// Reviewed project servers and the machine's configured servers, on
|
|
98
|
+
// initial cards, answers and review iterations alike.
|
|
99
|
+
mountArgs: mountsProjectMcp && rt === 'claude'
|
|
100
|
+
? {
|
|
101
|
+
mcpArgs: [...(claudeDisabled ? [] : ['--strict-mcp-config']), '--mcp-config', projectTools.mcpPath],
|
|
102
|
+
projectMcpServers: projectTools.mcpServers,
|
|
103
|
+
...(claudeDisabled ? { machineMcp: claudeDisabled, mcpCheckout: source?.repoRoot ?? repoRoot } : {}),
|
|
104
|
+
}
|
|
105
|
+
: fencedMount ? { mcpArgs: fencedMount.args } : {},
|
|
106
|
+
};
|
|
107
|
+
} catch (e) {
|
|
108
|
+
projectTools.cleanup();
|
|
109
|
+
throw e;
|
|
110
|
+
}
|
|
111
|
+
}
|