forked from ultraworkers/claw-code
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathspawnUtils.ts
More file actions
146 lines (132 loc) · 5.08 KB
/
Copy pathspawnUtils.ts
File metadata and controls
146 lines (132 loc) · 5.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
/**
* Shared utilities for spawning teammates across different backends.
*/
import {
getChromeFlagOverride,
getFlagSettingsPath,
getInlinePlugins,
getMainLoopModelOverride,
getSessionBypassPermissionsMode,
} from '../../bootstrap/state.js'
import { quote } from '../bash/shellQuote.js'
import { isInBundledMode } from '../bundledMode.js'
import type { PermissionMode } from '../permissions/PermissionMode.js'
import { getTeammateModeFromSnapshot } from './backends/teammateModeSnapshot.js'
import { TEAMMATE_COMMAND_ENV_VAR } from './constants.js'
/**
* Gets the command to use for spawning teammate processes.
* Uses TEAMMATE_COMMAND_ENV_VAR if set, otherwise falls back to the
* current process executable path.
*/
export function getTeammateCommand(): string {
if (process.env[TEAMMATE_COMMAND_ENV_VAR]) {
return process.env[TEAMMATE_COMMAND_ENV_VAR]
}
return isInBundledMode() ? process.execPath : process.argv[1]!
}
/**
* Builds CLI flags to propagate from the current session to spawned teammates.
* This ensures teammates inherit important settings like permission mode,
* model selection, and plugin configuration from their parent.
*
* @param options.planModeRequired - If true, don't inherit bypass permissions (plan mode takes precedence)
* @param options.permissionMode - Permission mode to propagate
*/
export function buildInheritedCliFlags(options?: {
planModeRequired?: boolean
permissionMode?: PermissionMode
}): string {
const flags: string[] = []
const { planModeRequired, permissionMode } = options || {}
// Propagate permission mode to teammates, but NOT if plan mode is required
// Plan mode takes precedence over bypass permissions for safety
if (planModeRequired) {
// Don't inherit bypass permissions when plan mode is required
} else if (
permissionMode === 'bypassPermissions' ||
getSessionBypassPermissionsMode()
) {
flags.push('--dangerously-skip-permissions')
} else if (permissionMode === 'acceptEdits') {
flags.push('--permission-mode acceptEdits')
}
// Propagate --model if explicitly set via CLI
const modelOverride = getMainLoopModelOverride()
if (modelOverride) {
flags.push(`--model ${quote([modelOverride])}`)
}
// Propagate --settings if set via CLI
const settingsPath = getFlagSettingsPath()
if (settingsPath) {
flags.push(`--settings ${quote([settingsPath])}`)
}
// Propagate --plugin-dir for each inline plugin
const inlinePlugins = getInlinePlugins()
for (const pluginDir of inlinePlugins) {
flags.push(`--plugin-dir ${quote([pluginDir])}`)
}
// Propagate --teammate-mode so tmux teammates use the same mode as leader
const sessionMode = getTeammateModeFromSnapshot()
flags.push(`--teammate-mode ${sessionMode}`)
// Propagate --chrome / --no-chrome if explicitly set on the CLI
const chromeFlagOverride = getChromeFlagOverride()
if (chromeFlagOverride === true) {
flags.push('--chrome')
} else if (chromeFlagOverride === false) {
flags.push('--no-chrome')
}
return flags.join(' ')
}
/**
* Environment variables that must be explicitly forwarded to tmux-spawned
* teammates. Tmux may start a new login shell that doesn't inherit the
* parent's env, so we forward any that are set in the current process.
*/
const TEAMMATE_ENV_VARS = [
// API provider selection — without these, teammates default to firstParty
// and send requests to the wrong endpoint (GitHub issue #23561)
'CLAUDE_CODE_USE_BEDROCK',
'CLAUDE_CODE_USE_VERTEX',
'CLAUDE_CODE_USE_FOUNDRY',
// Custom API endpoint
'ANTHROPIC_BASE_URL',
// Config directory override
'CLAUDE_CONFIG_DIR',
// CCR marker — teammates need this for CCR-aware code paths. Auth finds
// its own way via /home/claude/.claude/remote/.oauth_token regardless;
// the FD env var wouldn't help (pipe FDs don't cross tmux).
'CLAUDE_CODE_REMOTE',
// Auto-memory gate (memdir/paths.ts) checks REMOTE && !MEMORY_DIR to
// disable memory on ephemeral CCR filesystems. Forwarding REMOTE alone
// would flip teammates to memory-off when the parent has it on.
'CLAUDE_CODE_REMOTE_MEMORY_DIR',
// Upstream proxy — the parent's MITM relay is reachable from teammates
// (same container network). Forward the proxy vars so teammates route
// customer-configured upstream traffic through the relay for credential
// injection. Without these, teammates bypass the proxy entirely.
'HTTPS_PROXY',
'https_proxy',
'HTTP_PROXY',
'http_proxy',
'NO_PROXY',
'no_proxy',
'SSL_CERT_FILE',
'NODE_EXTRA_CA_CERTS',
'REQUESTS_CA_BUNDLE',
'CURL_CA_BUNDLE',
] as const
/**
* Builds the `env KEY=VALUE ...` string for teammate spawn commands.
* Always includes CLAUDECODE=1 and CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1,
* plus any provider/config env vars that are set in the current process.
*/
export function buildInheritedEnvVars(): string {
const envVars = ['CLAUDECODE=1', 'CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1']
for (const key of TEAMMATE_ENV_VARS) {
const value = process.env[key]
if (value !== undefined && value !== '') {
envVars.push(`${key}=${quote([value])}`)
}
}
return envVars.join(' ')
}