Skip to content

Commit 2386ac6

Browse files
xiaoche-hublefarcen
authored andcommitted
Merge main into feat/run-failure-longtail-895
Resolve i18n and run-failure guidance conflicts while preserving both the long-tail error cards and current main behavior.
2 parents bc633c2 + afec21a commit 2386ac6

271 files changed

Lines changed: 23774 additions & 2488 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/bake-plugin-previews-release.yml

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,16 @@ jobs:
4040
with:
4141
ref: ${{ github.ref }}
4242
fetch-depth: 0
43-
token: ${{ secrets.PREVIEW_BAKE_TOKEN || github.token }}
43+
# Read-only clone with the default token, but do NOT persist it as a
44+
# github.com credential. release/** is guarded by the "Protected
45+
# branches (preview/*, release/v*)" ruleset (deletion / non_fast_forward
46+
# / pull_request), so a direct push is rejected (GH013) unless the pusher
47+
# is a bypass actor. github-actions[bot] is NOT one — so the manifest
48+
# writeback below force-authenticates as open-design-bot, which IS a
49+
# bypass actor, via an explicit token URL. A persisted github.token
50+
# extraheader would override that inline token on push (see #5357), so
51+
# drop it; nothing between here and the push needs the persisted cred.
52+
persist-credentials: false
4453

4554
- name: Loop guard — skip the bake bot's own manifest commit
4655
id: guard
@@ -71,8 +80,26 @@ jobs:
7180
r2-bucket: ${{ secrets.CLOUDFLARE_R2_REPOSITORY_ASSETS_BUCKET }}
7281
r2-endpoint: ${{ secrets.CLOUDFLARE_R2_REPOSITORY_ASSETS_URL }}
7382

83+
- name: Mint the open-design-bot token for the release-branch push
84+
if: ${{ steps.guard.outputs.skip != 'true' }}
85+
id: bot
86+
# open-design-bot (app id 3640364) is a bypass actor on the
87+
# "Protected branches (preview/*, release/v*)" ruleset, so its push clears
88+
# the pull_request rule that rejects github-actions[bot] (GH013). The
89+
# release bake pushes the manifest directly (it then rides the non-squash
90+
# release back-merge to main). Minted here — after the long render, not at
91+
# job start — because an App token lives ~1h and the render can run long.
92+
uses: actions/create-github-app-token@v3.2.0
93+
with:
94+
client-id: ${{ secrets.BOT_APP_CLIENT_ID }}
95+
private-key: ${{ secrets.BOT_APP_PRIVATE_KEY }}
96+
owner: nexu-io
97+
repositories: open-design
98+
7499
- name: Commit the authoritative manifest onto the release branch
75100
if: ${{ steps.guard.outputs.skip != 'true' }}
101+
env:
102+
BOT_TOKEN: ${{ steps.bot.outputs.token }}
76103
run: |
77104
OLD=data/plugin-previews/manifest.json
78105
NEW=.tmp/plugin-previews/manifest.json
@@ -89,4 +116,7 @@ jobs:
89116
git config user.email "bot@open-design.ai"
90117
git add "$OLD"
91118
git commit -m "chore(plugin-previews): refresh baked preview manifest (release cut)"
92-
git push origin "HEAD:${GITHUB_REF#refs/heads/}"
119+
# Push as open-design-bot (a ruleset bypass actor) via an explicit token
120+
# URL. A plain `git push origin` would authenticate as github-actions[bot]
121+
# and be rejected by the release/** pull_request rule (GH013).
122+
git push "https://x-access-token:${BOT_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" "HEAD:${GITHUB_REF#refs/heads/}"

.github/workflows/bake-plugin-previews.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,23 @@ jobs:
4141
steps:
4242
- name: Checkout
4343
uses: actions/checkout@v6.0.2
44+
with:
45+
# Do NOT persist the default GITHUB_TOKEN as a github.com credential.
46+
# The rolling manifest branch is force-pushed below with the release-bot
47+
# App token (`x-access-token:${APP_TOKEN}`) precisely so the resulting PR
48+
# sync is attributed to app/open-design-release-bot and its CI runs
49+
# automatically (a github-actions[bot]-attributed pull_request run is held
50+
# as `action_required` — GitHub's bot-recursion gate — which starves
51+
# bake-plugin-previews-automerge.yml, since that reactor only approves +
52+
# enqueues on `workflow_run.conclusion == 'success'`). When checkout
53+
# persists the GITHUB_TOKEN it installs an `http.https://github.com/.extraheader`
54+
# Authorization header that overrides the inline App token on push, so the
55+
# force-push authenticates as github-actions[bot] and the rolling PR's CI
56+
# never runs without a manual "Approve and run". Nothing between here and
57+
# the push needs the persisted credential (setup-workspace only touches the
58+
# pnpm store; render/upload uses R2 creds; gh + push use the App token), so
59+
# dropping it is safe and keeps the App token the sole push credential.
60+
persist-credentials: false
4461

4562
- name: Setup workspace
4663
uses: ./.github/actions/setup-workspace

PRIVACY.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ default**, and you can turn it off at any time under **Settings → Privacy**.
1313

1414
Usage telemetry is **on by default**. On first run the app shows a privacy
1515
disclosure banner so you can see what is collected before doing anything else.
16-
It is an informed-disclosure notice with a single **I get it** acknowledgement,
17-
not an opt-in gate — and because telemetry is already enabled, the app may begin
18-
sending events (such as onboarding and UI-interaction events) from first launch.
16+
The banner asks you to choose **Share** or **Don’t share**.
17+
Choosing **Share** keeps telemetry enabled; choosing **Don’t share**
18+
turns telemetry off.
1919

20-
You stay in control: the banner footer tells you sharing is on and points you to
21-
**Settings → Privacy**, where you can turn telemetry off and toggle each category
22-
below — and you can change your decision at any time.
20+
You stay in control: the banner points you to **Settings → Privacy**, where you
21+
can turn telemetry off and toggle each category below — and you can change your
22+
decision at any time.
2323

2424
## What is collected
2525

apps/daemon/src/connectionTest.ts

Lines changed: 55 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ import {
5656
import { aihubmixHeaders } from './integrations/aihubmix.js';
5757
import type { AgentCliEnvPrefs } from './app-config.js';
5858
import type { RuntimeAgentDef } from './runtimes/types.js';
59-
import { resolveModelForAgent } from './runtimes/models.js';
6059
import { preparePromptFileForAgent, type PreparedPromptFile } from './runtimes/prompt-file.js';
6160
import { configuredAllowedInternalHosts } from './origin-validation.js';
6261
import {
@@ -76,7 +75,19 @@ import {
7675
type ProviderTestRequest,
7776
} from '@open-design/contracts/api/connectionTest';
7877
import { googleGenerateContentUrl } from './integrations/google-models.js';
79-
import { resolveAmrProfile } from './integrations/vela.js';
78+
import { readVelaCredentialRevision, resolveAmrProfile } from './integrations/vela.js';
79+
import { amrModelLoadingCache } from './runtimes/amr-model-cache.js';
80+
import { buildAmrModelCacheKey } from './runtimes/amr-model-probe.js';
81+
import {
82+
fetchVelaPresetModels,
83+
fetchVelaRemoteModelsWithRetry,
84+
} from './runtimes/defs/amr.js';
85+
import {
86+
getRememberedLiveModels,
87+
preferFreshLiveModels,
88+
resolveDefaultModelFromOptions,
89+
resolveModelForAgent,
90+
} from './runtimes/models.js';
8091

8192
export { validateBaseUrl } from '@open-design/contracts/api/connectionTest';
8293

@@ -1880,12 +1891,9 @@ function attachAgentStreamHandlers(
18801891
child,
18811892
prompt,
18821893
cwd,
1883-
// Same substitution as the chat-run path in server.ts — adapters whose
1884-
// CLI rejects the synthetic 'default' (e.g. AMR / vela, which forces
1885-
// session/set_model before session/prompt) need the def's first
1886-
// concrete fallback id here too, otherwise Test connection deadlocks
1887-
// on the same `session/set_model must be called before session/prompt`
1888-
// error the chat-run path already handles.
1894+
// Same substitution as the chat-run path in server.ts: omitted models can
1895+
// resolve to a concrete fallback, while an explicit 'default' is preserved
1896+
// so ACP runtimes can use their upstream configured default.
18891897
model: resolveModelForAgent(def as never, model ?? null, modelEnv, liveModelScope),
18901898
mcpServers: [],
18911899
send,
@@ -1963,11 +1971,41 @@ async function prepareOpenCodeConnectionTestCwd(tempDir: string): Promise<void>
19631971
}
19641972
}
19651973

1974+
async function resolveConnectionTestModelForAgent(
1975+
def: RuntimeAgentDef,
1976+
requestedModel: string | null,
1977+
env: NodeJS.ProcessEnv,
1978+
liveModelScope: string | null,
1979+
launchPath?: string | null,
1980+
): Promise<string | null> {
1981+
const resolved = resolveModelForAgent(def, requestedModel, env, liveModelScope);
1982+
if (def.id !== 'amr' || resolved !== 'default' || !launchPath) return resolved;
1983+
1984+
try {
1985+
const cacheKey = buildAmrModelCacheKey({
1986+
launchPath,
1987+
env,
1988+
credentialRevision: readVelaCredentialRevision(env),
1989+
});
1990+
const catalog = await amrModelLoadingCache.get(cacheKey, {
1991+
fetchPreset: () => fetchVelaPresetModels(launchPath, env),
1992+
fetchRemote: () => fetchVelaRemoteModelsWithRetry(launchPath, env),
1993+
});
1994+
const liveModels = preferFreshLiveModels(
1995+
catalog.models ?? [],
1996+
getRememberedLiveModels(def.id, liveModelScope),
1997+
);
1998+
return resolveDefaultModelFromOptions(liveModels) ?? resolved;
1999+
} catch {
2000+
return resolved;
2001+
}
2002+
}
2003+
19662004
async function testAgentConnectionInternal(
19672005
input: AgentConnectionInput,
19682006
): Promise<ConnectionTestResponse> {
19692007
const start = Date.now();
1970-
const model =
2008+
let model =
19712009
typeof input.model === 'string' && input.model.trim()
19722010
? input.model.trim()
19732011
: 'default';
@@ -2221,6 +2259,13 @@ async function testAgentConnectionInternal(
22212259
...baseEnv,
22222260
...(mmdRouteLaunchEnv || {}),
22232261
}, executableResolution);
2262+
model = await resolveConnectionTestModelForAgent(
2263+
def,
2264+
model,
2265+
env,
2266+
liveModelScope,
2267+
executableResolution.launchPath,
2268+
) ?? model;
22242269
const auth = await probeAgentAuthStatus(def, executableResolution.launchPath, env);
22252270
if (auth?.status === 'missing') {
22262271
// Preflight auth probe runs after binary resolution but before the
@@ -2278,7 +2323,7 @@ async function testAgentConnectionInternal(
22782323
child,
22792324
SMOKE_PROMPT,
22802325
tempDir,
2281-
input.model,
2326+
model,
22822327
env,
22832328
liveModelScope,
22842329
sink.send,

apps/daemon/src/design-systems/index.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ export type DesignSystemStaticFileDetail = {
9090

9191
export type DesignSystemPackageInfo = {
9292
manifest?: DesignSystemProjectManifest;
93+
availableFiles?: string[];
9394
sourceEvidence?: {
9495
scannedFileCount?: number;
9596
tokenCount?: number;
@@ -404,12 +405,49 @@ export async function readDesignSystemPackageInfo(
404405
if (manifest === null) return null;
405406

406407
const sourceEvidence = await readDesignSystemSourceEvidence(brandRoot, manifest);
408+
const availableFiles = await listAvailableDesignSystemPackageFiles(brandRoot, manifest);
407409
return {
408410
manifest,
411+
...(availableFiles.length > 0 ? { availableFiles } : {}),
409412
...(sourceEvidence ? { sourceEvidence } : {}),
410413
};
411414
}
412415

416+
async function listAvailableDesignSystemPackageFiles(
417+
brandRoot: string,
418+
manifest: DesignSystemProjectManifest,
419+
): Promise<string[]> {
420+
const candidates = new Set<string>(DESIGN_SYSTEM_STATIC_SYSTEM_FILES);
421+
const add = (filePath: string | undefined): void => {
422+
const cleanPath = typeof filePath === 'string' ? sanitizeRelativeFilePath(filePath) : null;
423+
if (cleanPath) candidates.add(cleanPath);
424+
};
425+
426+
add(manifest.files.design);
427+
add(manifest.files.tokens);
428+
add(manifest.files.components);
429+
add(manifest.files.designTokens);
430+
add(manifest.files.tailwind);
431+
add(manifest.usage);
432+
add(manifest.componentsManifest);
433+
for (const page of manifest.preview?.pages ?? []) add(page.path);
434+
for (const font of manifest.fonts ?? []) add(font.file);
435+
436+
const out: string[] = [];
437+
const resolvedRoot = path.resolve(brandRoot);
438+
for (const relativePath of Array.from(candidates).sort()) {
439+
const filePath = path.resolve(brandRoot, relativePath);
440+
if (filePath !== resolvedRoot && !filePath.startsWith(`${resolvedRoot}${path.sep}`)) continue;
441+
try {
442+
const stats = await stat(filePath);
443+
if (stats.isFile()) out.push(relativePath);
444+
} catch (err) {
445+
if (!isAbsenceError(err)) throw err;
446+
}
447+
}
448+
return out;
449+
}
450+
413451
/**
414452
* Structured (compiled) form of a brand's design system. Optional sibling
415453
* files alongside DESIGN.md that, when present, give agents a
@@ -1231,6 +1269,49 @@ export async function updateUserDesignSystem(
12311269
return listed.find((s) => s.id === `user:${dirId}`) ?? null;
12321270
}
12331271

1272+
// A design-system workspace project mirrors its design system's title:
1273+
// ensureUserDesignSystemWorkspaceProject re-stamps the project name from
1274+
// the registry title every time the workspace is ensured, so a rename
1275+
// applied only to the project row silently reverts on the next open.
1276+
// Renames on these projects must instead be written through to the
1277+
// design-system title — the sync then carries the new name back onto the
1278+
// project and both records agree.
1279+
export function workspaceRenameDesignSystemId(project: {
1280+
designSystemId?: string | null;
1281+
metadata?: unknown;
1282+
}): string | null {
1283+
const id = typeof project?.designSystemId === 'string' ? project.designSystemId : '';
1284+
if (!id.startsWith('user:')) return null;
1285+
const metadata = project?.metadata;
1286+
const importedFrom =
1287+
metadata && typeof metadata === 'object'
1288+
? (metadata as Record<string, unknown>).importedFrom
1289+
: undefined;
1290+
return importedFrom === 'design-system' ? id : null;
1291+
}
1292+
1293+
// 'not-applicable': the project is not a design-system workspace (or the
1294+
// name is blank) — the rename does not involve a design system at all.
1295+
// 'propagated': the bound design system's title now matches the new name.
1296+
// 'failed': the project IS bound to a user design system but the title
1297+
// could not be written through (e.g. the entry is missing on disk).
1298+
// Callers must not persist the project-row rename on 'failed' — doing so
1299+
// recreates the silent revert this write-through exists to prevent.
1300+
export type WorkspaceRenamePropagation = 'not-applicable' | 'propagated' | 'failed';
1301+
1302+
export async function propagateWorkspaceProjectRename(
1303+
root: string,
1304+
project: { designSystemId?: string | null; metadata?: unknown },
1305+
name: unknown,
1306+
): Promise<WorkspaceRenamePropagation> {
1307+
const id = workspaceRenameDesignSystemId(project);
1308+
if (!id) return 'not-applicable';
1309+
const title = typeof name === 'string' ? name.trim() : '';
1310+
if (!title) return 'not-applicable';
1311+
const updated = await updateUserDesignSystem(root, id, { title });
1312+
return updated != null ? 'propagated' : 'failed';
1313+
}
1314+
12341315
export async function linkUserDesignSystemProject(
12351316
root: string,
12361317
id: string,

apps/daemon/src/integrations/vela-errors.ts

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
export type AmrAccountErrorCode = 'AMR_AUTH_REQUIRED' | 'AMR_INSUFFICIENT_BALANCE';
1+
export type AmrAccountErrorCode =
2+
| 'AMR_AUTH_REQUIRED'
3+
| 'AMR_INSUFFICIENT_BALANCE'
4+
| 'AMR_TIER_UPGRADE_REQUIRED';
25

36
export interface AmrAccountFailure {
47
code: AmrAccountErrorCode;
58
message: string;
6-
action: 'relogin' | 'recharge';
9+
action: 'relogin' | 'recharge' | 'upgrade';
710
actionUrl?: string;
811
}
912

@@ -27,6 +30,12 @@ const AMR_AUTH_REQUIRED_MESSAGE =
2730
const AMR_INSUFFICIENT_BALANCE_MESSAGE =
2831
`AMR Cloud reported insufficient balance for this model. Recharge your AMR wallet at ${DEFAULT_AMR_RECHARGE_URL}, then retry this run.`;
2932

33+
const AMR_TIER_UPGRADE_REQUIRED_MESSAGE =
34+
'Your current AMR plan does not include this model or request type. Upgrade your AMR plan, or switch to an available model and retry.';
35+
36+
const AMR_TIER_REQUEST_KIND_NOT_ENTITLED_MESSAGE =
37+
'Your current AMR plan does not include this request type yet. Upgrade your AMR plan, or switch to a supported model and retry.';
38+
3039
function normalizeFailureText(text: string): string {
3140
return String(text || '').toLowerCase();
3241
}
@@ -74,6 +83,22 @@ export function classifyAmrAccountFailureDetails(details: unknown): AmrAccountFa
7483
};
7584
}
7685

86+
if (code === 'tier_model_not_entitled') {
87+
return {
88+
code: 'AMR_TIER_UPGRADE_REQUIRED',
89+
message: AMR_TIER_UPGRADE_REQUIRED_MESSAGE,
90+
action: 'upgrade',
91+
};
92+
}
93+
94+
if (code === 'tier_request_kind_not_entitled') {
95+
return {
96+
code: 'AMR_TIER_UPGRADE_REQUIRED',
97+
message: AMR_TIER_REQUEST_KIND_NOT_ENTITLED_MESSAGE,
98+
action: 'upgrade',
99+
};
100+
}
101+
77102
return null;
78103
}
79104

@@ -114,6 +139,22 @@ export function classifyAmrAccountFailure(text: string): AmrAccountFailure | nul
114139
};
115140
}
116141

142+
if (value.includes('tier_model_not_entitled')) {
143+
return {
144+
code: 'AMR_TIER_UPGRADE_REQUIRED',
145+
message: AMR_TIER_UPGRADE_REQUIRED_MESSAGE,
146+
action: 'upgrade',
147+
};
148+
}
149+
150+
if (value.includes('tier_request_kind_not_entitled')) {
151+
return {
152+
code: 'AMR_TIER_UPGRADE_REQUIRED',
153+
message: AMR_TIER_REQUEST_KIND_NOT_ENTITLED_MESSAGE,
154+
action: 'upgrade',
155+
};
156+
}
157+
117158
if (
118159
value.includes('auth_required') ||
119160
value.includes('authentication required') ||

0 commit comments

Comments
 (0)