[Init] Initial commit - NetMesh terminal manager
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
This commit is contained in:
239
components/settings/tabs/ai/managedAgentState.ts
Normal file
239
components/settings/tabs/ai/managedAgentState.ts
Normal file
@@ -0,0 +1,239 @@
|
||||
import type { ExternalAgentConfig } from "../../../../infrastructure/ai/types";
|
||||
import {
|
||||
type ManagedAgentKey,
|
||||
isPathLikeCommand,
|
||||
} from "../../../../infrastructure/ai/managedAgents";
|
||||
import type { AgentPathInfo } from "./types";
|
||||
import { AGENT_DEFAULTS, isCursorAvailableForMode } from "./types";
|
||||
import { buildCodebuddyEnv } from "./codebuddyConfigEnv";
|
||||
|
||||
function getAutoManagedAgentStoredPath(
|
||||
agents: ExternalAgentConfig[],
|
||||
agentKey: ManagedAgentKey,
|
||||
): string | null {
|
||||
const managed = agents.find((agent) => agent.id === `discovered_${agentKey}`);
|
||||
if (managed?.commandSource === "auto") return null;
|
||||
return isPathLikeCommand(managed?.command) ? managed?.command ?? null : null;
|
||||
}
|
||||
|
||||
export function areExternalAgentListsEqual(
|
||||
left: ExternalAgentConfig[],
|
||||
right: ExternalAgentConfig[],
|
||||
): boolean {
|
||||
if (left.length !== right.length) return false;
|
||||
return left.every((agent, index) => JSON.stringify(agent) === JSON.stringify(right[index]));
|
||||
}
|
||||
|
||||
export function buildManagedAgentState(
|
||||
prevAgents: ExternalAgentConfig[],
|
||||
defaultAgentId: string,
|
||||
agentKey: ManagedAgentKey,
|
||||
pathInfo: AgentPathInfo | null,
|
||||
commandSource: "manual" | "auto" = "auto",
|
||||
): { agents: ExternalAgentConfig[]; defaultAgentId: string } {
|
||||
const managedId = `discovered_${agentKey}`;
|
||||
const managedAgents = prevAgents.filter((agent) => agent.id === managedId);
|
||||
const otherAgents = prevAgents.filter((agent) => agent.id !== managedId);
|
||||
|
||||
if (!pathInfo?.available || !pathInfo.path) {
|
||||
const existingManaged = managedAgents.find((agent) => agent.id === managedId);
|
||||
if (agentKey === "cursor" && (existingManaged?.apiKey || existingManaged?.cursorAuthMode === "cli-login")) {
|
||||
const defaults = AGENT_DEFAULTS[agentKey];
|
||||
const {
|
||||
acpCommand: _legacyCommand,
|
||||
acpArgs: _legacyArgs,
|
||||
...existingManagedWithoutLegacy
|
||||
} = existingManaged;
|
||||
return {
|
||||
agents: [
|
||||
...otherAgents,
|
||||
{
|
||||
...existingManagedWithoutLegacy,
|
||||
...defaults,
|
||||
id: managedId,
|
||||
command: pathInfo?.path || existingManaged.command || "cursor",
|
||||
// Preserve enable preference when probe is temporarily unavailable
|
||||
// (e.g. wrong apiKeyPresent gating). Send requires available too.
|
||||
enabled: existingManaged.enabled ?? true,
|
||||
available: false,
|
||||
// Preserve stored API key across mode / temporary unavailability.
|
||||
...(existingManaged.apiKey ? { apiKey: existingManaged.apiKey } : {}),
|
||||
cursorAuthMode: existingManaged.cursorAuthMode === "cli-login" ? "cli-login" : "api-key",
|
||||
},
|
||||
],
|
||||
defaultAgentId: existingManaged.id === defaultAgentId ? "catty" : defaultAgentId,
|
||||
};
|
||||
}
|
||||
if (agentKey === "codebuddy") {
|
||||
const hasSavedCodebuddyConfig = Boolean(
|
||||
(existingManaged?.env && Object.keys(existingManaged.env).length > 0) ||
|
||||
(
|
||||
existingManaged?.codebuddyOptions &&
|
||||
Object.keys(existingManaged.codebuddyOptions).length > 0
|
||||
),
|
||||
);
|
||||
if (hasSavedCodebuddyConfig) {
|
||||
return {
|
||||
agents: [
|
||||
...otherAgents,
|
||||
{
|
||||
...existingManaged,
|
||||
...AGENT_DEFAULTS.codebuddy,
|
||||
id: managedId,
|
||||
command: existingManaged.command || "codebuddy",
|
||||
enabled: false,
|
||||
available: false,
|
||||
},
|
||||
],
|
||||
defaultAgentId: existingManaged.id === defaultAgentId ? "catty" : defaultAgentId,
|
||||
};
|
||||
}
|
||||
}
|
||||
return {
|
||||
agents: otherAgents,
|
||||
defaultAgentId: managedAgents.some((agent) => agent.id === defaultAgentId)
|
||||
? "catty"
|
||||
: defaultAgentId,
|
||||
};
|
||||
}
|
||||
|
||||
const existingManaged = managedAgents.find((agent) => agent.id === managedId);
|
||||
const {
|
||||
acpCommand: _legacyCommand,
|
||||
acpArgs: _legacyArgs,
|
||||
...existingManagedWithoutLegacy
|
||||
} = existingManaged ?? {};
|
||||
const defaults = AGENT_DEFAULTS[agentKey];
|
||||
const managedEnv =
|
||||
agentKey === "claude"
|
||||
? { ...(existingManaged?.env ?? {}), CLAUDE_CODE_EXECUTABLE: pathInfo.path }
|
||||
: agentKey === "codebuddy"
|
||||
? { ...(existingManaged?.env ?? {}), CODEBUDDY_CODE_PATH: pathInfo.path }
|
||||
: agentKey === "opencode"
|
||||
? { ...(existingManaged?.env ?? {}), OPENCODE_BIN: pathInfo.path }
|
||||
: existingManaged?.env;
|
||||
const cursorAuthMode = agentKey === "cursor"
|
||||
? (existingManaged?.cursorAuthMode
|
||||
?? (pathInfo.authSource === "cli-login" || pathInfo.cliLoginOk ? "cli-login" : "api-key"))
|
||||
: undefined;
|
||||
const cursorModeAvailable = agentKey === "cursor"
|
||||
? isCursorAvailableForMode(pathInfo, cursorAuthMode === "cli-login" ? "cli-login" : "api-key", {
|
||||
hasStoredApiKey: Boolean(existingManaged?.apiKey),
|
||||
})
|
||||
: true;
|
||||
|
||||
const nextManagedAgent: ExternalAgentConfig = {
|
||||
...existingManagedWithoutLegacy,
|
||||
...defaults,
|
||||
id: managedId,
|
||||
command: agentKey === "cursor" && cursorAuthMode === "cli-login"
|
||||
? (pathInfo.cliBinPath || pathInfo.path)
|
||||
: pathInfo.path,
|
||||
commandSource,
|
||||
// Persist probed --version so the chat model picker can gate GPT-5.6+
|
||||
// even when this custom path is not the PATH discovery binary.
|
||||
...(pathInfo.version ? { cliVersion: pathInfo.version } : {}),
|
||||
...(managedEnv ? { env: managedEnv } : {}),
|
||||
available: cursorModeAvailable,
|
||||
// Do not force-disable when only the current auth mode is temporarily
|
||||
// unavailable (user may switch modes). Send paths already require available.
|
||||
enabled: managedAgents.length === 0
|
||||
|| (agentKey === "codebuddy" && existingManaged && !isPathLikeCommand(existingManaged.command))
|
||||
? true
|
||||
: managedAgents.some((agent) => agent.enabled) || managedAgents.every((agent) => agent.available === false),
|
||||
...(agentKey === "cursor" ? {
|
||||
cursorAuthMode,
|
||||
// Keep stored API key in both modes; CLI turns omit it via env wiring.
|
||||
...(existingManaged?.apiKey ? { apiKey: existingManaged.apiKey } : {}),
|
||||
} : {}),
|
||||
};
|
||||
|
||||
return {
|
||||
agents: [...otherAgents, nextManagedAgent],
|
||||
defaultAgentId: managedAgents.some((agent) => agent.id === defaultAgentId)
|
||||
? managedId
|
||||
: defaultAgentId,
|
||||
};
|
||||
}
|
||||
|
||||
export function updateCodebuddyManagedEnv(
|
||||
prevAgents: ExternalAgentConfig[],
|
||||
internetEnv: string,
|
||||
envText: string,
|
||||
): ExternalAgentConfig[] {
|
||||
const managedId = "discovered_codebuddy";
|
||||
const existingManaged = prevAgents.find((agent) => agent.id === managedId);
|
||||
const nextEnv = buildCodebuddyEnv(existingManaged?.env, internetEnv, envText);
|
||||
|
||||
if (existingManaged) {
|
||||
if (!nextEnv && !isPathLikeCommand(existingManaged.command)) {
|
||||
return prevAgents.filter((agent) => agent.id !== managedId);
|
||||
}
|
||||
return prevAgents.map((agent) =>
|
||||
agent.id === managedId
|
||||
? { ...agent, ...(nextEnv ? { env: nextEnv } : { env: undefined }) }
|
||||
: agent,
|
||||
);
|
||||
}
|
||||
|
||||
if (!nextEnv) return prevAgents;
|
||||
|
||||
return [
|
||||
...prevAgents,
|
||||
{
|
||||
...AGENT_DEFAULTS.codebuddy,
|
||||
id: managedId,
|
||||
command: "codebuddy",
|
||||
enabled: false,
|
||||
env: nextEnv,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function updateCodebuddyManagedOptions(
|
||||
prevAgents: ExternalAgentConfig[],
|
||||
options: ExternalAgentConfig['codebuddyOptions'],
|
||||
): ExternalAgentConfig[] {
|
||||
const managedId = "discovered_codebuddy";
|
||||
const existingManaged = prevAgents.find((agent) => agent.id === managedId);
|
||||
|
||||
if (existingManaged) {
|
||||
if (
|
||||
!options &&
|
||||
(!existingManaged.env || Object.keys(existingManaged.env).length === 0) &&
|
||||
!isPathLikeCommand(existingManaged.command)
|
||||
) {
|
||||
return prevAgents.filter((agent) => agent.id !== managedId);
|
||||
}
|
||||
return prevAgents.map((agent) =>
|
||||
agent.id === managedId
|
||||
? { ...agent, codebuddyOptions: options }
|
||||
: agent,
|
||||
);
|
||||
}
|
||||
|
||||
if (!options) return prevAgents;
|
||||
|
||||
return [
|
||||
...prevAgents,
|
||||
{
|
||||
...AGENT_DEFAULTS.codebuddy,
|
||||
id: managedId,
|
||||
command: "codebuddy",
|
||||
enabled: false,
|
||||
codebuddyOptions: options,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function getInitialManagedAgentPaths(agents: ExternalAgentConfig[]) {
|
||||
return {
|
||||
codex: getAutoManagedAgentStoredPath(agents, "codex") ?? "",
|
||||
claude: getAutoManagedAgentStoredPath(agents, "claude") ?? "",
|
||||
copilot: getAutoManagedAgentStoredPath(agents, "copilot") ?? "",
|
||||
cursor: getAutoManagedAgentStoredPath(agents, "cursor") ?? "",
|
||||
codebuddy: getAutoManagedAgentStoredPath(agents, "codebuddy") ?? "",
|
||||
opencode: getAutoManagedAgentStoredPath(agents, "opencode") ?? "",
|
||||
grok: getAutoManagedAgentStoredPath(agents, "grok") ?? "",
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user