feat(team): warn on large native bootstrap context
This commit is contained in:
parent
eb0dfcc452
commit
da1e5dfc81
3 changed files with 151 additions and 11 deletions
|
|
@ -171,6 +171,7 @@ import {
|
|||
} from './bootstrap/BootstrapProofValidation';
|
||||
import {
|
||||
buildNativeAppManagedBootstrapSpecs,
|
||||
buildNativeAppManagedBootstrapSpecsWithDiagnostics,
|
||||
type NativeAppManagedBootstrapSpec,
|
||||
} from './bootstrap/NativeAppManagedBootstrapContextBuilder';
|
||||
import {
|
||||
|
|
@ -18911,16 +18912,30 @@ export class TeamProvisioningService {
|
|||
'Building deterministic create bootstrap spec',
|
||||
`expectedMembers=${effectiveMemberSpecs.length}`
|
||||
);
|
||||
const nativeAppManagedBootstrapByMember = await buildNativeAppManagedBootstrapSpecs({
|
||||
const nativeBootstrapBuild = await buildNativeAppManagedBootstrapSpecsWithDiagnostics({
|
||||
teamName: request.teamName,
|
||||
cwd: request.cwd,
|
||||
members: effectiveMemberSpecs,
|
||||
});
|
||||
if (nativeBootstrapBuild.diagnostics.warning) {
|
||||
run.progress = {
|
||||
...run.progress,
|
||||
warnings: mergeProvisioningWarnings(
|
||||
run.progress.warnings,
|
||||
nativeBootstrapBuild.diagnostics.warning
|
||||
),
|
||||
};
|
||||
emitProvisioningCheckpoint(
|
||||
run,
|
||||
'Native bootstrap startup context is large',
|
||||
nativeBootstrapBuild.diagnostics.warning
|
||||
);
|
||||
}
|
||||
const bootstrapSpec = buildDeterministicCreateBootstrapSpec(
|
||||
runId,
|
||||
request,
|
||||
effectiveMemberSpecs,
|
||||
nativeAppManagedBootstrapByMember
|
||||
nativeBootstrapBuild.specs
|
||||
);
|
||||
emitProvisioningCheckpoint(run, 'Writing deterministic bootstrap spec file');
|
||||
bootstrapSpecPath = await writeDeterministicBootstrapSpecFile(bootstrapSpec);
|
||||
|
|
@ -20189,15 +20204,30 @@ export class TeamProvisioningService {
|
|||
'Building deterministic launch bootstrap spec',
|
||||
`expectedMembers=${effectiveMemberSpecs.length}`
|
||||
);
|
||||
const nativeBootstrapBuild = await buildNativeAppManagedBootstrapSpecsWithDiagnostics({
|
||||
teamName: request.teamName,
|
||||
cwd: request.cwd,
|
||||
members: effectiveMemberSpecs,
|
||||
});
|
||||
if (nativeBootstrapBuild.diagnostics.warning) {
|
||||
run.progress = {
|
||||
...run.progress,
|
||||
warnings: mergeProvisioningWarnings(
|
||||
run.progress.warnings,
|
||||
nativeBootstrapBuild.diagnostics.warning
|
||||
),
|
||||
};
|
||||
emitProvisioningCheckpoint(
|
||||
run,
|
||||
'Native bootstrap startup context is large',
|
||||
nativeBootstrapBuild.diagnostics.warning
|
||||
);
|
||||
}
|
||||
const bootstrapSpec = buildDeterministicLaunchBootstrapSpec(
|
||||
runId,
|
||||
request,
|
||||
effectiveMemberSpecs,
|
||||
await buildNativeAppManagedBootstrapSpecs({
|
||||
teamName: request.teamName,
|
||||
cwd: request.cwd,
|
||||
members: effectiveMemberSpecs,
|
||||
})
|
||||
nativeBootstrapBuild.specs
|
||||
);
|
||||
emitProvisioningCheckpoint(run, 'Writing deterministic bootstrap spec file');
|
||||
bootstrapSpecPath = await writeDeterministicBootstrapSpecFile(bootstrapSpec);
|
||||
|
|
|
|||
|
|
@ -16,9 +16,23 @@ export interface NativeAppManagedBootstrapSpec {
|
|||
generatedAt: string;
|
||||
}
|
||||
|
||||
export interface NativeAppManagedBootstrapBuildDiagnostics {
|
||||
nativeMemberCount: number;
|
||||
totalContextChars: number;
|
||||
totalContextLimitChars: number;
|
||||
warning: string | null;
|
||||
}
|
||||
|
||||
export interface NativeAppManagedBootstrapBuildResult {
|
||||
specs: Map<string, NativeAppManagedBootstrapSpec>;
|
||||
diagnostics: NativeAppManagedBootstrapBuildDiagnostics;
|
||||
}
|
||||
|
||||
const MAX_NATIVE_BOOTSTRAP_BRIEFING_CHARS = 18_000;
|
||||
const MAX_NATIVE_BOOTSTRAP_CONTEXT_CHARS = 24_000;
|
||||
const MAX_NATIVE_BOOTSTRAP_TOTAL_CONTEXT_CHARS = 96_000;
|
||||
export const MAX_NATIVE_BOOTSTRAP_TOTAL_CONTEXT_CHARS = 256_000;
|
||||
const NATIVE_BOOTSTRAP_LARGE_ROSTER_MEMBER_COUNT = 7;
|
||||
const NATIVE_BOOTSTRAP_NEAR_LIMIT_RATIO = 0.85;
|
||||
|
||||
export function isNativeAppManagedBootstrapProvider(providerId?: TeamProviderId): boolean {
|
||||
return providerId == null || providerId === 'anthropic' || providerId === 'codex';
|
||||
|
|
@ -80,6 +94,45 @@ function buildContextText(params: {
|
|||
);
|
||||
}
|
||||
|
||||
function formatCompactChars(value: number): string {
|
||||
if (!Number.isFinite(value)) {
|
||||
return 'unknown';
|
||||
}
|
||||
if (value >= 1000) {
|
||||
return `${Math.round(value / 1000)}k chars`;
|
||||
}
|
||||
return `${Math.max(0, Math.round(value))} chars`;
|
||||
}
|
||||
|
||||
function buildNativeBootstrapWarning(params: {
|
||||
nativeMemberCount: number;
|
||||
totalContextChars: number;
|
||||
totalContextLimitChars: number;
|
||||
}): string | null {
|
||||
if (params.nativeMemberCount === 0) {
|
||||
return null;
|
||||
}
|
||||
const ratio =
|
||||
params.totalContextLimitChars > 0
|
||||
? params.totalContextChars / params.totalContextLimitChars
|
||||
: 0;
|
||||
const isLargeNativeRoster =
|
||||
params.nativeMemberCount >= NATIVE_BOOTSTRAP_LARGE_ROSTER_MEMBER_COUNT;
|
||||
const isNearLimit = ratio >= NATIVE_BOOTSTRAP_NEAR_LIMIT_RATIO;
|
||||
if (!isLargeNativeRoster && !isNearLimit) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const usage = `${formatCompactChars(params.totalContextChars)} / ${formatCompactChars(
|
||||
params.totalContextLimitChars
|
||||
)}`;
|
||||
const percent = `${Math.round(ratio * 100)}%`;
|
||||
const rosterHint = `${params.nativeMemberCount} native app-managed member${
|
||||
params.nativeMemberCount === 1 ? '' : 's'
|
||||
}`;
|
||||
return `Large native team startup context: ${usage} (${percent}) across ${rosterHint}. Launch can continue, but if bootstrap confirmation is slow or fails, reduce native member count or use OpenCode for secondary members.`;
|
||||
}
|
||||
|
||||
function buildLocalNativeMemberBriefing(params: {
|
||||
teamName: string;
|
||||
cwd: string;
|
||||
|
|
@ -114,6 +167,14 @@ export async function buildNativeAppManagedBootstrapSpecs(params: {
|
|||
cwd: string;
|
||||
members: TeamCreateRequest['members'];
|
||||
}): Promise<Map<string, NativeAppManagedBootstrapSpec>> {
|
||||
return (await buildNativeAppManagedBootstrapSpecsWithDiagnostics(params)).specs;
|
||||
}
|
||||
|
||||
export async function buildNativeAppManagedBootstrapSpecsWithDiagnostics(params: {
|
||||
teamName: string;
|
||||
cwd: string;
|
||||
members: TeamCreateRequest['members'];
|
||||
}): Promise<NativeAppManagedBootstrapBuildResult> {
|
||||
const controller = createController({
|
||||
teamName: params.teamName,
|
||||
claudeDir: getClaudeBasePath(),
|
||||
|
|
@ -121,12 +182,14 @@ export async function buildNativeAppManagedBootstrapSpecs(params: {
|
|||
});
|
||||
const result = new Map<string, NativeAppManagedBootstrapSpec>();
|
||||
let totalContextChars = 0;
|
||||
let nativeMemberCount = 0;
|
||||
|
||||
for (const member of params.members) {
|
||||
const providerId = normalizeOptionalTeamProviderId(member.providerId) ?? 'anthropic';
|
||||
if (!isNativeAppManagedBootstrapProvider(providerId)) {
|
||||
continue;
|
||||
}
|
||||
nativeMemberCount += 1;
|
||||
|
||||
let briefing: string;
|
||||
try {
|
||||
|
|
@ -182,5 +245,16 @@ export async function buildNativeAppManagedBootstrapSpecs(params: {
|
|||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
const diagnostics: NativeAppManagedBootstrapBuildDiagnostics = {
|
||||
nativeMemberCount,
|
||||
totalContextChars,
|
||||
totalContextLimitChars: MAX_NATIVE_BOOTSTRAP_TOTAL_CONTEXT_CHARS,
|
||||
warning: buildNativeBootstrapWarning({
|
||||
nativeMemberCount,
|
||||
totalContextChars,
|
||||
totalContextLimitChars: MAX_NATIVE_BOOTSTRAP_TOTAL_CONTEXT_CHARS,
|
||||
}),
|
||||
};
|
||||
|
||||
return { specs: result, diagnostics };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|||
|
||||
import {
|
||||
buildNativeAppManagedBootstrapSpecs,
|
||||
buildNativeAppManagedBootstrapSpecsWithDiagnostics,
|
||||
hashNativeBootstrapText,
|
||||
MAX_NATIVE_BOOTSTRAP_TOTAL_CONTEXT_CHARS,
|
||||
} from '../../../../src/main/services/team/bootstrap/NativeAppManagedBootstrapContextBuilder';
|
||||
import { TeamMembersMetaStore } from '../../../../src/main/services/team/TeamMembersMetaStore';
|
||||
import { TeamMetaStore } from '../../../../src/main/services/team/TeamMetaStore';
|
||||
|
|
@ -99,6 +101,40 @@ describe('NativeAppManagedBootstrapContextBuilder', () => {
|
|||
expect(alice?.contextHash).toBe(hashNativeBootstrapText(alice?.contextText ?? ''));
|
||||
});
|
||||
|
||||
it('warns but still builds for large native rosters below the aggregate budget', async () => {
|
||||
await new TeamMetaStore().writeMeta('large-warning-native-team', {
|
||||
cwd: '/tmp/workspace',
|
||||
providerId: 'anthropic',
|
||||
model: 'claude-opus-4-6',
|
||||
createdAt: Date.now(),
|
||||
});
|
||||
await new TeamMembersMetaStore().writeMembers(
|
||||
'large-warning-native-team',
|
||||
Array.from({ length: 7 }, (_, index) => ({
|
||||
name: `member-${index}`,
|
||||
providerId: 'anthropic' as const,
|
||||
role: 'Developer',
|
||||
}))
|
||||
);
|
||||
|
||||
const result = await buildNativeAppManagedBootstrapSpecsWithDiagnostics({
|
||||
teamName: 'large-warning-native-team',
|
||||
cwd: '/tmp/workspace',
|
||||
members: Array.from({ length: 7 }, (_, index) => ({
|
||||
name: `member-${index}`,
|
||||
providerId: 'anthropic' as const,
|
||||
role: 'Developer',
|
||||
})),
|
||||
});
|
||||
|
||||
expect(result.specs.size).toBe(7);
|
||||
expect(result.diagnostics.nativeMemberCount).toBe(7);
|
||||
expect(result.diagnostics.totalContextLimitChars).toBe(
|
||||
MAX_NATIVE_BOOTSTRAP_TOTAL_CONTEXT_CHARS
|
||||
);
|
||||
expect(result.diagnostics.warning).toMatch(/Large native team startup context/);
|
||||
});
|
||||
|
||||
it('fails closed when aggregate native context budget is exceeded', async () => {
|
||||
const hugeRole = 'x'.repeat(40_000);
|
||||
await new TeamMetaStore().writeMeta('large-native-team', {
|
||||
|
|
@ -109,7 +145,7 @@ describe('NativeAppManagedBootstrapContextBuilder', () => {
|
|||
});
|
||||
await new TeamMembersMetaStore().writeMembers(
|
||||
'large-native-team',
|
||||
Array.from({ length: 8 }, (_, index) => ({
|
||||
Array.from({ length: 16 }, (_, index) => ({
|
||||
name: `member-${index}`,
|
||||
providerId: 'anthropic' as const,
|
||||
role: hugeRole,
|
||||
|
|
@ -120,7 +156,7 @@ describe('NativeAppManagedBootstrapContextBuilder', () => {
|
|||
buildNativeAppManagedBootstrapSpecs({
|
||||
teamName: 'large-native-team',
|
||||
cwd: '/tmp/workspace',
|
||||
members: Array.from({ length: 8 }, (_, index) => ({
|
||||
members: Array.from({ length: 16 }, (_, index) => ({
|
||||
name: `member-${index}`,
|
||||
providerId: 'anthropic' as const,
|
||||
role: hugeRole,
|
||||
|
|
|
|||
Loading…
Reference in a new issue