fix(team): propagate managed runtime settings env
This commit is contained in:
parent
5403a2cea9
commit
77e08af03f
7 changed files with 51 additions and 0 deletions
|
|
@ -1252,6 +1252,7 @@ const STALL_CHECK_INTERVAL_MS = 10_000;
|
||||||
const STALL_WARNING_THRESHOLD_MS = 20_000;
|
const STALL_WARNING_THRESHOLD_MS = 20_000;
|
||||||
const APP_TEAM_RUNTIME_DISALLOWED_TOOLS =
|
const APP_TEAM_RUNTIME_DISALLOWED_TOOLS =
|
||||||
'TeamDelete,TodoWrite,TaskCreate,TaskUpdate,mcp__agent-teams__team_launch,mcp__agent-teams__team_stop';
|
'TeamDelete,TodoWrite,TaskCreate,TaskUpdate,mcp__agent-teams__team_launch,mcp__agent-teams__team_stop';
|
||||||
|
const CLAUDE_TEAM_RUNTIME_SETTINGS_PATH_ENV = 'CLAUDE_TEAM_RUNTIME_SETTINGS_PATH';
|
||||||
const TEAM_JSON_READ_TIMEOUT_MS = 5_000;
|
const TEAM_JSON_READ_TIMEOUT_MS = 5_000;
|
||||||
const TEAM_CONFIG_MAX_BYTES = 10 * 1024 * 1024;
|
const TEAM_CONFIG_MAX_BYTES = 10 * 1024 * 1024;
|
||||||
const TEAM_INBOX_MAX_BYTES = 2 * 1024 * 1024;
|
const TEAM_INBOX_MAX_BYTES = 2 * 1024 * 1024;
|
||||||
|
|
@ -2216,6 +2217,17 @@ function buildCodexCrossProviderSafeEnvPatch(env: NodeJS.ProcessEnv): NodeJS.Pro
|
||||||
return envPatch;
|
return envPatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function applyAppManagedRuntimeSettingsPathEnv(
|
||||||
|
env: NodeJS.ProcessEnv,
|
||||||
|
settingsPath: string | null
|
||||||
|
): void {
|
||||||
|
if (settingsPath) {
|
||||||
|
env[CLAUDE_TEAM_RUNTIME_SETTINGS_PATH_ENV] = settingsPath;
|
||||||
|
} else {
|
||||||
|
delete env[CLAUDE_TEAM_RUNTIME_SETTINGS_PATH_ENV];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interface TeamRuntimeAuthContext {
|
interface TeamRuntimeAuthContext {
|
||||||
teamName?: string;
|
teamName?: string;
|
||||||
authMaterialId?: string;
|
authMaterialId?: string;
|
||||||
|
|
@ -2238,6 +2250,7 @@ interface TeamRuntimeLaunchArgsPlan {
|
||||||
providerArgs: string[];
|
providerArgs: string[];
|
||||||
extraArgs: string[];
|
extraArgs: string[];
|
||||||
inheritedProviderArgs: string[];
|
inheritedProviderArgs: string[];
|
||||||
|
appManagedSettingsPath: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
type WorkspaceTrustProviderArgsResolver = (input: {
|
type WorkspaceTrustProviderArgsResolver = (input: {
|
||||||
|
|
@ -4259,6 +4272,7 @@ export class TeamProvisioningService {
|
||||||
providerArgs: rawProviderArgs,
|
providerArgs: rawProviderArgs,
|
||||||
extraArgs: rawExtraArgs,
|
extraArgs: rawExtraArgs,
|
||||||
inheritedProviderArgs: rawInheritedProviderArgs,
|
inheritedProviderArgs: rawInheritedProviderArgs,
|
||||||
|
appManagedSettingsPath: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -4313,6 +4327,7 @@ export class TeamProvisioningService {
|
||||||
providerArgs: splitProviderArgs.passthroughArgs,
|
providerArgs: splitProviderArgs.passthroughArgs,
|
||||||
extraArgs: splitExtraArgs.passthroughArgs,
|
extraArgs: splitExtraArgs.passthroughArgs,
|
||||||
inheritedProviderArgs: splitInheritedArgs.passthroughArgs,
|
inheritedProviderArgs: splitInheritedArgs.passthroughArgs,
|
||||||
|
appManagedSettingsPath: settingsBundle?.settingsPath ?? null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -14819,6 +14834,10 @@ export class TeamProvisioningService {
|
||||||
includeAnthropicHelper: providerId === 'anthropic',
|
includeAnthropicHelper: providerId === 'anthropic',
|
||||||
contextLabel: `Direct teammate restart (${input.configuredMember.name})`,
|
contextLabel: `Direct teammate restart (${input.configuredMember.name})`,
|
||||||
});
|
});
|
||||||
|
applyAppManagedRuntimeSettingsPathEnv(
|
||||||
|
provisioningEnv.env,
|
||||||
|
runtimeArgsPlan.appManagedSettingsPath
|
||||||
|
);
|
||||||
|
|
||||||
const runtimeArgs = mergeJsonSettingsArgs([
|
const runtimeArgs = mergeJsonSettingsArgs([
|
||||||
'--agent-id',
|
'--agent-id',
|
||||||
|
|
@ -15010,6 +15029,10 @@ export class TeamProvisioningService {
|
||||||
includeAnthropicHelper: providerId === 'anthropic',
|
includeAnthropicHelper: providerId === 'anthropic',
|
||||||
contextLabel: `Direct process teammate restart (${input.configuredMember.name})`,
|
contextLabel: `Direct process teammate restart (${input.configuredMember.name})`,
|
||||||
});
|
});
|
||||||
|
applyAppManagedRuntimeSettingsPathEnv(
|
||||||
|
provisioningEnv.env,
|
||||||
|
runtimeArgsPlan.appManagedSettingsPath
|
||||||
|
);
|
||||||
|
|
||||||
const runtimeArgs = mergeJsonSettingsArgs([
|
const runtimeArgs = mergeJsonSettingsArgs([
|
||||||
'--teammate-runtime',
|
'--teammate-runtime',
|
||||||
|
|
@ -20257,6 +20280,7 @@ export class TeamProvisioningService {
|
||||||
...runtimeArgsPlan.settingsArgs,
|
...runtimeArgsPlan.settingsArgs,
|
||||||
...runtimeArgsPlan.inheritedProviderArgs,
|
...runtimeArgsPlan.inheritedProviderArgs,
|
||||||
]);
|
]);
|
||||||
|
applyAppManagedRuntimeSettingsPathEnv(shellEnv, runtimeArgsPlan.appManagedSettingsPath);
|
||||||
const runtimeWarning = buildRuntimeLaunchWarning(request, shellEnv, {
|
const runtimeWarning = buildRuntimeLaunchWarning(request, shellEnv, {
|
||||||
geminiRuntimeAuth,
|
geminiRuntimeAuth,
|
||||||
promptSize,
|
promptSize,
|
||||||
|
|
@ -21575,6 +21599,7 @@ export class TeamProvisioningService {
|
||||||
emitProvisioningCheckpoint(run, 'Resolving cross-provider member launch args');
|
emitProvisioningCheckpoint(run, 'Resolving cross-provider member launch args');
|
||||||
launchArgs.push(...runtimeArgsPlan.inheritedProviderArgs);
|
launchArgs.push(...runtimeArgsPlan.inheritedProviderArgs);
|
||||||
const finalLaunchArgs = mergeJsonSettingsArgs(launchArgs);
|
const finalLaunchArgs = mergeJsonSettingsArgs(launchArgs);
|
||||||
|
applyAppManagedRuntimeSettingsPathEnv(shellEnv, runtimeArgsPlan.appManagedSettingsPath);
|
||||||
const runtimeWarning = buildRuntimeLaunchWarning(request, shellEnv, {
|
const runtimeWarning = buildRuntimeLaunchWarning(request, shellEnv, {
|
||||||
geminiRuntimeAuth,
|
geminiRuntimeAuth,
|
||||||
promptSize,
|
promptSize,
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import type { TeamProviderId } from '@shared/types';
|
||||||
const DIRECT_TMUX_RESTART_ENV_KEYS = [
|
const DIRECT_TMUX_RESTART_ENV_KEYS = [
|
||||||
'CLAUDE_CONFIG_DIR',
|
'CLAUDE_CONFIG_DIR',
|
||||||
'CLAUDE_TEAM_CONTROL_URL',
|
'CLAUDE_TEAM_CONTROL_URL',
|
||||||
|
'CLAUDE_TEAM_RUNTIME_SETTINGS_PATH',
|
||||||
'CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST',
|
'CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST',
|
||||||
'CLAUDE_CODE_USE_OPENAI',
|
'CLAUDE_CODE_USE_OPENAI',
|
||||||
'CLAUDE_CODE_USE_BEDROCK',
|
'CLAUDE_CODE_USE_BEDROCK',
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@ describe('TeamProvisioningDirectRestart', () => {
|
||||||
CLAUDE_CODE_ENTRY_PROVIDER: 'gemini',
|
CLAUDE_CODE_ENTRY_PROVIDER: 'gemini',
|
||||||
CLAUDE_CODE_CODEX_BACKEND: 'codex-native',
|
CLAUDE_CODE_CODEX_BACKEND: 'codex-native',
|
||||||
CLAUDE_CODE_CODEX_FORCED_LOGIN_METHOD: 'chatgpt',
|
CLAUDE_CODE_CODEX_FORCED_LOGIN_METHOD: 'chatgpt',
|
||||||
|
CLAUDE_TEAM_RUNTIME_SETTINGS_PATH: '/tmp/runtime-settings.json',
|
||||||
},
|
},
|
||||||
'codex'
|
'codex'
|
||||||
);
|
);
|
||||||
|
|
@ -74,6 +75,7 @@ describe('TeamProvisioningDirectRestart', () => {
|
||||||
expect(assignments).toContain("CLAUDE_CODE_ENTRY_PROVIDER='codex'");
|
expect(assignments).toContain("CLAUDE_CODE_ENTRY_PROVIDER='codex'");
|
||||||
expect(assignments).toContain("CLAUDE_CODE_CODEX_BACKEND='codex-native'");
|
expect(assignments).toContain("CLAUDE_CODE_CODEX_BACKEND='codex-native'");
|
||||||
expect(assignments).toContain("CLAUDE_CODE_CODEX_FORCED_LOGIN_METHOD='chatgpt'");
|
expect(assignments).toContain("CLAUDE_CODE_CODEX_FORCED_LOGIN_METHOD='chatgpt'");
|
||||||
|
expect(assignments).toContain("CLAUDE_TEAM_RUNTIME_SETTINGS_PATH='/tmp/runtime-settings.json'");
|
||||||
expect(assignments).toContain("CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST='1'");
|
expect(assignments).toContain("CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST='1'");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -531,6 +531,7 @@ describe('TeamProvisioningService member MCP config safe e2e', () => {
|
||||||
settingsArgs: string[];
|
settingsArgs: string[];
|
||||||
extraArgs: string[];
|
extraArgs: string[];
|
||||||
inheritedProviderArgs: string[];
|
inheritedProviderArgs: string[];
|
||||||
|
appManagedSettingsPath: string | null;
|
||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
).buildTeamRuntimeLaunchArgsPlan = vi.fn(async () => ({
|
).buildTeamRuntimeLaunchArgsPlan = vi.fn(async () => ({
|
||||||
|
|
@ -540,6 +541,7 @@ describe('TeamProvisioningService member MCP config safe e2e', () => {
|
||||||
settingsArgs: [],
|
settingsArgs: [],
|
||||||
extraArgs: [],
|
extraArgs: [],
|
||||||
inheritedProviderArgs: [],
|
inheritedProviderArgs: [],
|
||||||
|
appManagedSettingsPath: null,
|
||||||
}));
|
}));
|
||||||
(
|
(
|
||||||
svc as unknown as { updateDirectTmuxRestartMemberConfig: () => Promise<void> }
|
svc as unknown as { updateDirectTmuxRestartMemberConfig: () => Promise<void> }
|
||||||
|
|
|
||||||
|
|
@ -15538,6 +15538,7 @@ describe('TeamProvisioningService', () => {
|
||||||
settingsArgs: [],
|
settingsArgs: [],
|
||||||
extraArgs: [],
|
extraArgs: [],
|
||||||
inheritedProviderArgs: [],
|
inheritedProviderArgs: [],
|
||||||
|
appManagedSettingsPath: null,
|
||||||
}));
|
}));
|
||||||
(svc as any).materializeDirectProcessNativeBootstrapContext = vi.fn(async () => ({}));
|
(svc as any).materializeDirectProcessNativeBootstrapContext = vi.fn(async () => ({}));
|
||||||
(svc as any).updateDirectTmuxRestartMemberConfig = vi.fn(async () => {});
|
(svc as any).updateDirectTmuxRestartMemberConfig = vi.fn(async () => {});
|
||||||
|
|
@ -15637,6 +15638,7 @@ describe('TeamProvisioningService', () => {
|
||||||
settingsArgs: [],
|
settingsArgs: [],
|
||||||
extraArgs: [],
|
extraArgs: [],
|
||||||
inheritedProviderArgs: [],
|
inheritedProviderArgs: [],
|
||||||
|
appManagedSettingsPath: null,
|
||||||
}));
|
}));
|
||||||
(svc as any).materializeDirectProcessNativeBootstrapContext = vi.fn(async () => ({}));
|
(svc as any).materializeDirectProcessNativeBootstrapContext = vi.fn(async () => ({}));
|
||||||
(svc as any).updateDirectTmuxRestartMemberConfig = vi.fn(async () => {});
|
(svc as any).updateDirectTmuxRestartMemberConfig = vi.fn(async () => {});
|
||||||
|
|
|
||||||
|
|
@ -3566,6 +3566,7 @@ describe('TeamProvisioningService prepare/auth behavior', () => {
|
||||||
|
|
||||||
expect(result.settingsArgs[0]).toBe('--settings');
|
expect(result.settingsArgs[0]).toBe('--settings');
|
||||||
expect(result.inheritedProviderArgs).toEqual([]);
|
expect(result.inheritedProviderArgs).toEqual([]);
|
||||||
|
expect(result.appManagedSettingsPath).toBe(result.settingsArgs[1]);
|
||||||
const settings = JSON.parse(fs.readFileSync(result.settingsArgs[1], 'utf8'));
|
const settings = JSON.parse(fs.readFileSync(result.settingsArgs[1], 'utf8'));
|
||||||
expect(settings.codex.forced_login_method).toBe('chatgpt');
|
expect(settings.codex.forced_login_method).toBe('chatgpt');
|
||||||
});
|
});
|
||||||
|
|
@ -3651,6 +3652,7 @@ describe('TeamProvisioningService prepare/auth behavior', () => {
|
||||||
|
|
||||||
expect(result.settingsArgs).toEqual([]);
|
expect(result.settingsArgs).toEqual([]);
|
||||||
expect(result.inheritedProviderArgs).toEqual(inheritedProviderArgs);
|
expect(result.inheritedProviderArgs).toEqual(inheritedProviderArgs);
|
||||||
|
expect(result.appManagedSettingsPath).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('coalesces inherited JSON settings into Anthropic helper settings without keeping helper path args', async () => {
|
it('coalesces inherited JSON settings into Anthropic helper settings without keeping helper path args', async () => {
|
||||||
|
|
@ -3685,6 +3687,7 @@ describe('TeamProvisioningService prepare/auth behavior', () => {
|
||||||
expect(result.settingsArgs[0]).toBe('--settings');
|
expect(result.settingsArgs[0]).toBe('--settings');
|
||||||
expect(result.settingsArgs[1]).toContain(helperDir);
|
expect(result.settingsArgs[1]).toContain(helperDir);
|
||||||
expect(result.settingsArgs[1]).not.toBe(helperSettingsPath);
|
expect(result.settingsArgs[1]).not.toBe(helperSettingsPath);
|
||||||
|
expect(result.appManagedSettingsPath).toBe(result.settingsArgs[1]);
|
||||||
const settings = JSON.parse(fs.readFileSync(result.settingsArgs[1], 'utf8'));
|
const settings = JSON.parse(fs.readFileSync(result.settingsArgs[1], 'utf8'));
|
||||||
expect(settings.apiKeyHelper).toBe(`'${path.join(helperDir, 'helper.sh')}'`);
|
expect(settings.apiKeyHelper).toBe(`'${path.join(helperDir, 'helper.sh')}'`);
|
||||||
expect(settings.codex.forced_login_method).toBe('chatgpt');
|
expect(settings.codex.forced_login_method).toBe('chatgpt');
|
||||||
|
|
|
||||||
|
|
@ -178,6 +178,16 @@ function readRuntimeSettingsFromLaunchArgs(callIndex = 0): Record<string, unknow
|
||||||
return JSON.parse(fs.readFileSync(settingsValue, 'utf8')) as Record<string, unknown>;
|
return JSON.parse(fs.readFileSync(settingsValue, 'utf8')) as Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function readRuntimeSettingsPathFromLaunchArgs(callIndex = 0): string {
|
||||||
|
const args = vi.mocked(spawnCli).mock.calls[callIndex]?.[1] as string[] | undefined;
|
||||||
|
const settingsFlagIndex = args?.indexOf('--settings') ?? -1;
|
||||||
|
const settingsValue = settingsFlagIndex >= 0 ? args?.[settingsFlagIndex + 1] : null;
|
||||||
|
if (!settingsValue || settingsValue.trim().startsWith('{')) {
|
||||||
|
throw new Error('Failed to extract runtime settings path from spawn args');
|
||||||
|
}
|
||||||
|
return settingsValue;
|
||||||
|
}
|
||||||
|
|
||||||
function registerNoopOpenCodeRuntimeAdapter(svc: TeamProvisioningService): void {
|
function registerNoopOpenCodeRuntimeAdapter(svc: TeamProvisioningService): void {
|
||||||
svc.setRuntimeAdapterRegistry(
|
svc.setRuntimeAdapterRegistry(
|
||||||
new TeamRuntimeAdapterRegistry([
|
new TeamRuntimeAdapterRegistry([
|
||||||
|
|
@ -564,6 +574,9 @@ describe('TeamProvisioningService prompt content (solo mode discipline)', () =>
|
||||||
expect(extractBootstrapSpec().members).toEqual([
|
expect(extractBootstrapSpec().members).toEqual([
|
||||||
expect.objectContaining({ name: 'alice', provider: 'codex' }),
|
expect.objectContaining({ name: 'alice', provider: 'codex' }),
|
||||||
]);
|
]);
|
||||||
|
const settingsPath = readRuntimeSettingsPathFromLaunchArgs();
|
||||||
|
const launchEnv = vi.mocked(spawnCli).mock.calls[0]?.[2]?.env as NodeJS.ProcessEnv;
|
||||||
|
expect(launchEnv.CLAUDE_TEAM_RUNTIME_SETTINGS_PATH).toBe(settingsPath);
|
||||||
const settings = readRuntimeSettingsFromLaunchArgs();
|
const settings = readRuntimeSettingsFromLaunchArgs();
|
||||||
expect((settings.codex as Record<string, unknown>).forced_login_method).toBe('chatgpt');
|
expect((settings.codex as Record<string, unknown>).forced_login_method).toBe('chatgpt');
|
||||||
|
|
||||||
|
|
@ -1260,6 +1273,9 @@ describe('TeamProvisioningService prompt content (solo mode discipline)', () =>
|
||||||
expect(extractBootstrapSpec().members).toEqual([
|
expect(extractBootstrapSpec().members).toEqual([
|
||||||
expect.objectContaining({ name: 'alice', provider: 'codex' }),
|
expect.objectContaining({ name: 'alice', provider: 'codex' }),
|
||||||
]);
|
]);
|
||||||
|
const settingsPath = readRuntimeSettingsPathFromLaunchArgs();
|
||||||
|
const launchEnv = vi.mocked(spawnCli).mock.calls[0]?.[2]?.env as NodeJS.ProcessEnv;
|
||||||
|
expect(launchEnv.CLAUDE_TEAM_RUNTIME_SETTINGS_PATH).toBe(settingsPath);
|
||||||
const settings = readRuntimeSettingsFromLaunchArgs();
|
const settings = readRuntimeSettingsFromLaunchArgs();
|
||||||
expect((settings.codex as Record<string, unknown>).forced_login_method).toBe('chatgpt');
|
expect((settings.codex as Record<string, unknown>).forced_login_method).toBe('chatgpt');
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue