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
12 changes: 6 additions & 6 deletions .agents/skills/desktop-brand-builder/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,22 +65,22 @@ Use explicit user-provided override values as-is after basic validation.
## Build Workflow

Use an isolated build directory under the current working directory so user
changes in the current worktree are not mutated. Default to the qwen-code desktop branch; do not clone from
`craft-agents-oss`, OpenWork, or another local checkout unless the user
explicitly asks for that source:
changes in the current worktree are not mutated. Default to the qwen-code main
branch; do not clone from `craft-agents-oss`, OpenWork, or another local
checkout unless the user explicitly asks for that source:

```bash
BUILD_ROOT="$PWD/brand-builds/<brandId>-<timestamp>"
mkdir -p "$BUILD_ROOT"
git clone --branch dragon/feat-unstable-desktop-app --single-branch \
git clone --branch main --single-branch \
https://github.com/QwenLM/qwen-code.git \
"$BUILD_ROOT/qwen-code"
cd "$BUILD_ROOT/qwen-code"
git checkout -B dragon/brand-<brandId> origin/dragon/feat-unstable-desktop-app
git checkout -B brand-<brandId> origin/main
```

If the branch fetch or checkout fails, stop and report the failure. Do not
continue as if `dragon/brand-<brandId>` was created.
continue as if `brand-<brandId>` was created.

Create a temporary `brand.json` in the build directory:

Expand Down
10 changes: 4 additions & 6 deletions apps/electron/scripts/build-dmg.sh
Original file line number Diff line number Diff line change
Expand Up @@ -150,17 +150,15 @@ if [ -n "$APPLE_SIGNING_IDENTITY" ]; then
export CSC_NAME="$CSC_NAME_CLEAN"
fi

# Add notarization if all credentials are available
# Add notarization if all credentials are available.
# electron-builder (>=24) auto-notarizes via notarytool whenever APPLE_ID,
# APPLE_APP_SPECIFIC_PASSWORD, and APPLE_TEAM_ID are present in the env.
# No `notarize:` config block or NOTARIZE flag is required (or read).
if [ -n "$APPLE_ID" ] && [ -n "$APPLE_TEAM_ID" ] && [ -n "$APPLE_APP_SPECIFIC_PASSWORD" ]; then
echo "Notarization enabled"
export APPLE_ID="$APPLE_ID"
export APPLE_TEAM_ID="$APPLE_TEAM_ID"
export APPLE_APP_SPECIFIC_PASSWORD="$APPLE_APP_SPECIFIC_PASSWORD"

# Enable notarization in electron-builder by setting env vars
# The electron-builder.yml has notarize section commented out,
# but we can enable it via environment
export NOTARIZE=true
fi

# Run electron-builder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
ChevronDown,
AlertCircle,
CornerDownRight,
GitBranch,
X,
} from 'lucide-react';
import { Icon_Home, Icon_Folder } from '@craft-agent/ui';
Expand Down Expand Up @@ -2753,15 +2754,22 @@ function WorkingDirectoryBadge({

// Fetch git branch when working directory changes
React.useEffect(() => {
let cancelled = false;
setGitBranch(null);

if (workingDirectory) {
window.electronAPI
?.getGitBranch?.(workingDirectory)
.then((branch: string | null) => {
setGitBranch(branch);
if (!cancelled) {
setGitBranch(branch);
}
});
} else {
setGitBranch(null);
}

return () => {
cancelled = true;
};
}, [workingDirectory]);

// Reset filter, refresh history, and focus input when popover opens
Expand Down Expand Up @@ -2833,6 +2841,7 @@ function WorkingDirectoryBadge({
const folderName = hasFolder
? getPathBasename(workingDirectory) || 'Folder'
: 'Work in Folder';
const visibleGitBranch = hasFolder ? gitBranch : null;

// Show reset option when a folder is selected and it differs from session folder
const showReset =
Expand All @@ -2854,6 +2863,21 @@ function WorkingDirectoryBadge({
<FreeFormInputContextBadge
icon={<Icon_Home className="h-4 w-4" />}
label={folderName}
ariaLabel={
visibleGitBranch
? `${folderName}, ${t('chat.onBranch', {
branch: visibleGitBranch,
})}`
: folderName
}
trailingContent={
visibleGitBranch ? (
<span className="inline-flex min-w-0 max-w-[120px] shrink items-center gap-1 text-muted-foreground">
<GitBranch className="h-3 w-3 shrink-0" />
<span className="truncate">{visibleGitBranch}</span>
</span>
) : undefined
}
isExpanded={isEmptySession}
hasSelection={hasFolder}
showChevron={true}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ export interface FreeFormInputContextBadgeProps {
icon: React.ReactNode
/** Label text - shown in expanded state or collapsed with selection */
label: string
/** Accessible label for the full badge when visible metadata is appended */
ariaLabel?: string
/** Optional trailing metadata shown after the label */
trailingContent?: React.ReactNode
/** Whether to show expanded state (icon + label + chevron) vs collapsed */
isExpanded?: boolean
/** Whether there's an active selection (affects collapsed state styling and shows label) */
Expand Down Expand Up @@ -45,6 +49,8 @@ export const FreeFormInputContextBadge = React.forwardRef<HTMLButtonElement, Fre
{
icon,
label,
ariaLabel,
trailingContent,
isExpanded = false,
hasSelection = false,
showChevron = false,
Expand All @@ -68,7 +74,7 @@ export const FreeFormInputContextBadge = React.forwardRef<HTMLButtonElement, Fre
<button
ref={mergedRef as React.Ref<HTMLButtonElement>}
type="button"
aria-label={label}
aria-label={ariaLabel ?? label}
onClick={onClick}
disabled={disabled}
data-tutorial={dataTutorial}
Expand Down Expand Up @@ -107,6 +113,8 @@ export const FreeFormInputContextBadge = React.forwardRef<HTMLButtonElement, Fre
)
)}

{showLabel && trailingContent}

{/* Optional chevron - only in expanded state */}
{isExpanded && showChevron && (
<ChevronDown className="h-3 w-3 opacity-50 shrink-0" />
Expand Down
6 changes: 3 additions & 3 deletions docs/plans/2026-06-08-desktop-auto-update-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ Before this change, the desktop app already had most of the runtime surface:
Update source configuration belongs in `packages/shared/src/branding.ts` with the rest of desktop brand metadata. Each brand owns its release location:

- `openwork` uses `modelstudioai/openwork`.
- `qwen-code` uses `QwenLM/qwen-code`.
- `qwen-code` uses a fixed `QwenLM/qwen-code` `desktop-latest` release download URL so desktop updates do not depend on the repository-wide GitHub latest release.

The brand config exposes a GitHub update source with `provider`, `owner`, `repo`, and `releasePageUrl`. `scripts/electron-builder-config.ts` reads it and emits the `publish` block in `apps/electron/electron-builder.generated.yml`. Runtime code reads the same brand update source for logs and fallback release-page actions.
The brand config exposes an update source plus `releasePageUrl`. GitHub sources use `provider`, `owner`, and `repo`; generic sources use `provider` and `url`. `scripts/electron-builder-config.ts` reads it and emits the `publish` block in `apps/electron/electron-builder.generated.yml`. Runtime code reads the same brand update source to decide whether packaged builds can check for updates.

## Release Flow

The existing desktop release workflow remains responsible for uploading assets to GitHub Releases. `electron-builder` should generate updater metadata, but the workflow continues to publish assets itself.
The existing desktop release workflow remains responsible for uploading assets to GitHub Releases. `electron-builder` should generate updater metadata, but the workflow continues to publish assets itself. Qwen Code publishes versioned `desktop-v*` releases for history and also clobbers the fixed `desktop-latest` release used by the generic update feed.

Expected assets include platform installers and feed files:

Expand Down
27 changes: 18 additions & 9 deletions packages/shared/src/branding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,21 @@
// Brand config type
// ---------------------------------------------------------------------------

type GitHubUpdateSource = {
provider: 'github';
owner: string;
repo: string;
releasePageUrl: string;
};

type GenericUpdateSource = {
provider: 'generic';
url: string;
releasePageUrl: string;
};

type UpdateSource = GitHubUpdateSource | GenericUpdateSource;

export interface BrandConfig {
/** Internal identifier */
id: string;
Expand All @@ -30,12 +45,7 @@ export interface BrandConfig {
/** Session viewer base URL */
viewerUrl: string;
/** Stable desktop auto-update source for packaged app builds. */
updates?: {
provider: 'github';
owner: string;
repo: string;
releasePageUrl: string;
};
updates?: UpdateSource;
/** Brand-owned external links shown in the Help menu */
helpMenuLinks: Array<{ labelKey: string; url: string; icon: string }>;
/** Brand-specific Electron resource paths, relative to apps/electron/ */
Expand Down Expand Up @@ -80,9 +90,8 @@ const QWEN_CODE_BRAND: BrandConfig = {
selfReferName: 'Qwen Code',
viewerUrl: 'https://agents.craft.do',
updates: {
provider: 'github',
owner: 'QwenLM',
repo: 'qwen-code',
provider: 'generic',
url: 'https://github.com/QwenLM/qwen-code/releases/download/desktop-latest',
releasePageUrl: 'https://github.com/QwenLM/qwen-code/releases',
},
helpMenuLinks: [
Expand Down
6 changes: 4 additions & 2 deletions scripts/build/darwin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,16 @@ export async function packageDarwin(config: BuildConfig): Promise<string> {
process.env.CSC_NAME = cscName;
}

// Add notarization if all credentials are available
// Add notarization if all credentials are available.
// electron-builder auto-notarizes via notarytool when APPLE_ID,
// APPLE_APP_SPECIFIC_PASSWORD, and APPLE_TEAM_ID are present in the env;
// no `notarize:` config block or NOTARIZE flag is required (or read).
if (
process.env.APPLE_ID &&
process.env.APPLE_TEAM_ID &&
process.env.APPLE_APP_SPECIFIC_PASSWORD
) {
console.log(' Notarization enabled');
process.env.NOTARIZE = 'true';
}

// Run electron-builder
Expand Down
17 changes: 12 additions & 5 deletions scripts/electron-builder-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,18 @@ linux.icon = BRAND.assets.linuxIcon;
linux.artifactName = artifactName;

if (BRAND.updates) {
config.publish = {
provider: BRAND.updates.provider,
owner: BRAND.updates.owner,
repo: BRAND.updates.repo,
};
if (BRAND.updates.provider === 'github') {
config.publish = {
provider: BRAND.updates.provider,
owner: BRAND.updates.owner,
repo: BRAND.updates.repo,
};
} else {
config.publish = {
provider: BRAND.updates.provider,
url: BRAND.updates.url,
};
}
} else {
delete config.publish;
}
Expand Down
2 changes: 1 addition & 1 deletion scripts/vendor-qwen-code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ async function vendorLocalCheckout(repoRoot: string): Promise<void> {
console.log(`Building Qwen Code CLI from ${repoRoot}...`);

const npm = npmCommand();
await run([npm, 'run', 'build'], repoRoot);
await run([npm, 'run', 'build', '--', '--cli-only'], repoRoot);
await run([npm, 'run', 'bundle'], repoRoot);
await run([npm, 'run', 'prepare:package'], repoRoot);

Expand Down