flowviant 0.125.4 → 0.125.5
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.
|
@@ -46,9 +46,9 @@
|
|
|
46
46
|
* swept on the next write.
|
|
47
47
|
*/
|
|
48
48
|
import { randomBytes } from 'node:crypto';
|
|
49
|
-
import { chmodSync, lstatSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
49
|
+
import { chmodSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
50
50
|
import { homedir } from 'node:os';
|
|
51
|
-
import { basename, dirname, join } from 'node:path';
|
|
51
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
52
52
|
import { MACHINE_CREDENTIAL_ENV } from './machineEnv.mjs';
|
|
53
53
|
import { WINDOW_ENV_REMOVED, withoutWindows } from './noWindowEnv.mjs';
|
|
54
54
|
|
|
@@ -141,6 +141,56 @@ export function personalClaudeSettings(env = process.env) {
|
|
|
141
141
|
return out;
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
/** Far past any real CLI config — `.claude.json` also holds every project's
|
|
145
|
+
* history; a bigger one is not read. */
|
|
146
|
+
const CLI_CONFIG_MAX_BYTES = 32 * 1024 * 1024;
|
|
147
|
+
const isRecord = (v) => v != null && typeof v === 'object' && !Array.isArray(v);
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* THE MACHINE'S OWN MCP SERVERS THAT A CARD TURN'S SETTINGS SOURCES NEVER
|
|
151
|
+
* REACH (2026-10-17). The 2026-10-11 ruling mounts "the user and local
|
|
152
|
+
* servers" on every card turn, and neither arrived where the person keeps
|
|
153
|
+
* them. MEASURED on Claude Code 2.1.302, a scratch CLAUDE_CONFIG_DIR whose
|
|
154
|
+
* `.claude.json` held one user and one local server:
|
|
155
|
+
* · `--setting-sources ''` (every fenced posture: write-up, mockup, 3D model,
|
|
156
|
+
* deck) loaded NEITHER — a research card on the owner's box saw the
|
|
157
|
+
* claude.ai connectors and no Roblox_Studio, a user-scope server;
|
|
158
|
+
* · `--setting-sources user` (a code card) loaded the user server only;
|
|
159
|
+
* · a local server is keyed on the git root it was added from (`claude mcp
|
|
160
|
+
* add`'s default scope), which is the person's checkout — never the
|
|
161
|
+
* agent's worktree, so no card turn could see one even with every source.
|
|
162
|
+
* So the lane names what is missing and the adapter hands it over through a
|
|
163
|
+
* private `--mcp-config` file (runtimeClaude.mjs): `user` when the turn's
|
|
164
|
+
* sources drop the user scope, and the checkout's local servers always. A
|
|
165
|
+
* local name shadows a user one, the CLI's own precedence; an `--mcp-config`
|
|
166
|
+
* server beat a user server of the same name (measured), so a code card keeps
|
|
167
|
+
* that order too. `{}` when the file is absent, unreadable or not an object.
|
|
168
|
+
*/
|
|
169
|
+
export function machineMcpServers({ user = false, checkout = null, env = process.env } = {}) {
|
|
170
|
+
const path = env.CLAUDE_CONFIG_DIR ? join(env.CLAUDE_CONFIG_DIR, '.claude.json') : join(env.HOME || homedir(), '.claude.json');
|
|
171
|
+
let raw;
|
|
172
|
+
try {
|
|
173
|
+
if (statSync(path).size > CLI_CONFIG_MAX_BYTES) return {};
|
|
174
|
+
raw = JSON.parse(readFileSync(path, 'utf8'));
|
|
175
|
+
} catch {
|
|
176
|
+
return {};
|
|
177
|
+
}
|
|
178
|
+
if (!isRecord(raw)) return {};
|
|
179
|
+
const out = {};
|
|
180
|
+
const take = (servers) => {
|
|
181
|
+
if (!isRecord(servers)) return;
|
|
182
|
+
for (const [name, config] of Object.entries(servers)) if (name !== '__proto__' && isRecord(config)) out[name] = config;
|
|
183
|
+
};
|
|
184
|
+
if (user) take(raw.mcpServers);
|
|
185
|
+
if (typeof checkout === 'string' && checkout && isRecord(raw.projects)) {
|
|
186
|
+
const keys = [resolve(checkout)];
|
|
187
|
+
try { keys.push(realpathSync(checkout)); } catch { /* the resolved path alone */ }
|
|
188
|
+
const key = keys.find((k) => Object.hasOwn(raw.projects, k));
|
|
189
|
+
if (key) take(raw.projects[key]?.mcpServers);
|
|
190
|
+
}
|
|
191
|
+
return out;
|
|
192
|
+
}
|
|
193
|
+
|
|
144
194
|
/** Where a fenced turn's settings file lives — the daemon's own state
|
|
145
195
|
* directory, private to this user. */
|
|
146
196
|
export const turnSettingsDir = () => join(homedir(), '.flowviant', 'run');
|
|
@@ -182,11 +232,12 @@ export function writeTurnSettings(settings, { dir = turnSettingsDir() } = {}) {
|
|
|
182
232
|
}
|
|
183
233
|
}
|
|
184
234
|
|
|
185
|
-
/** Remove the settings file a turn's argv names, when it is one of ours
|
|
235
|
+
/** Remove the settings file a turn's argv names, when it is one of ours — and
|
|
236
|
+
* the machine-MCP file (`machineMcpServers`), written the same way. */
|
|
186
237
|
export function releaseTurnSettings(args, { dir = turnSettingsDir() } = {}) {
|
|
187
238
|
if (!Array.isArray(args)) return;
|
|
188
239
|
args.forEach((a, i) => {
|
|
189
|
-
if (i === 0 || args[i - 1]
|
|
240
|
+
if (i === 0 || !['--settings', '--mcp-config'].includes(args[i - 1]) || typeof a !== 'string') return;
|
|
190
241
|
if (dirname(a) !== dir || !TURN_SETTINGS_FILE.test(basename(a))) return;
|
|
191
242
|
rmSync(a, { force: true });
|
|
192
243
|
});
|
package/bin/lib/runTurn.mjs
CHANGED
|
@@ -234,6 +234,10 @@ function runPlainTurn(opts) {
|
|
|
234
234
|
// server names an agent lane disables beside them, on a profile that
|
|
235
235
|
// mounts project servers — never on one that does not.
|
|
236
236
|
machineMcp: turnProfile.projectMcp ? opts.machineMcp : undefined,
|
|
237
|
+
// …beside them, the names the base copy mounts and the person's checkout,
|
|
238
|
+
// whose local servers no card turn's cwd reaches (2026-10-17).
|
|
239
|
+
baseMcp: turnProfile.projectMcp ? (opts.projectMcpServers ?? []) : [],
|
|
240
|
+
mcpCheckout: turnProfile.projectMcp ? opts.mcpCheckout : undefined,
|
|
237
241
|
// Handed to the adapter rather than appended here, because WHERE these go
|
|
238
242
|
// is a property of the CLI: Codex reads its prompt as a trailing
|
|
239
243
|
// positional, so a flag after it is a flag in the wrong place.
|
|
@@ -14,7 +14,7 @@ import { mkdtempSync, writeFileSync } from 'node:fs';
|
|
|
14
14
|
import { tmpdir } from 'node:os';
|
|
15
15
|
import { join } from 'node:path';
|
|
16
16
|
import { MODEL, USER_AGENT } from './config.mjs';
|
|
17
|
-
import { NO_SETTING_SOURCES, personalClaudeSettings, releaseTurnSettings, withPersonalSettings } from './claudePersonal.mjs';
|
|
17
|
+
import { NO_SETTING_SOURCES, machineMcpServers, personalClaudeSettings, releaseTurnSettings, withPersonalSettings, writeTurnSettings } from './claudePersonal.mjs';
|
|
18
18
|
import { withMachineMcp } from './claudePosture.mjs';
|
|
19
19
|
import { orchestratorHooks } from './orchestratorHooks.mjs';
|
|
20
20
|
|
|
@@ -102,7 +102,7 @@ export const CLAUDE_RUNTIME = {
|
|
|
102
102
|
* strongest form of it available anywhere: `--append-system-prompt` sits
|
|
103
103
|
* above the conversation rather than inside it.
|
|
104
104
|
*/
|
|
105
|
-
args({ writableDirs = [], prompt, system, model, effort, resume, perm, profile = 'build', mcp = [], resultSchemaArgs = [], adoptResumeId, resumeThreadId, knowledgeDir, vaultDir, agentTools, personalSettings, machineMcp, streamInput = false, orchestration = false }) {
|
|
105
|
+
args({ writableDirs = [], prompt, system, model, effort, resume, perm, profile = 'build', mcp = [], resultSchemaArgs = [], adoptResumeId, resumeThreadId, knowledgeDir, vaultDir, agentTools, personalSettings, machineMcp, baseMcp = [], mcpCheckout = null, streamInput = false, orchestration = false }) {
|
|
106
106
|
// THE CARTOGRAPHER'S FENCE NAMES ITS VAULT (2026-10-10): its one write rule
|
|
107
107
|
// is the vault's absolute path (claudePosture.mjs `wikiPermFor`), so a turn
|
|
108
108
|
// with no vault, or one no rule can name, has no list — refused here in
|
|
@@ -221,8 +221,24 @@ export const CLAUDE_RUNTIME = {
|
|
|
221
221
|
// kept is written to a private file, never onto argv (claudePersonal.mjs)
|
|
222
222
|
// — so this is the LAST thing built: nothing after it may throw and
|
|
223
223
|
// leave the file with no turn to release it.
|
|
224
|
+
//
|
|
225
|
+
// …AND THE MACHINE'S SERVERS NO SOURCE OF THIS TURN READS (2026-10-17,
|
|
226
|
+
// claudePersonal.mjs `machineMcpServers`): the user scope a fenced turn's
|
|
227
|
+
// empty source list drops, and the checkout's local scope every card turn
|
|
228
|
+
// missed. In a private file too — a server's env and headers are secrets —
|
|
229
|
+
// minus every name the base copy mounts, which speaks for the project.
|
|
230
|
+
const own = machine ? machineMcpServers({ user: fenced, checkout: mcpCheckout }) : {};
|
|
231
|
+
for (const name of baseMcp) delete own[name];
|
|
232
|
+
const ownArgs = Object.keys(own).length ? ['--mcp-config', writeTurnSettings({ mcpServers: own })] : [];
|
|
224
233
|
const postured = machine ? withMachineMcp(perm, machineMcp) : perm;
|
|
225
|
-
|
|
234
|
+
let tail;
|
|
235
|
+
try {
|
|
236
|
+
tail = fenced ? withPersonalSettings(postured, personalSettings ?? personalClaudeSettings()) : postured;
|
|
237
|
+
} catch (error) {
|
|
238
|
+
releaseTurnSettings(ownArgs);
|
|
239
|
+
throw error;
|
|
240
|
+
}
|
|
241
|
+
a.push(...ownArgs, ...tail);
|
|
226
242
|
return a;
|
|
227
243
|
},
|
|
228
244
|
/** What `args` wrote for a turn — the fenced posture's settings file —
|
|
@@ -592,7 +592,7 @@ export function createAgentTurnExecution({
|
|
|
592
592
|
? {
|
|
593
593
|
mcpArgs: [...(claudeDisabled ? [] : ['--strict-mcp-config']), '--mcp-config', projectTools.mcpPath],
|
|
594
594
|
projectMcpServers: projectTools.mcpServers,
|
|
595
|
-
...(claudeDisabled ? { machineMcp: claudeDisabled } : {}),
|
|
595
|
+
...(claudeDisabled ? { machineMcp: claudeDisabled, mcpCheckout: source?.repoRoot ?? repoRoot } : {}),
|
|
596
596
|
}
|
|
597
597
|
: fencedMount ? { mcpArgs: fencedMount.args } : {}),
|
|
598
598
|
knowledgeDir: knowledgeDirFor(repoRoot),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flowviant",
|
|
3
|
-
"version": "0.125.
|
|
3
|
+
"version": "0.125.5",
|
|
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": {
|