Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/renderer/components/thread/ThreadAuthRequiredDock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { toast } from "@heroui/react";
import { KeyRound, LogIn, RefreshCw, Settings } from "lucide-react";
import { Trans, useLingui } from "@lingui/react/macro";
import type { AgentStatus, Project } from "@/shared/contracts";
import { friendlyError } from "@/shared/messages";
import { isRemoteSession, readBridge } from "@/renderer/bridge";
import { runAgentLoginCommand } from "@/renderer/actions/agentLoginActions";
import { openSettings } from "@/renderer/actions/panelActions";
Expand Down Expand Up @@ -104,7 +105,9 @@ export function ThreadAuthRequiredDock(props: { agentStatus: AgentStatus; projec
toast.success(t`${agentStatus.label} authenticated.`);
} catch (error) {
toast.danger(
error instanceof Error ? error.message : t`Unable to authenticate ${agentStatus.label}.`,
error instanceof Error
? friendlyError(error)
: t`Unable to authenticate ${agentStatus.label}.`,
);
} finally {
setPendingAction(undefined);
Expand Down
13 changes: 13 additions & 0 deletions src/renderer/i18n/sharedMessages.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,19 @@ describe("shared message i18n integration", () => {
expect(summary.length).toBeGreaterThan(0);
});

it("translates wrapped ACP authentication verification errors", async () => {
await dynamicActivate("es");
const summary = friendlyError(
new Error(
"Error invoking remote method 'poracode:authenticate-acp-agent': Error: My ACP reported authentication success, but Poracode could not verify it. Configure My ACP directly, then try again.",
),
);

expect(summary).toBe(
"My ACP informó que la autenticación se realizó correctamente, pero Poracode no pudo verificarla. Configura My ACP directamente y vuelve a intentarlo.",
);
});

it("translates main-process SSH manifest errors and preserves their path", async () => {
await dynamicActivate("es");
const path = "C:\\Poracode\\server.ssh-runtime-manifest.json";
Expand Down
4 changes: 4 additions & 0 deletions src/renderer/i18n/sharedMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,10 @@ const SHARED_MESSAGE_DESCRIPTORS: Record<MessageKey, MessageDescriptor> = {
"supervisor.exited": msg({ message: "Background process exited unexpectedly" }),
"supervisor.notRunning": msg({ message: "Background process is not running" }),
"supervisor.proposedPlan": msg({ message: "Proposed plan" }),
"acp.authenticationUnverified": msg({
message:
"{agent} reported authentication success, but Poracode could not verify it. Configure {agent} directly, then try again.",
}),
"kimi.credentialsLocked": msg({
message:
"Kimi Code could not update its credentials because another process is using the credential file. Close other Poracode or Kimi Code processes, then retry.",
Expand Down
4 changes: 4 additions & 0 deletions src/renderer/locales/de/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,10 @@ msgstr "{0}/{1} ausgewählt"
msgid "{0}mo ago"
msgstr "vor {0} Mon."

#: src/renderer/i18n/sharedMessages.ts
msgid "{agent} reported authentication success, but Poracode could not verify it. Configure {agent} directly, then try again."
msgstr "{agent} hat eine erfolgreiche Authentifizierung gemeldet, Poracode konnte sie jedoch nicht überprüfen. Konfiguriere {agent} direkt und versuche es dann erneut."

#: src/renderer/views/SettingsOverlay/parts/AcpRegistrySettings.tsx
msgid "{agentLabel} install targets"
msgstr "{agentLabel} Installationsziele"
Expand Down
4 changes: 4 additions & 0 deletions src/renderer/locales/en/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,10 @@ msgstr "{0}/{1} selected"
msgid "{0}mo ago"
msgstr "{0}mo ago"

#: src/renderer/i18n/sharedMessages.ts
msgid "{agent} reported authentication success, but Poracode could not verify it. Configure {agent} directly, then try again."
msgstr "{agent} reported authentication success, but Poracode could not verify it. Configure {agent} directly, then try again."

#: src/renderer/views/SettingsOverlay/parts/AcpRegistrySettings.tsx
msgid "{agentLabel} install targets"
msgstr "{agentLabel} install targets"
Expand Down
4 changes: 4 additions & 0 deletions src/renderer/locales/es/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,10 @@ msgstr "{0}/{1} seleccionados"
msgid "{0}mo ago"
msgstr "hace {0} m"

#: src/renderer/i18n/sharedMessages.ts
msgid "{agent} reported authentication success, but Poracode could not verify it. Configure {agent} directly, then try again."
msgstr "{agent} informó que la autenticación se realizó correctamente, pero Poracode no pudo verificarla. Configura {agent} directamente y vuelve a intentarlo."

#: src/renderer/views/SettingsOverlay/parts/AcpRegistrySettings.tsx
msgid "{agentLabel} install targets"
msgstr "{agentLabel} objetivos de instalación"
Expand Down
4 changes: 4 additions & 0 deletions src/renderer/locales/fr/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,10 @@ msgstr "{0}/{1} sélectionnés"
msgid "{0}mo ago"
msgstr "il y a {0} mois"

#: src/renderer/i18n/sharedMessages.ts
msgid "{agent} reported authentication success, but Poracode could not verify it. Configure {agent} directly, then try again."
msgstr "{agent} a signalé que l’authentification avait réussi, mais Poracode n’a pas pu la vérifier. Configurez directement {agent}, puis réessayez."

#: src/renderer/views/SettingsOverlay/parts/AcpRegistrySettings.tsx
msgid "{agentLabel} install targets"
msgstr "{agentLabel} cibles d'installation"
Expand Down
4 changes: 4 additions & 0 deletions src/renderer/locales/ja/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,10 @@ msgstr "{0}/{1} 件を選択済み"
msgid "{0}mo ago"
msgstr "{0}か月前"

#: src/renderer/i18n/sharedMessages.ts
msgid "{agent} reported authentication success, but Poracode could not verify it. Configure {agent} directly, then try again."
msgstr "{agent} は認証に成功したと報告しましたが、Poracode では確認できませんでした。{agent} を直接設定してから、もう一度お試しください。"

#: src/renderer/views/SettingsOverlay/parts/AcpRegistrySettings.tsx
msgid "{agentLabel} install targets"
msgstr "{agentLabel}インストールターゲット"
Expand Down
4 changes: 4 additions & 0 deletions src/renderer/locales/ko/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,10 @@ msgstr "{0}/{1}개 선택됨"
msgid "{0}mo ago"
msgstr "{0}개월 전"

#: src/renderer/i18n/sharedMessages.ts
msgid "{agent} reported authentication success, but Poracode could not verify it. Configure {agent} directly, then try again."
msgstr "{agent}이(가) 인증에 성공했다고 보고했지만 Poracode에서 확인할 수 없습니다. {agent}을(를) 직접 구성한 후 다시 시도하세요."

#: src/renderer/views/SettingsOverlay/parts/AcpRegistrySettings.tsx
msgid "{agentLabel} install targets"
msgstr "{agentLabel} 설치 대상"
Expand Down
4 changes: 4 additions & 0 deletions src/renderer/locales/pl/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,10 @@ msgstr "Wybrano {0}/{1}"
msgid "{0}mo ago"
msgstr "{0} mies. temu"

#: src/renderer/i18n/sharedMessages.ts
msgid "{agent} reported authentication success, but Poracode could not verify it. Configure {agent} directly, then try again."
msgstr "{agent} zgłosił pomyślne uwierzytelnienie, ale Poracode nie mógł go zweryfikować. Skonfiguruj bezpośrednio {agent}, a następnie spróbuj ponownie."

#: src/renderer/views/SettingsOverlay/parts/AcpRegistrySettings.tsx
msgid "{agentLabel} install targets"
msgstr "{agentLabel} cele instalacji"
Expand Down
4 changes: 4 additions & 0 deletions src/renderer/locales/pt-BR/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,10 @@ msgstr "{0}/{1} selecionados"
msgid "{0}mo ago"
msgstr "há {0} meses"

#: src/renderer/i18n/sharedMessages.ts
msgid "{agent} reported authentication success, but Poracode could not verify it. Configure {agent} directly, then try again."
msgstr "{agent} informou que a autenticação foi bem-sucedida, mas o Poracode não conseguiu verificá-la. Configure {agent} diretamente e tente novamente."

#: src/renderer/views/SettingsOverlay/parts/AcpRegistrySettings.tsx
msgid "{agentLabel} install targets"
msgstr "{agentLabel} destinos de instalação"
Expand Down
4 changes: 4 additions & 0 deletions src/renderer/locales/ru/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,10 @@ msgstr "Выбрано: {0}/{1}"
msgid "{0}mo ago"
msgstr "{0} мес. назад"

#: src/renderer/i18n/sharedMessages.ts
msgid "{agent} reported authentication success, but Poracode could not verify it. Configure {agent} directly, then try again."
msgstr "{agent} сообщил об успешной аутентификации, но Poracode не удалось её проверить. Настройте {agent} напрямую и повторите попытку."

#: src/renderer/views/SettingsOverlay/parts/AcpRegistrySettings.tsx
msgid "{agentLabel} install targets"
msgstr "{agentLabel} целей установки"
Expand Down
4 changes: 4 additions & 0 deletions src/renderer/locales/tr/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,10 @@ msgstr "{0}/{1} seçildi"
msgid "{0}mo ago"
msgstr "{0} ay önce"

#: src/renderer/i18n/sharedMessages.ts
msgid "{agent} reported authentication success, but Poracode could not verify it. Configure {agent} directly, then try again."
msgstr "{agent}, kimlik doğrulamanın başarılı olduğunu bildirdi ancak Poracode bunu doğrulayamadı. {agent} aracısını doğrudan yapılandırıp yeniden deneyin."

#: src/renderer/views/SettingsOverlay/parts/AcpRegistrySettings.tsx
msgid "{agentLabel} install targets"
msgstr "{agentLabel} yükleme hedefi"
Expand Down
4 changes: 4 additions & 0 deletions src/renderer/locales/uk/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,10 @@ msgstr "Вибрано: {0}/{1}"
msgid "{0}mo ago"
msgstr "{0} міс. тому"

#: src/renderer/i18n/sharedMessages.ts
msgid "{agent} reported authentication success, but Poracode could not verify it. Configure {agent} directly, then try again."
msgstr "{agent} повідомив про успішну автентифікацію, але Poracode не вдалося її перевірити. Налаштуйте {agent} безпосередньо та повторіть спробу."

#: src/renderer/views/SettingsOverlay/parts/AcpRegistrySettings.tsx
msgid "{agentLabel} install targets"
msgstr "{agentLabel} цілей встановлення"
Expand Down
4 changes: 4 additions & 0 deletions src/renderer/locales/vi/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,10 @@ msgstr "Đã chọn {0}/{1}"
msgid "{0}mo ago"
msgstr "{0} tháng trước"

#: src/renderer/i18n/sharedMessages.ts
msgid "{agent} reported authentication success, but Poracode could not verify it. Configure {agent} directly, then try again."
msgstr "{agent} báo cáo xác thực thành công nhưng Poracode không thể xác minh. Hãy cấu hình trực tiếp {agent} rồi thử lại."

#: src/renderer/views/SettingsOverlay/parts/AcpRegistrySettings.tsx
msgid "{agentLabel} install targets"
msgstr "{agentLabel} mục tiêu cài đặt"
Expand Down
4 changes: 4 additions & 0 deletions src/renderer/locales/zh-CN/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,10 @@ msgstr "已选择 {0}/{1} 个"
msgid "{0}mo ago"
msgstr "{0}个月前"

#: src/renderer/i18n/sharedMessages.ts
msgid "{agent} reported authentication success, but Poracode could not verify it. Configure {agent} directly, then try again."
msgstr "{agent} 报告身份验证成功,但 Poracode 无法验证。请直接配置 {agent},然后重试。"

#: src/renderer/views/SettingsOverlay/parts/AcpRegistrySettings.tsx
msgid "{agentLabel} install targets"
msgstr "{agentLabel}安装目标"
Expand Down
28 changes: 20 additions & 8 deletions src/renderer/state/agentStatusesStore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,22 +45,24 @@ function reset() {
beforeEach(reset);

describe("persisted agent status cache", () => {
it("invalidates v7 statuses cached before Codex context-window capabilities", async () => {
it("invalidates v8 statuses cached before successful ACP sessions established auth", async () => {
const options = useAgentStatusesStore.persist.getOptions();
expect(options.version).toBe(8);
const staleCodex = makeStatus({
kind: "codex",
label: "Codex",
expect(options.version).toBe(10);
const staleAcp = makeStatus({
kind: "acp-generic:example",
label: "Example ACP",
authState: "missing",
authMethods: [{ id: "login", name: "Login" }],
capabilities: { ...makeStatus().capabilities },
});
const migrated = await options.migrate!(
{
agentStatuses: [staleCodex],
agentStatuses: [staleAcp],
wslAgentStatuses: [],
windowsLoaded: true,
wslLoaded: true,
},
7,
8,
);
expect(migrated).toMatchObject({
agentStatuses: [],
Expand All @@ -72,7 +74,7 @@ describe("persisted agent status cache", () => {

it("invalidates v6 statuses produced without the Grok login-shell environment", async () => {
const options = useAgentStatusesStore.persist.getOptions();
expect(options.version).toBe(8);
expect(options.version).toBe(10);
expect(options.migrate).toBeTypeOf("function");

const grok = makeStatus({
Expand Down Expand Up @@ -156,6 +158,16 @@ describe("setAgentStatuses", () => {
);
});

it("replaces the array when ACP session readiness changes", () => {
const cached = makeStatus({ authState: "unknown" });
const fresh = makeStatus({ authState: "unknown", acpSessionEstablished: true });
useAgentStatusesStore.setState({ agentStatuses: [cached], windowsLoaded: true });

useAgentStatusesStore.getState().setAgentStatuses([fresh]);

expect(useAgentStatusesStore.getState().agentStatuses).toEqual([fresh]);
});

it("replaces the array when only a presentation capability catalog changes", () => {
const cached = makeStatus({
capabilities: {
Expand Down
11 changes: 6 additions & 5 deletions src/renderer/state/agentStatusesStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ function statusesEqual(a: AgentStatus[], b: AgentStatus[]): boolean {
x.icon === b[i]!.icon &&
x.version === b[i]!.version &&
x.authState === b[i]!.authState &&
x.acpSessionEstablished === b[i]!.acpSessionEstablished &&
areAgentPresentationRuntimeFieldsEqual(x, b[i]!) &&
x.loginCommand === b[i]!.loginCommand &&
x.envKind === b[i]!.envKind &&
Expand Down Expand Up @@ -254,11 +255,11 @@ export const useAgentStatusesStore = create<AgentStatusesStore>()(
}),
{
name: "poracode-agent-statuses-v1",
version: 8,
// v8 drops statuses cached before Codex advertised selectable context
// windows. This mirrors the supervisor STATUS_CACHE_VERSION=11 bump, which
// only invalidates the supervisor's on-disk cache, not this localStorage
// copy.
version: 10,
// v10 adds ACP session readiness separately from authentication and
// normalized ACP approval-policy labels. This mirrors the supervisor
// STATUS_CACHE_VERSION=13 bump, which only invalidates the supervisor's
// on-disk cache, not this localStorage copy.
migrate: (persisted) => {
const prev = (persisted ?? {}) as Partial<AgentStatusesStore>;
return {
Expand Down
1 change: 1 addition & 0 deletions src/renderer/state/slices/agentStatusesSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ function statusesEqual(a: AgentStatus[], b: AgentStatus[]): boolean {
x.icon === b[i]!.icon &&
x.version === b[i]!.version &&
x.authState === b[i]!.authState &&
x.acpSessionEstablished === b[i]!.acpSessionEstablished &&
areAgentPresentationRuntimeFieldsEqual(x, b[i]!) &&
x.loginCommand === b[i]!.loginCommand &&
areAgentProviderMetadataEqual(x.providerMetadata, b[i]!.providerMetadata),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1000,6 +1000,34 @@ describe("AcpRegistrySettings", () => {
await waitFor(() => expect(bridge.refreshAgentStatuses).toHaveBeenCalled());
});

it("does not infer login is required from auth methods after ACP session setup", async () => {
settingsState.acpRegistryInstalledAgents = {
"glm-acp-agent": {
id: "glm-acp-agent",
name: "GLM Agent",
version: "1.1.3",
installedAt: new Date(0).toISOString(),
adapterKind: "acp-generic:glm-acp-agent",
installKind: "generic",
},
};
statusesState.agentStatuses = [
makeStatus("acp-generic:glm-acp-agent", {
label: "GLM Agent",
authState: "unknown",
acpSessionEstablished: true,
authMethods: [{ id: "sso", name: "SSO" }],
}),
];

render(<AcpRegistrySettings />);

await screen.findByRole("heading", { name: "Agent Registry" });
const glmCard = screen.getByText("GLM through ACP").closest(".rounded-lg");
expect(glmCard).toBeTruthy();
expect(within(glmCard as HTMLElement).queryByRole("button", { name: "Login" })).toBeNull();
});

it("runs Factory agent-owned auth from its native card", async () => {
statusesState.agentStatuses = [
makeStatus("factory", {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
type Project,
type RefreshAgentScope,
} from "@/shared/contracts";
import { friendlyError } from "@/shared/messages";
import { msg } from "@lingui/core/macro";
import { isWindows, readBridge } from "@/renderer/bridge";
import { i18n } from "@/renderer/i18n/i18n";
Expand Down Expand Up @@ -382,7 +383,7 @@ export function AcpRegistrySettings(props: { onOpenAgentSettings?: (kind: string
}),
)
.catch((err: unknown) => {
setError(err instanceof Error ? err.message : String(err));
setError(err instanceof Error ? friendlyError(err) : String(err));
})
.finally(() => setPendingAuthAgentId(undefined));
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1167,6 +1167,52 @@ describe("SingleAgentSettings", () => {
expect(within(row).getByRole("button", { name: "Login" })).toBeInTheDocument();
});

it("does not request login when ACP session setup succeeded without proving auth", () => {
statusesState.agentStatuses = [
makeStatus("acp-generic:ready-agent", {
label: "Ready Agent",
authState: "unknown",
acpSessionEstablished: true,
authMethods: [{ id: "login", name: "Login" }],
}),
];

render(<SingleAgentSettings agentKind="acp-generic:ready-agent" />);

const row = envRow("Default");
expect(within(row).queryByRole("button", { name: "Login" })).not.toBeInTheDocument();
expect(screen.queryByText("Login required")).not.toBeInTheDocument();
});

it("keeps login required for an unready WSL env when native ACP setup succeeded", () => {
statusesState.agentStatuses = [
makeStatus("acp-generic:ready-agent", {
label: "Ready Agent",
authState: "unknown",
acpSessionEstablished: true,
authMethods: [{ id: "login", name: "Login" }],
envKind: "windows",
}),
];
statusesState.wslAgentStatuses = [
makeStatus("acp-generic:ready-agent", {
label: "Ready Agent",
authState: "unknown",
authMethods: [{ id: "login", name: "Login" }],
envKind: "wsl",
envDistro: "Ubuntu",
}),
];

render(<SingleAgentSettings agentKind="acp-generic:ready-agent" />);

expect(screen.getAllByText("Login required").length).toBeGreaterThan(0);
expect(within(envRow("Windows")).queryByRole("button", { name: "Login" })).toBeNull();
expect(
within(envRow("WSL (Ubuntu)")).getByRole("button", { name: "Login WSL (Ubuntu)" }),
).toBeVisible();
});

it("offers logout (not re-login) for an authenticated ACP agent env", async () => {
statusesState.agentStatuses = [
makeStatus("acp-generic:sso-agent", {
Expand Down
Loading