diff --git a/scripts/dev-with-runtime.mjs b/scripts/dev-with-runtime.mjs index 4c6d57df..0076b9c1 100644 --- a/scripts/dev-with-runtime.mjs +++ b/scripts/dev-with-runtime.mjs @@ -1,84 +1,90 @@ #!/usr/bin/env node -import fs from 'node:fs' -import path from 'node:path' -import { spawnSync } from 'node:child_process' -import { fileURLToPath } from 'node:url' +import fs from 'node:fs'; +import path from 'node:path'; +import { spawnSync } from 'node:child_process'; +import { fileURLToPath } from 'node:url'; function runOrExit(cmd, args, options = {}) { const result = spawnSync(cmd, args, { stdio: 'inherit', ...options, - }) + }); if (result.error) { - console.error(`Failed to run ${cmd}: ${result.error.message}`) - process.exit(1) + console.error(`Failed to run ${cmd}: ${result.error.message}`); + process.exit(1); } if (result.status !== 0) { - process.exit(result.status ?? 1) + process.exit(result.status ?? 1); } } function readPackageManagerCommand(repoRoot) { - const packageJsonPath = path.join(repoRoot, 'package.json') - const rawPackageJson = fs.readFileSync(packageJsonPath, 'utf8') - const packageJson = JSON.parse(rawPackageJson) - const rawPackageManager = packageJson.packageManager + const packageJsonPath = path.join(repoRoot, 'package.json'); + const rawPackageJson = fs.readFileSync(packageJsonPath, 'utf8'); + const packageJson = JSON.parse(rawPackageJson); + const rawPackageManager = packageJson.packageManager; if (typeof rawPackageManager !== 'string' || rawPackageManager.trim().length === 0) { - return 'pnpm' + return 'pnpm'; } - const [packageManagerName] = rawPackageManager.trim().split('@', 1) + const [packageManagerName] = rawPackageManager.trim().split('@', 1); if (!packageManagerName) { - return 'pnpm' + return 'pnpm'; } - return packageManagerName + return packageManagerName; } -const scriptDir = path.dirname(fileURLToPath(import.meta.url)) -const uiRepoRoot = path.resolve(scriptDir, '..') +const scriptDir = path.dirname(fileURLToPath(import.meta.url)); +const uiRepoRoot = path.resolve(scriptDir, '..'); // Keep the dev runtime target explicit. This workspace can contain multiple // sibling repos with the same package name, so auto-discovery is ambiguous and // can silently point the UI at the wrong runtime after branch switches. -const runtimeRepoRoot = process.env.CLAUDE_DEV_RUNTIME_ROOT?.trim() +const runtimeRepoRoot = process.env.CLAUDE_DEV_RUNTIME_ROOT?.trim(); if (!runtimeRepoRoot) { console.error( 'CLAUDE_DEV_RUNTIME_ROOT is required for pnpm dev. ' + - 'Point it at the runtime repo root you want the UI to use in dev.', - ) - process.exit(1) + 'Point it at the runtime repo root you want the UI to use in dev.' + ); + process.exit(1); } -const runtimePackageJsonPath = path.join(runtimeRepoRoot, 'package.json') +const runtimePackageJsonPath = path.join(runtimeRepoRoot, 'package.json'); if (!fs.existsSync(runtimePackageJsonPath)) { - console.error( - `CLAUDE_DEV_RUNTIME_ROOT does not look like a repo root: ${runtimeRepoRoot}`, - ) - process.exit(1) + console.error(`CLAUDE_DEV_RUNTIME_ROOT does not look like a repo root: ${runtimeRepoRoot}`); + process.exit(1); } -const runtimePackageManager = readPackageManagerCommand(runtimeRepoRoot) +const runtimePackageManager = readPackageManagerCommand(runtimeRepoRoot); if (process.argv.includes('--print-runtime-path')) { - process.stdout.write(`${runtimeRepoRoot}\n`) - process.exit(0) + process.stdout.write(`${runtimeRepoRoot}\n`); + process.exit(0); } // Respect the runtime repo's own package manager. The UI repo uses pnpm, but // the runtime may legitimately be a Bun workspace, and forcing pnpm there can // fail before the build even starts. -runOrExit(runtimePackageManager, ['run', 'build:dev'], { cwd: runtimeRepoRoot }) +runOrExit(runtimePackageManager, ['run', 'build:dev'], { cwd: runtimeRepoRoot }); + +const runtimeCliPath = path.join(runtimeRepoRoot, 'cli-dev'); +const uiEnv = { + ...process.env, + // Dev-only free-code runtime override. Keep it separate from the generic + // CLAUDE_CLI_PATH override so switching the app into Claude CLI mode still + // resolves the real official binary instead of this local cli-dev shim. + CLAUDE_FREE_CODE_CLI_PATH: runtimeCliPath, +}; +// If the parent shell exported a stale generic override, do not let it leak +// into the Electron main process. Claude mode must resolve the real binary. +delete uiEnv.CLAUDE_CLI_PATH; -const runtimeCliPath = path.join(runtimeRepoRoot, 'cli-dev') runOrExit('pnpm', ['run', 'dev:ui'], { cwd: uiRepoRoot, - env: { - ...process.env, - CLAUDE_CLI_PATH: runtimeCliPath, - }, -}) + env: uiEnv, +}); diff --git a/src/main/services/team/ClaudeBinaryResolver.ts b/src/main/services/team/ClaudeBinaryResolver.ts index 25ac3874..dd99a69a 100644 --- a/src/main/services/team/ClaudeBinaryResolver.ts +++ b/src/main/services/team/ClaudeBinaryResolver.ts @@ -226,7 +226,10 @@ export class ClaudeBinaryResolver { const enrichedPath = buildMergedCliPath(null); const flavor = getConfiguredCliFlavor(); - const overrideRaw = process.env.CLAUDE_CLI_PATH?.trim(); + const overrideRaw = + flavor === 'free-code' + ? (process.env.CLAUDE_FREE_CODE_CLI_PATH?.trim() ?? process.env.CLAUDE_CLI_PATH?.trim()) + : process.env.CLAUDE_CLI_PATH?.trim(); if (overrideRaw) { const looksLikePath = path.isAbsolute(overrideRaw) || overrideRaw.includes('\\') || overrideRaw.includes('/'); diff --git a/src/renderer/components/dashboard/CliStatusBanner.tsx b/src/renderer/components/dashboard/CliStatusBanner.tsx index 1b4e1d23..5e23702a 100644 --- a/src/renderer/components/dashboard/CliStatusBanner.tsx +++ b/src/renderer/components/dashboard/CliStatusBanner.tsx @@ -70,6 +70,21 @@ const DetailLine = ({ text }: { text: string | null }): React.JSX.Element | null ); }; +const InstallCompletedNotice = ({ version }: { version: string | null }): React.JSX.Element => ( +
- Verifying authentication... -
-
- {totalGroupCount > 0 ? (
+ {data.total > 0 ? (
<>
- {filteredGroupCount} of{' '}
- {totalGroupCount} groups
- {data.total !== totalGroupCount ? (
- <>
- {' '}
- {data.total}{' '}
- raw lines
- >
- ) : null}
+ {data.total} lines
>
) : isAlive ? (
'No logs yet.'
diff --git a/src/renderer/components/team/dialogs/EditTeamDialog.tsx b/src/renderer/components/team/dialogs/EditTeamDialog.tsx
index 8d396dc0..8cf506be 100644
--- a/src/renderer/components/team/dialogs/EditTeamDialog.tsx
+++ b/src/renderer/components/team/dialogs/EditTeamDialog.tsx
@@ -4,6 +4,7 @@ import { api } from '@renderer/api';
import {
buildMembersFromDrafts,
createMemberDraftsFromInputs,
+ filterEditableMemberInputs,
MembersEditorSection,
validateMemberNameInline,
} from '@renderer/components/team/members/MembersEditorSection';
@@ -49,7 +50,7 @@ interface EditTeamDialogProps {
}
function membersToDrafts(members: ResolvedTeamMember[]) {
- return createMemberDraftsFromInputs(members);
+ return createMemberDraftsFromInputs(filterEditableMemberInputs(members));
}
export const EditTeamDialog = ({
diff --git a/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx b/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx
index 5e2cc034..c4dc3bc7 100644
--- a/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx
+++ b/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx
@@ -7,12 +7,12 @@ import {
buildMembersFromDrafts,
clearMemberModelOverrides,
createMemberDraftsFromInputs,
+ filterEditableMemberInputs,
normalizeMemberDraftForProviderMode,
normalizeProviderForMode,
validateMemberNameInline,
} from '@renderer/components/team/members/MembersEditorSection';
import { TeamRosterEditorSection } from '@renderer/components/team/members/TeamRosterEditorSection';
-import { TeamProvisioningBanner } from '@renderer/components/team/TeamProvisioningBanner';
import { SkipPermissionsCheckbox } from '@renderer/components/team/dialogs/SkipPermissionsCheckbox';
import { Button } from '@renderer/components/ui/button';
import { Checkbox } from '@renderer/components/ui/checkbox';
@@ -42,10 +42,7 @@ import {
} from '@renderer/utils/geminiUiFreeze';
import { normalizeTeamModelForUi } from '@renderer/utils/teamModelAvailability';
import { isTeamProviderId, normalizeOptionalTeamProviderId } from '@shared/utils/teamProvider';
-import {
- getCurrentProvisioningProgressForTeam,
- isTeamProvisioningActive,
-} from '@renderer/store/slices/teamSlice';
+import { isTeamProvisioningActive } from '@renderer/store/slices/teamSlice';
import { normalizePath } from '@renderer/utils/pathNormalize';
import { nameColorSet } from '@renderer/utils/projectColor';
import {
@@ -75,6 +72,7 @@ import {
type ProvisioningProviderCheck,
} from './ProvisioningProviderStatusList';
import { ProjectPathSelector } from './ProjectPathSelector';
+import { resolveLaunchDialogPrefill } from './launchDialogPrefill';
import {
computeEffectiveTeamModel,
formatTeamModelSummary,
@@ -219,6 +217,7 @@ export const LaunchTeamDialog = (props: LaunchTeamDialogProps): React.JSX.Elemen
const [selectedTeamName, setSelectedTeamName] = useState('');
const teamByName = useStore((s) => s.teamByName);
const openDashboard = useStore((s) => s.openDashboard);
+ const openTeamTab = useStore((s) => s.openTeamTab);
const teamOptions = useMemo(
() =>
Object.values(teamByName)
@@ -485,6 +484,13 @@ export const LaunchTeamDialog = (props: LaunchTeamDialogProps): React.JSX.Elemen
setMaxBudgetUsd('');
};
+ const closeDialog = (): void => {
+ if (isLaunch) {
+ resetFormState();
+ }
+ onClose();
+ };
+
// Populate form in schedule edit mode
useEffect(() => {
if (!open || !isSchedule) return;
@@ -551,18 +557,13 @@ export const LaunchTeamDialog = (props: LaunchTeamDialogProps): React.JSX.Elemen
}
if (cancelled) return;
- const rawNextProviderId =
- savedRequest?.providerId === 'codex' || savedRequest?.providerId === 'gemini'
- ? savedRequest.providerId
- : 'anthropic';
- const nextProviderId = normalizeProviderForMode(rawNextProviderId, multimodelEnabled);
- const providerFromSaved = Boolean(savedRequest?.providerId);
const nextMembersSource =
members.length > 0
? members
: savedRequest?.members && savedRequest.members.length > 0
? savedRequest.members
: [];
+ const editableMembersSource = filterEditableMemberInputs(nextMembersSource);
const storedEffort = localStorage.getItem('team:lastSelectedEffort');
const savedProviderId =
savedRequest?.providerId === 'codex' || savedRequest?.providerId === 'gemini'
@@ -570,35 +571,29 @@ export const LaunchTeamDialog = (props: LaunchTeamDialogProps): React.JSX.Elemen
: savedRequest?.providerId === 'anthropic'
? 'anthropic'
: null;
+ const storedProviderId = normalizeProviderForMode(getStoredTeamProvider(), multimodelEnabled);
+ const launchPrefill = resolveLaunchDialogPrefill({
+ members,
+ savedRequest,
+ previousLaunchParams,
+ multimodelEnabled,
+ storedProviderId,
+ storedEffort: storedEffort === null ? 'medium' : storedEffort,
+ getStoredModel: getStoredTeamModel,
+ });
setSavedLaunchProviderId(savedProviderId);
setMembersDrafts(
- createMemberDraftsFromInputs(nextMembersSource).map((member) =>
+ createMemberDraftsFromInputs(editableMembersSource).map((member) =>
normalizeMemberDraftForProviderMode(member, multimodelEnabled)
)
);
setSyncModelsWithLead(
- !nextMembersSource.some((member) => member.providerId || member.model || member.effort)
- );
- setSelectedProviderIdRaw(
- providerFromSaved
- ? nextProviderId
- : normalizeProviderForMode(getStoredTeamProvider(), multimodelEnabled)
- );
- setSelectedModelRaw(
- typeof savedRequest?.model === 'string' &&
- rawNextProviderId !== 'gemini' &&
- nextProviderId === normalizeProviderForMode(rawNextProviderId, true)
- ? savedRequest.model
- : getStoredTeamModel(
- providerFromSaved
- ? nextProviderId
- : normalizeProviderForMode(getStoredTeamProvider(), multimodelEnabled)
- )
- );
- setSelectedEffortRaw(
- savedRequest?.effort ?? (storedEffort === null ? 'medium' : storedEffort)
+ !editableMembersSource.some((member) => member.providerId || member.model || member.effort)
);
+ setSelectedProviderIdRaw(launchPrefill.providerId);
+ setSelectedModelRaw(launchPrefill.model);
+ setSelectedEffortRaw(launchPrefill.effort);
setLimitContextRaw(
savedRequest?.limitContext === true ||
localStorage.getItem('team:lastLimitContext') === 'true'
@@ -612,7 +607,7 @@ export const LaunchTeamDialog = (props: LaunchTeamDialogProps): React.JSX.Elemen
return () => {
cancelled = true;
};
- }, [open, isLaunch, effectiveTeamName, members, multimodelEnabled]);
+ }, [open, isLaunch, effectiveTeamName, members, multimodelEnabled, previousLaunchParams]);
const previousProviderId = useMemo