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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ The [website](https://maka.apache.org/en/) walks through one turn of the log and

**Apache Releases**: Maka has not made an Apache release yet. When one exists, the signed source archive will be the official release; packages distributed elsewhere are convenience artifacts. See the [downloads page](https://maka.apache.org/en/downloads/) and [`.github/ASF_SOURCE_RELEASE.md`](./.github/ASF_SOURCE_RELEASE.md) for candidate criteria, signing procedures, and verification steps.

**Desktop Nightly**: Built daily from `main` for developers and testers, for macOS on Apple Silicon and Intel, Windows x64 and Linux x64 and arm64; the Windows and Linux builds are unsigned previews. It is not an ASF release and is not intended for production use. The [downloads page](https://maka.apache.org/en/downloads/) has the installers and the platform status.
**Development testing**: To contribute or help test unreleased changes, see [CONTRIBUTING](./CONTRIBUTING.md) and the [development mailing list](https://lists.apache.org/list.html?dev@maka.apache.org). Development builds are not approved Apache releases.

**Build from source**: To compile and run Desktop, the TUI, or the CLI directly from a source checkout, see the [Build from source](#build-from-source) section below.

Expand Down
2 changes: 1 addition & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ Agent harness 的本职就是把任务做完。衡量它的标准只有一条:

**Apache Releases**:Maka 尚未发布过 Apache release。发布之后,带签名的源码包才是正式 release,其他渠道分发的包属于便利构建。候选版本的准入标准、签名路径与验包步骤见[下载页面](https://maka.apache.org/zh-CN/downloads/)与 [`.github/ASF_SOURCE_RELEASE.md`](./.github/ASF_SOURCE_RELEASE.md)。

**Desktop Nightly**:每天从 `main` 构建,面向开发者和测试者,覆盖 macOS 的 Apple Silicon 与 Intel、Windows x64、Linux x64 与 arm64;Windows 和 Linux 构建是未签名预览。它不是 ASF release,不适合生产使用。安装包与平台状态见[下载页面](https://maka.apache.org/zh-CN/downloads/)
**开发测试**:如果希望贡献代码或协助测试尚未发布的改动,请阅读[贡献指南](./CONTRIBUTING.zh-CN.md)并关注[开发邮件列表](https://lists.apache.org/list.html?dev@maka.apache.org)。开发构建不是获批的 Apache 正式版本

**从源码构建**:要从源码 checkout 直接构建并运行 Desktop、TUI 或 CLI,见下方的[从源码构建](#从源码构建)一节。

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ describe('model catalog picker helpers', () => {
{
connectionId: 'connection-ready',
connectionSlug: 'ready-second',
providerType: 'opencode-free',
providerType: 'opencode-go',
providerLabel: 'OpenCode Zen',
model: 'ready-model',
label: 'Ready',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import { openInteractiveRuntimePolicyStoresForWrite } from '@maka/storage/runtim
import { resolveConnectionModelCatalog } from '@maka/core/model-catalog';
import type { UpdateCatalogConnectionInput } from '@maka/core/runtime-policy';
import { createDesktopConnectionSettingsServices } from '../../renderer/platform/desktop/create-connection-settings-services.js';
import { defaultEnabledModelIdsWhenOmitted } from '@maka/core/llm-connections';
import type {
RuntimeHostConnectionCatalogEntry as ConnectionCatalogEntry,
RuntimeHostConnectionCatalogSnapshot as ConnectionCatalogSnapshot,
Expand All @@ -43,12 +42,7 @@ import {
} from '../runtime-host-connections-ipc-main.js';
import { normalizeCreateConnectionInputForIpc } from '../connections-ipc-validation.js';

const OPENCODE_FREE_ENABLED_MODEL_IDS: readonly string[] =
defaultEnabledModelIdsWhenOmitted('opencode-free') ?? [];

// `providerType in PROVIDER_REGISTRY` traverses the prototype chain, so an
// inherited member named a provider the build does not register. The renderer
// reaches this boundary, and what it admits is persisted.
test('refuses a prototype member posing as a provider type', () => {
for (const providerType of ['__proto__', 'toString', 'constructor', 'hasOwnProperty']) {
assert.throws(
Expand Down Expand Up @@ -513,7 +507,7 @@ test('keeps saved custom header values out of the renderer and preserves them by
]);
});

test('preserves the provider default inventory beside the recommended model', async () => {
test('creates a connection with only the explicitly selected model', async () => {
const handlers = new Map<string, (...args: unknown[]) => unknown>();
let createdModels: readonly string[] = [];
const emptyCatalog: ConnectionCatalogSnapshot = {
Expand All @@ -536,11 +530,11 @@ test('preserves the provider default inventory beside the recommended model', as
defaultTarget: null,
connections: [
{
connectionId: 'connection-free',
connectionId: 'connection-go',
revision: 1,
slug: 'opencode-free',
name: 'OpenCode Free',
providerType: 'opencode-free',
slug: 'opencode-go',
name: 'OpenCode Go',
providerType: 'opencode-go',
enabled: true,
enabledModelIds: createdModels,
catalogEntries: [],
Expand All @@ -555,22 +549,21 @@ test('preserves the provider default inventory beside the recommended model', as
createdModels = draft.enabledModelIds;
return {
kind: 'committed',
connection: { connectionId: 'connection-free', revision: 1 },
connection: { connectionId: 'connection-go', revision: 1 },
};
},
} as never,
emitConnectionListChanged() {},
});

await handlers.get('connections:create')?.({}, {
slug: 'opencode-free',
name: 'OpenCode Free',
providerType: 'opencode-free',
defaultModel: 'nemotron-3-ultra-free',
slug: 'opencode-go',
name: 'OpenCode Go',
providerType: 'opencode-go',
defaultModel: 'minimax-m3',
});

// Snapshot-derived set; assert the contract, not today's ids.
assert.deepEqual(createdModels, [...OPENCODE_FREE_ENABLED_MODEL_IDS]);
assert.deepEqual(createdModels, ['minimax-m3']);
});

test('projects the Host default target without inventing a second Connection authority', () => {
Expand Down
2 changes: 0 additions & 2 deletions apps/desktop/src/main/runtime-host-connections-ipc-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import { buildChatModelChoices } from '@maka/core/chat-model-choice';
import type { ProjectedLlmConnection } from '@maka/core/llm-connections';
import {
connectionEnabledModelIds,
defaultEnabledModelIdsWhenOmitted,
PROVIDER_REGISTRY,
providerAuthRequiresSecret,
} from '@maka/core/llm-connections';
Expand Down Expand Up @@ -222,7 +221,6 @@ export function registerRuntimeHostConnectionsIpc(
enabled: true,
enabledModelIds: connectionEnabledModelIds({
defaultModel: input.defaultModel,
enabledModelIds: defaultEnabledModelIdsWhenOmitted(input.providerType),
}),
...(modelOverrides === undefined ? {} : { modelOverrides }),
...(input.requestBodyOverlay === undefined
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,9 @@ const zhCopy = {
oauthLoadingDetail: '正在读取本机 OAuth 登录状态,读取完成前不会把未知状态显示成未登录。',
oauthUnknownDetail: '暂时无法读取本机 OAuth 登录状态;请刷新页面或重新打开设置。',
oauthWaitingDetail: '请到账号连接完成登录;登录成功后会自动出现在模型连接里。',
oauthRetired: '此登录方式已停用',
oauthRetiredDetail:
'这条连接使用的登录方式已从 Maka 移除,无法再登录,也无法用于对话。改用 Anthropic API Key 连接即可继续使用 Claude 模型;删除这条连接会一并清除本机保存的登录凭据。',
providerRetired: '此模型服务已停用',
providerRetiredDetail:
'这条连接已无法用于对话。请添加其他模型连接,并选择新的默认模型;原有对话记录会保留。',
credentialLoadingDetail: '正在读取模型凭据状态,读取完成前暂不测试连接或刷新模型。',
credentialUnknownDetail: '模型凭据状态暂时没刷新成功,已避免把未知状态显示成未登录或未配置。',
testConnection: '测试连接',
Expand Down Expand Up @@ -372,9 +372,9 @@ const zhTwCopy = {
oauthLoadingDetail: '正在讀取本機 OAuth 登入狀態,讀取完成前不會把未知狀態顯示成未登入。',
oauthUnknownDetail: '暫時無法讀取本機 OAuth 登入狀態;請重新整理頁面或重新開啟設定。',
oauthWaitingDetail: '請到帳號連線完成登入;登入成功後會自動出現在模型連線裡。',
oauthRetired: '此登入方式已停用',
oauthRetiredDetail:
'這條連線使用的登入方式已從 Maka 移除,無法再登入,也無法用於對話。改用 Anthropic API Key 連線即可繼續使用 Claude 模型;刪除這條連線會一併清除本機儲存的登入憑據。',
providerRetired: '此模型服務已停用',
providerRetiredDetail:
'這條連線已無法用於對話。請新增其他模型連線,並選擇新的預設模型;原有對話記錄會保留。',
credentialLoadingDetail: '正在讀取模型憑據狀態,讀取完成前暫不測試連線或重新整理模型。',
credentialUnknownDetail: '模型憑據狀態暫時沒重新整理成功,已避免把未知狀態顯示成未登入或未設定。',
testConnection: '測試連線',
Expand Down Expand Up @@ -598,9 +598,9 @@ const enCopy: ProviderSettingsCopy = {
oauthLoadingDetail: 'Reading the local OAuth status. An unknown state will not be shown as signed out.',
oauthUnknownDetail: 'The local OAuth status is temporarily unavailable. Refresh the page or reopen Settings.',
oauthWaitingDetail: 'Complete sign-in under account connections. The model connection appears automatically afterward.',
oauthRetired: 'This sign-in path is retired',
oauthRetiredDetail:
'The sign-in this connection uses was removed from Maka. It can no longer be signed into or used in a conversation. Add an Anthropic API key connection to keep using Claude models; deleting this connection also clears the sign-in credential stored on this machine.',
providerRetired: 'This provider is retired',
providerRetiredDetail:
'This connection can no longer be used for conversations. Add another provider and choose a new default model. Existing conversations are kept.',
credentialLoadingDetail: 'Reading model credential status. Connection tests and model refresh are paused until it finishes.',
credentialUnknownDetail: 'Model credential status could not be refreshed, so the connection is not being mislabeled as signed out or unconfigured.',
testConnection: 'Test connection',
Expand Down
12 changes: 6 additions & 6 deletions apps/desktop/src/renderer/locales/onboarding-copy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ const ONBOARDING_COPY_BY_LOCALE: UiCatalog<OnboardingCatalog> = {
},
'blocked:all_connections_retired': {
eyebrow: '连接需要处理',
title: '现有连接的登录方式已停用。',
body: '这些连接使用的登录方式已从 Maka 移除,无法再登录,也无法用于对话。添加一个新的模型连接即可继续。',
title: '现有连接的模型服务已停用。',
body: '这些连接已无法用于对话。添加一个新的模型连接即可继续。',
cta: { label: '添加模型连接' },
tone: 'destructive',
},
Expand Down Expand Up @@ -129,8 +129,8 @@ const ONBOARDING_COPY_BY_LOCALE: UiCatalog<OnboardingCatalog> = {
},
'blocked:all_connections_retired': {
eyebrow: '連線需要處理',
title: '現有連線的登入方式已停用。',
body: '這些連線使用的登入方式已從 Maka 移除,無法再登入,也無法用於對話。新增一個新的模型連線即可繼續。',
title: '現有連線的模型服務已停用。',
body: '這些連線已無法用於對話。新增一個模型連線即可繼續。',
cta: { label: '新增模型連線' },
tone: 'destructive',
},
Expand Down Expand Up @@ -178,8 +178,8 @@ const ONBOARDING_COPY_BY_LOCALE: UiCatalog<OnboardingCatalog> = {
},
'blocked:all_connections_retired': {
eyebrow: 'Connection needs attention',
title: 'The sign-in your connections use is retired.',
body: 'The sign-in these connections use was removed from Maka. They can no longer be signed into or used in a conversation. Add a new model connection to continue.',
title: 'Your model providers are retired.',
body: 'These connections can no longer be used for conversations. Add a new model connection to continue.',
cta: { label: 'Add a model connection' },
tone: 'destructive',
},
Expand Down
1 change: 0 additions & 1 deletion apps/desktop/src/renderer/onboarding-provider-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import type { ProviderType } from '@maka/core/llm-connections';

export const FIRST_RUN_PROVIDER_TYPES = [
'opencode-free',
'opencode-go',
'openai',
'anthropic',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -435,10 +435,11 @@ function ConnectionDetailInner(props: ConnectionDetailProps) {

return (
<VStack gap={8}>
{needsOAuth && (
retired ? (
<Banner status="error" role="alert" title={copy.oauthRetired} description={copy.oauthRetiredDetail} />
) : oauthLoginService ? (
{retired && (
<Banner status="error" role="alert" title={copy.providerRetired} description={copy.providerRetiredDetail} />
)}
{needsOAuth && !retired && (
oauthLoginService ? (
<OAuthReloginNotice
service={oauthLoginService}
hasSecret={hasSecret}
Expand Down Expand Up @@ -480,7 +481,7 @@ function ConnectionDetailInner(props: ConnectionDetailProps) {
title={copy.credentials}
/* One claim, not four phrasings of it: the credential never leaves this
machine. The endpoint is not a secret, so it did not need a variant. */
description={supportsApiKey ? copy.credentialsHelp : copy.credentialsHelpAccount}
description={supportsApiKey ? copy.credentialsHelp : needsOAuth ? copy.credentialsHelpAccount : undefined}
>
{/* The name row is outside the key/endpoint guard below: a connection
with neither — an OAuth subscription, say — still has a name, and
Expand Down
6 changes: 3 additions & 3 deletions apps/desktop/src/renderer/settings/provider-display-copy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,9 +295,9 @@ export const PROVIDER_DISPLAY_COPY = {
en: { name: 'OpenCode Go', description: 'Low-cost subscription to curated open coding models.', badge: 'Plan' },
},
'opencode-free': {
'zh-CN': { name: 'OpenCode Free', description: '免费匿名 OpenCode Zen 模型,无需密钥,按 IP 限速', badge: 'Free' },
'zh-TW': { name: 'OpenCode Free', description: '免費匿名 OpenCode Zen 模型,無需金鑰,按 IP 限速', badge: 'Free' },
en: { name: 'OpenCode Free', description: 'Free anonymous OpenCode Zen models — no API key, IP-limited.', badge: 'Free' },
'zh-CN': { name: 'OpenCode Free', description: '已停用:OpenCode 免费服务仅限其官方客户端使用。', badge: 'Free' },
'zh-TW': { name: 'OpenCode Free', description: '已停用:OpenCode 免費服務僅限其官方用戶端使用。', badge: 'Free' },
en: { name: 'OpenCode Free', description: 'Retired: OpenCode restricts its free tier to its own client.', badge: 'Free' },
},
commandcode: {
'zh-CN': { name: 'Command Code', description: '使用 Command Code 套餐额度,连接后自动获取模型。', badge: 'Coding' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,7 @@ export function useConnectionDetail(props: ConnectionDetailProps) {
if (!isConnectionDetailCurrent(lifecycle)) return;
if (result.ok) {
// The backend probes the enabled models first, then the provider
// fallbacks (opencode-free tries each in turn until one answers). When
// fallback. When
// the model that actually answered isn't one the user enabled, a plain
// "connection succeeded · <model>" reads as if their selection never
// took — and hides that their chosen model is currently down. Name both
Expand Down
26 changes: 25 additions & 1 deletion apps/desktop/stories/settings/provider-settings.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ type AutoOpenTarget =
| 'detail-static'
| 'detail-relay'
| 'detail-large'
| 'detail-retired'
| 'add'
| 'catalog'
| 'oauth'
Expand Down Expand Up @@ -700,12 +701,15 @@ function clickAutoOpenTarget(root: HTMLElement, target: AutoOpenTarget): boolean
|| target === 'detail-static'
|| target === 'detail-relay'
|| target === 'detail-large'
|| target === 'detail-retired'
) {
// ListItem's clickable surface is an invisible button inside the row, so
// the row is located by its slug hook and the button taken from within it.
const slug =
target === 'detail'
? 'zai-live'
: target === 'detail-retired'
? 'opencode-free'
: target === 'detail-alibaba'
? 'alibaba-token-plan-cn'
: target === 'detail-static'
Expand Down Expand Up @@ -790,7 +794,8 @@ export const EmptyProviders: Story = {
render: () => <ProviderStory bridge={createBridge({ connections: [] })} />,
play: async ({ canvasElement }) => {
await waitFor(() => {
expect(canvasElement.querySelector('.providerCatalogRow[data-provider="opencode-free"]')).not.toBeNull();
expect(canvasElement.querySelector('.providerCatalogRow[data-provider="opencode-go"]')).not.toBeNull();
expect(canvasElement.querySelector('.providerCatalogRow[data-provider="opencode-free"]')).toBeNull();
}, { timeout: 5_000 });
expect(canvasElement.querySelector('[data-maka-contract="provider-catalog"]')).toBeNull();
},
Expand All @@ -806,6 +811,25 @@ export const ConnectionDetailPage: Story = {
),
};

// Real path: after upgrading, open the retained OpenCode Free connection in Settings.
export const RetiredFreeConnection: Story = {
render: () => (
<ProviderStory
bridge={createBridge({ connections: [makeConnection({
slug: 'opencode-free', name: 'OpenCode Free', providerType: 'opencode-free',
defaultModel: 'nemotron-3-ultra-free', models: [{ id: 'nemotron-3-ultra-free' }],
})] })}
autoOpen="detail-retired"
/>
),
play: async ({ canvasElement }) => {
await waitFor(() => {
expect(within(canvasElement).getByRole('alert')).toHaveTextContent(/已停用|retired/);
});
expect(within(canvasElement).queryByRole('button', { name: /测试连接|測試連線|Test connection/ })).toBeNull();
},
};

// Real path: 设置 → 模型 → an OpenRouter connection after fetching hundreds of models.
// The fetched snapshot is local; browsing it never needs an API key.
export const LargeConnectionDetail: Story = {
Expand Down
5 changes: 4 additions & 1 deletion packages/cli/src/__tests__/acp-child-process.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,10 @@ describe('Maka ACP child process', () => {
assert.deepEqual(await harness.waitForExit(), { code: 0, signal: null });
assert.equal(harness.stderr, '');
},
{ startRuntimeHost: true },
{
startRuntimeHost: true,
model: { id: 'acp-session-fixture', thinkingLevels: [] },
},
);
});

Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/runtime-host-tui-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export async function runRuntimeHostTui(input: RunRuntimeHostTuiInput): Promise<
input.locale,
input.hostProfileId,
);
if (!configured) throw error;
if (!configured) return 0;
context = await createRuntimeHostTuiContext(contextInput);
}
let ownerAction: (() => Promise<number>) | undefined;
Expand Down
Loading
Loading