This commit is contained in:
ZacharyZhang-NY committed 2026-03-19 16:51:44 -04:00
1 parent f482f25856
commit d126c4cf21
165 files changed
+123 -541297

No files matched your search

-12
View File
@@ -24,24 +24,12 @@ function connectorAuthSpec(connectorId: ConnectorId, command: string) {
shellCommand: `${quoteShell(command)} auth login`,
intro: "AgentCompany is opening the Claude Code login flow.",
};
case "opencode_local":
return {
title: "OpenCode Login",
shellCommand: `${quoteShell(command)} auth login`,
intro: "AgentCompany is opening the OpenCode provider login flow.",
};
case "gemini_local":
return {
title: "Gemini CLI Login",
shellCommand: `${quoteShell(command)}`,
intro: "AgentCompany is opening Gemini CLI. If authentication is required, use /auth in the Gemini session.",
};
case "kimi_local":
return {
title: "Kimi Code Login",
shellCommand: `${quoteShell(command)}`,
intro: "AgentCompany is opening Kimi Code. Complete the interactive login flow there if prompted.",
};
}
}
+3 -55
View File
@@ -9,10 +9,6 @@ import {
import { execute as codexExecute, sessionCodec as codexSessionCodec, testEnvironment as codexTestEnvironment } from "@agentcompany/adapter-codex-local/server";
import { agentConfigurationDoc as geminiDoc, DEFAULT_GEMINI_LOCAL_MODEL, models as geminiModels } from "@agentcompany/adapter-gemini-local";
import { execute as geminiExecute, sessionCodec as geminiSessionCodec, testEnvironment as geminiTestEnvironment } from "@agentcompany/adapter-gemini-local/server";
import { agentConfigurationDoc as kimiDoc, DEFAULT_KIMI_LOCAL_MODEL, models as kimiModels } from "@agentcompany/adapter-kimi-local";
import { execute as kimiExecute, testEnvironment as kimiTestEnvironment } from "@agentcompany/adapter-kimi-local/server";
import { agentConfigurationDoc as opencodeDoc } from "@agentcompany/adapter-opencode-local";
import { execute as opencodeExecute, listOpenCodeModels, sessionCodec as opencodeSessionCodec, testEnvironment as opencodeTestEnvironment } from "@agentcompany/adapter-opencode-local/server";
import type { CapabilityMatrix, ConnectorId, ConnectorRecord } from "@shared/types";
const RESERVED_ENV_BINDING_KEYS = new Set([
@@ -47,7 +43,7 @@ const CONNECTORS: ConnectorDefinition[] = [
description: "Local Codex CLI with session resume, structured event parsing, and model selection.",
defaultCommand: "codex",
defaultModel: DEFAULT_CODEX_LOCAL_MODEL,
notes: "Best fit for high-context coding tasks and shell-heavy automation.",
notes: "",
capabilityMatrix: {
sessionResume: true,
gracefulCancel: true,
@@ -71,7 +67,7 @@ const CONNECTORS: ConnectorDefinition[] = [
description: "Local Claude Code CLI with session persistence and structured result parsing.",
defaultCommand: "claude",
defaultModel: claudeModels[0]?.id ?? null,
notes: "Useful for long-running coding sessions and agent-style loops.",
notes: "",
capabilityMatrix: {
sessionResume: true,
gracefulCancel: true,
@@ -95,7 +91,7 @@ const CONNECTORS: ConnectorDefinition[] = [
description: "Local Gemini CLI with resume support and JSON event parsing.",
defaultCommand: "gemini",
defaultModel: DEFAULT_GEMINI_LOCAL_MODEL,
notes: "Good for multi-provider local workflows when Google auth is already in place.",
notes: "",
capabilityMatrix: {
sessionResume: true,
gracefulCancel: true,
@@ -113,54 +109,6 @@ const CONNECTORS: ConnectorDefinition[] = [
},
listModels: async () => geminiModels,
},
{
id: "opencode_local",
label: "OpenCode",
description: "Local OpenCode runtime with provider/model routing and session restore.",
defaultCommand: "opencode",
defaultModel: null,
notes: "Best when model routing belongs to the runtime rather than the app.",
capabilityMatrix: {
sessionResume: true,
gracefulCancel: true,
modelDiscovery: true,
structuredTranscript: true,
costAttribution: true,
},
configurationDoc: opencodeDoc,
module: {
type: "opencode_local",
execute: opencodeExecute,
testEnvironment: opencodeTestEnvironment,
sessionCodec: opencodeSessionCodec,
models: [],
listModels: listOpenCodeModels,
},
listModels: listOpenCodeModels,
},
{
id: "kimi_local",
label: "Kimi Code",
description: "Official Kimi Code CLI surfaced through a generic execution contract with explicit capability downgrade.",
defaultCommand: "kimi",
defaultModel: DEFAULT_KIMI_LOCAL_MODEL,
notes: "MVP support is explicit generic mode until richer runtime contract evidence is implemented.",
capabilityMatrix: {
sessionResume: false,
gracefulCancel: false,
modelDiscovery: false,
structuredTranscript: false,
costAttribution: false,
},
configurationDoc: kimiDoc,
module: {
type: "kimi_local",
execute: kimiExecute,
testEnvironment: kimiTestEnvironment,
models: kimiModels,
},
listModels: async () => kimiModels,
},
];
export function getConnectorDefinition(id: ConnectorId): ConnectorDefinition {
-6
View File
@@ -124,12 +124,6 @@ function buildConnectorExecutionConfig(job: RunJob, sanitizedEnv: Record<string,
approvalMode: "yolo",
sandbox: true,
};
case "kimi_local":
return {
...baseConfig,
extraArgs: ["--yolo"],
};
case "opencode_local":
default:
return baseConfig;
}
+1 -1
View File
@@ -702,7 +702,7 @@ Body: {
"title": "Descriptive title",
"department": "engineering|product|marketing|sales|hr|finance|legal|operations|customer_support|research|design|executive",
"reportsTo": "{managerAgentId — who this person reports to}",
"connectorId": "codex_local|claude_local|gemini_local|opencode_local|kimi_local",
"connectorId": "codex_local|claude_local|gemini_local",
"workspaceId": "{optionalWorkspaceId}",
"model": "model-identifier",
"capabilities": "Detailed description of what this hire should own and be able to do",
+113 -1
View File
@@ -5,11 +5,13 @@ import {
CheckCircle,
Cpu,
FolderOpen,
GearSix,
GlobeSimple,
Lightning,
Rocket,
Target,
WarningCircle,
X,
XCircle,
} from "@phosphor-icons/react";
import { getConnectorExecutionReadinessIssue, isConnectorExecutionReady } from "@shared/connector-policy";
@@ -50,6 +52,11 @@ export function CompanyOnboarding({
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [launchSummary, setLaunchSummary] = useState<OnboardingBootstrapResult | null>(null);
const [claudeSettingsOpen, setClaudeSettingsOpen] = useState(false);
const [claudeApiKey, setClaudeApiKey] = useState("");
const [claudeBaseUrl, setClaudeBaseUrl] = useState("");
const [claudeModel, setClaudeModel] = useState("");
const [claudeSettingsSaving, setClaudeSettingsSaving] = useState(false);
const executionReadyConnectors = useMemo(
() => connectors.filter((connector) => isConnectorExecutionReady(connector)),
@@ -70,6 +77,44 @@ export function CompanyOnboarding({
}
}, [executionReadyConnectors, selectedConnectorId]);
const openClaudeSettings = useCallback(() => {
const claude = connectors.find((c) => c.id === "claude_local");
if (!claude) return;
const lines = claude.envBindingText.split(/\r?\n/);
setClaudeApiKey(lines.find((l) => l.startsWith("ANTHROPIC_API_KEY="))?.split("=").slice(1).join("=") ?? "");
setClaudeBaseUrl(lines.find((l) => l.startsWith("ANTHROPIC_BASE_URL="))?.split("=").slice(1).join("=") ?? "");
setClaudeModel(claude.model ?? "");
setClaudeSettingsOpen(true);
}, [connectors]);
const saveClaudeSettings = useCallback(async () => {
setClaudeSettingsSaving(true);
try {
const envLines: string[] = [];
if (claudeApiKey.trim()) envLines.push(`ANTHROPIC_API_KEY=${claudeApiKey.trim()}`);
if (claudeBaseUrl.trim()) envLines.push(`ANTHROPIC_BASE_URL=${claudeBaseUrl.trim()}`);
const claude = connectors.find((c) => c.id === "claude_local");
if (!claude) return;
const existingLines = claude.envBindingText.split(/\r?\n/).filter((l) => {
const key = l.split("=")[0]?.trim();
return key && key !== "ANTHROPIC_API_KEY" && key !== "ANTHROPIC_BASE_URL" && l.trim();
});
const newEnvText = [...envLines, ...existingLines].join("\n");
unwrap(await window.agentCompany.saveConnector({
id: "claude_local",
command: claude.command,
model: claudeModel.trim() || null,
envBindingText: newEnvText,
notes: claude.notes,
}));
setClaudeSettingsOpen(false);
} catch (err) {
setError(err instanceof Error ? err.message : String(err));
} finally {
setClaudeSettingsSaving(false);
}
}, [claudeApiKey, claudeBaseUrl, claudeModel, connectors]);
const stepLabels = [
{ label: t("onboarding.stepLabel1"), icon: Buildings },
{ label: t("onboarding.stepLabel2"), icon: Target },
@@ -221,7 +266,7 @@ export function CompanyOnboarding({
) : (
<WarningCircle size={16} weight="fill" className="shrink-0 text-[color:var(--warn)]" />
)}
<div className="min-w-0">
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<span className={isReady ? "font-medium text-[color:var(--text)]" : isChecking ? "text-[color:var(--text)]" : "text-[color:var(--muted)]"}>
{connector.label}
@@ -236,6 +281,16 @@ export function CompanyOnboarding({
</div>
) : null}
</div>
{connector.id === "claude_local" ? (
<button
type="button"
onClick={(e) => { e.stopPropagation(); openClaudeSettings(); }}
className="shrink-0 rounded-[6px] p-1.5 text-[color:var(--muted)] transition hover:bg-[color:var(--panel-soft)] hover:text-[color:var(--text)]"
title={currentLocale === "zh" ? "Claude Code 设置" : "Claude Code Settings"}
>
<GearSix size={14} />
</button>
) : null}
</div>
);
})}
@@ -569,6 +624,63 @@ export function CompanyOnboarding({
</AnimatePresence>
</div>
</div>
{claudeSettingsOpen ? (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-[color:var(--bg)]/72 backdrop-blur-sm" onClick={() => setClaudeSettingsOpen(false)}>
<div className="w-full max-w-[420px] rounded-[12px] border border-[color:var(--line)] bg-[color:var(--panel)] p-6 shadow-lg" onClick={(e) => e.stopPropagation()}>
<div className="mb-5 flex items-center justify-between">
<h3 className="text-[16px] font-semibold text-[color:var(--text)]">Claude Code {currentLocale === "zh" ? "设置" : "Settings"}</h3>
<button type="button" onClick={() => setClaudeSettingsOpen(false)} className="rounded-[6px] p-1 text-[color:var(--muted)] transition hover:text-[color:var(--text)]"><X size={16} /></button>
</div>
<div className="space-y-4">
<div>
<label className="mb-1.5 block text-[12px] font-medium text-[color:var(--muted-strong)]">API Key</label>
<input
type="password"
className="focus-ring w-full rounded-[8px] border border-[color:var(--line)] bg-[color:var(--panel)] px-3 py-2.5 text-[13px] text-[color:var(--text)] placeholder:text-[color:var(--muted)]"
value={claudeApiKey}
onChange={(e) => setClaudeApiKey(e.target.value)}
placeholder="sk-ant-..."
/>
<div className="mt-1 text-[11px] text-[color:var(--muted)]">{currentLocale === "zh" ? "留空使用订阅认证" : "Leave empty to use subscription auth"}</div>
</div>
<div>
<label className="mb-1.5 block text-[12px] font-medium text-[color:var(--muted-strong)]">Base URL</label>
<input
className="focus-ring w-full rounded-[8px] border border-[color:var(--line)] bg-[color:var(--panel)] px-3 py-2.5 text-[13px] text-[color:var(--text)] placeholder:text-[color:var(--muted)]"
value={claudeBaseUrl}
onChange={(e) => setClaudeBaseUrl(e.target.value)}
placeholder="https://api.anthropic.com"
/>
<div className="mt-1 text-[11px] text-[color:var(--muted)]">{currentLocale === "zh" ? "留空使用默认端点" : "Leave empty for default endpoint"}</div>
</div>
<div>
<label className="mb-1.5 block text-[12px] font-medium text-[color:var(--muted-strong)]">Model</label>
<input
className="focus-ring w-full rounded-[8px] border border-[color:var(--line)] bg-[color:var(--panel)] px-3 py-2.5 text-[13px] text-[color:var(--text)] placeholder:text-[color:var(--muted)]"
value={claudeModel}
onChange={(e) => setClaudeModel(e.target.value)}
placeholder="claude-sonnet-4-20250514"
/>
<div className="mt-1 text-[11px] text-[color:var(--muted)]">{currentLocale === "zh" ? "留空使用连接器默认模型" : "Leave empty for connector default"}</div>
</div>
</div>
<div className="mt-6 flex justify-end gap-3">
<button type="button" onClick={() => setClaudeSettingsOpen(false)} className="rounded-[8px] px-4 py-2 text-[13px] font-medium text-[color:var(--muted)] transition hover:text-[color:var(--text)]">
{currentLocale === "zh" ? "取消" : "Cancel"}
</button>
<button
type="button"
onClick={() => void saveClaudeSettings()}
disabled={claudeSettingsSaving}
className="rounded-[8px] bg-[color:var(--accent)] px-4 py-2 text-[13px] font-medium text-white transition hover:opacity-90 disabled:opacity-50"
>
{claudeSettingsSaving ? (currentLocale === "zh" ? "保存中..." : "Saving...") : (currentLocale === "zh" ? "保存" : "Save")}
</button>
</div>
</div>
</div>
) : null}
</div>
);
}
+2 -14
View File
@@ -19,24 +19,12 @@ function getAuthActionCopy(connector: ConnectorRecord) {
message: "Claude Code is installed, but the local CLI is not authenticated for this desktop runtime.",
buttonLabel: "Open Terminal To Sign In",
};
case "opencode_local":
return {
title: "OpenCode provider login required",
message: "OpenCode is installed, but the provider login must be completed before runs can start.",
buttonLabel: "Open Terminal To Sign In",
};
case "gemini_local":
return {
title: "Gemini authentication required",
message: "Gemini CLI is available, but the runtime still needs an authenticated session. AgentCompany will open Gemini in a terminal and you can complete its interactive auth flow there.",
buttonLabel: "Open Terminal",
};
case "kimi_local":
return {
title: "Kimi interactive login required",
message: "Kimi Code is available in generic mode. AgentCompany can open the CLI in a terminal so you can complete the interactive login flow there if prompted.",
buttonLabel: "Open Terminal",
};
}
}
@@ -55,8 +43,8 @@ export function ConnectorEditor({ connector, onSaved }: { connector: ConnectorRe
const authPollTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const authActionCopy = getAuthActionCopy(connector);
const needsLogin = connector.authState === "required" || connector.status === "auth_required";
const requiresExplicitModel = connector.id === "opencode_local";
const shouldLoadModelOptions = connector.capabilityMatrix.modelDiscovery || connector.id === "opencode_local";
const requiresExplicitModel = false;
const shouldLoadModelOptions = connector.capabilityMatrix.modelDiscovery;
useEffect(() => {
setModel(connector.model ?? "");
-2
View File
@@ -1176,8 +1176,6 @@ const adapterLabels: Record<string, string> = {
claude_local: "Claude",
codex_local: "Codex",
gemini_local: "Gemini",
opencode_local: "OpenCode",
kimi_local: "Kimi",
};
export function OrgChart({
+2 -2
View File
@@ -88,7 +88,7 @@ export const companyInputSchema = z.object({
});
export const connectorInputSchema = z.object({
id: z.enum(["codex_local", "claude_local", "gemini_local", "opencode_local", "kimi_local"]),
id: z.enum(["codex_local", "claude_local", "gemini_local"]),
command: z.string().min(1),
model: z.string().nullable().optional(),
envBindingText: z.string().default(""),
@@ -288,7 +288,7 @@ export const bootstrapOnboardingSchema = z.object({
autoApproveHires: z.boolean().default(true),
goalTitle: z.string().default(""),
goalDescription: z.string().default(""),
connectorId: z.enum(["codex_local", "claude_local", "gemini_local", "opencode_local", "kimi_local"]).optional(),
connectorId: z.enum(["codex_local", "claude_local", "gemini_local"]).optional(),
});
export const socialAccountInputSchema = z.object({
+1 -3
View File
@@ -27,9 +27,7 @@ export type SectionId =
export type ConnectorId =
| "codex_local"
| "claude_local"
| "gemini_local"
| "opencode_local"
| "kimi_local";
| "gemini_local";
export type ConnectorStatus =
| "not_installed"