Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
fa00ec3
chore: add the missing desktop release changesets
elkaix Aug 16, 2026
525fc27
feat(site): rework the hero as a dark desktop-first section
elkaix Aug 16, 2026
65a4f6c
test(desktop): match the desktop anchor without depending on its elem…
elkaix Aug 16, 2026
3670f27
fix(web): make dark-mode secondary text legible on the desktop sidebar
elkaix Aug 16, 2026
774aca7
fix(web): keep the new-session dialog inside its own border box
elkaix Aug 16, 2026
3cdf7b3
fix(web): bound the model quick-switch dropdown to the space above it…
elkaix Aug 16, 2026
a2964e2
fix(desktop): paint an opaque window on every platform
elkaix Aug 16, 2026
87a5a77
feat(site): enhance desktop hero
elkaix Aug 16, 2026
5e55aac
chore(site): remove open-source and MIT license mentions
elkaix Aug 16, 2026
2085cf7
feat(web): adopt the DeepSeek palette and animate the mascot from APNGs
elkaix Aug 16, 2026
146eb66
refactor(web): render every mascot state from an APNG
elkaix Aug 16, 2026
e0467f6
feat(desktop): repaint both palettes and restore the macOS vibrancy w…
elkaix Aug 16, 2026
a7f26ad
fix(web): float the latest-messages pill above the composer
elkaix Aug 16, 2026
085449d
feat(desktop): draw the macOS window chrome in the renderer
elkaix Aug 16, 2026
7a13f4a
fix(desktop): restore the native macOS window frame and sync the mate…
elkaix Aug 16, 2026
055e0ac
feat(web): adopt the DeepSeek design token sheets
elkaix Aug 16, 2026
3a0cb6a
feat: document the desktop app and remove the legacy migration
elkaix Aug 16, 2026
ab8442a
feat(web): add the general session mode and refine the desktop chrome
elkaix Aug 16, 2026
d539a21
chore(site): remove the marketing site
elkaix Aug 17, 2026
5c949d7
fix(agent-core): harden collaboration tool inputs
elkaix Aug 17, 2026
5b2e1b8
fix: repair fallout from the legacy migration removal
elkaix Aug 17, 2026
d2800a0
ci: pin release workflow actions to commit SHAs
elkaix Aug 17, 2026
e6d603a
feat(web): select a thinking effort level from the model menu
elkaix Aug 17, 2026
8b1f04c
fix(web): drop the stale attachment chip theme overrides
elkaix Aug 17, 2026
ec98391
docs(web): describe the prompt anchor rail as it is built
elkaix Aug 17, 2026
667e640
chore: remove the last references to the deleted marketing site
elkaix Aug 17, 2026
d441c63
test(agent-core): treat the general profile prompt as templated
elkaix Aug 17, 2026
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 .agents/skills/gen-changesets/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ description: Use when generating changesets in the pythinker-code repository, in

- `@pymodel/pythinker-code`: the CLI

All other `@pymodel/*` packages are treated as internal packages, including `@pymodel/pythinker-code-sdk`, `agent-core`, `kosong`, `kaos`, `pythinker-code-oauth`, `pythinker-telemetry`, and `migration-legacy`.
All other `@pymodel/*` packages are treated as internal packages, including `@pymodel/pythinker-code-sdk`, `agent-core`, `kosong`, `kaos`, `pythinker-code-oauth`, and `pythinker-telemetry`.

## Core Rules

Expand Down
2 changes: 0 additions & 2 deletions .changeset/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,12 @@ All other workspace packages are private internal packages, are not published to
- `@pymodel/pythinker-telemetry`
- `@pymodel/pythinker-web`
- `@pymodel/kosong`
- `@pymodel/migration-legacy`
- `@pymodel/protocol`
- `@pymodel/server`
- `@pymodel/server-e2e`
- `@pymodel/dashboard`
- `@pymodel/dashboard-server`
- `@pymodel/dashboard-web`
- `pythinker-migration-legacy`

Version impact from internal dependencies must be judged manually. The published artifacts for CLI and SDK bundle internal workspace packages into the artifact itself; runtime `dependencies` of published packages must not include any `@pymodel/*` internal workspace packages.

Expand Down
4 changes: 1 addition & 3 deletions .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,12 @@
"@pymodel/pythinker-telemetry",
"@pymodel/pythinker-web",
"@pymodel/kosong",
"@pymodel/migration-legacy",
"@pymodel/protocol",
"@pymodel/server",
"@pymodel/server-e2e",
"@pymodel/dashboard",
"@pymodel/dashboard-server",
"@pymodel/dashboard-web",
"pythinker-migration-legacy"
"@pymodel/dashboard-web"
],
"snapshot": {
"useCalculatedVersion": true,
Expand Down
6 changes: 6 additions & 0 deletions .changeset/dark-sidebar-contrast.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@pymodel/pythinker-code": patch
"@pymodel/pythinker-desktop": patch
---

Make the workspace header, session timestamps, and the settings row legible in dark mode on the translucent desktop sidebar.
5 changes: 5 additions & 0 deletions .changeset/desktop-dedicated-update-channel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@pymodel/pythinker-desktop": patch
---

Publish desktop releases to a dedicated update channel so update checks resolve a desktop build instead of an unrelated release, and fail the release when a packaged build carries no update feed.
5 changes: 5 additions & 0 deletions .changeset/desktop-pin-host-port.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@pymodel/pythinker-desktop": patch
---

Pin the Host port so the desktop app reconnects to its own Host, and stop reporting builds that cannot self-update as update errors.
6 changes: 6 additions & 0 deletions .changeset/general-session-mode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@pymodel/agent-core": minor
"@pymodel/protocol": minor
---

Add a general non-coding session mode for research and connected tools.
7 changes: 7 additions & 0 deletions .changeset/model-catalog-thinking-metadata.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@pymodel/protocol": minor
"@pymodel/agent-core": minor
"@pymodel/pythinker-web": minor
---

The model catalog now carries per-model thinking-effort levels and the adaptive-thinking flag to API clients.
6 changes: 6 additions & 0 deletions .changeset/remove-legacy-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@pymodel/pythinker-code': minor
---

Remove the legacy pythinker-cli migration: the `pythinker migrate` subcommand, the first-launch
migration prompt, and the `[imported]` session badge.
32 changes: 16 additions & 16 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,15 @@ jobs:
id-token: write # Required for NPM Trusted Publishing (OIDC)
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # pinned from v6.0.2
with:
fetch-depth: 0

- name: Setup pnpm
uses: pnpm/action-setup@v6
uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # pinned from v6

- name: Setup Node.js
uses: actions/setup-node@v6
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # pinned from v6
with:
node-version-file: .nvmrc
cache: "pnpm"
Expand All @@ -93,14 +93,14 @@ jobs:

- name: Mint release-bot token
id: release-bot
uses: actions/create-github-app-token@v2
uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # pinned from v2
with:
app-id: ${{ vars.RELEASE_BOT_APP_ID }}
private-key: ${{ secrets.RELEASE_BOT_PRIVATE_KEY }}

- name: Create Release Pull Request or Publish to npm
id: changesets
uses: changesets/action@v1
uses: changesets/action@a45c4d594aa4e2c509dc14a9f2b3b67ba3780d0d # pinned from v1
with:
publish: node scripts/release/changeset-publish-idempotent.mjs
version: pnpm run version:release
Expand Down Expand Up @@ -142,13 +142,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # pinned from v6.0.2

- name: Setup pnpm
uses: pnpm/action-setup@v6
uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # pinned from v6

- name: Setup Node.js
uses: actions/setup-node@v6
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # pinned from v6
with:
node-version-file: .nvmrc
cache: "pnpm"
Expand Down Expand Up @@ -196,7 +196,7 @@ jobs:

- name: Upload VSIX artifacts
if: always()
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # pinned from v7
with:
name: pythinker-code-vsix
path: apps/vscode/artifacts/vsix/*.vsix
Expand Down Expand Up @@ -296,12 +296,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # pinned from v6.0.2
with:
fetch-depth: 0

- name: Setup Node.js
uses: actions/setup-node@v6
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # pinned from v6
with:
node-version-file: .nvmrc

Expand All @@ -318,10 +318,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # pinned from v6.0.2

- name: Setup Node.js
uses: actions/setup-node@v6
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # pinned from v6
with:
node-version-file: .nvmrc

Expand All @@ -330,7 +330,7 @@ jobs:
# Cloning and pushing the tap needs contents and nothing else.
- name: Mint tap token
id: tap-token
uses: actions/create-github-app-token@v2
uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # pinned from v2
with:
app-id: ${{ vars.RELEASE_BOT_APP_ID }}
private-key: ${{ secrets.RELEASE_BOT_PRIVATE_KEY }}
Expand Down Expand Up @@ -388,10 +388,10 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # pinned from v6.0.2

- name: Download native artifacts
uses: actions/download-artifact@v8
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # pinned from v8
with:
pattern: pythinker-code-native-*
path: dist-native-release
Expand Down
3 changes: 0 additions & 3 deletions apps/dashboard/server/src/lib/session-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ export async function readSessionDetail(home: string, sessionId: string): Promis
const index = await readSessionIndex(home);
const workDir = index.get(sessionId)?.workDir ?? '';
const state = await readState(sessionDir);
if (state.custom['imported_from_pythinker_cli'] === true) return null;
const agents = await inventoryAgents(sessionDir, state);
return { sessionId, sessionDir, workDir, state, agents };
}
Expand All @@ -65,8 +64,6 @@ async function tryReadSummary(
}
throw error;
}
if (state.custom['imported_from_pythinker_cli'] === true) return null;

try {
const agents = await inventoryAgents(sessionDir, state);
const main = agents.find((agent) => agent.agentId === 'main');
Expand Down
13 changes: 0 additions & 13 deletions apps/dashboard/server/test/lib/session-store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,19 +65,6 @@ describe('session-store', () => {
expect(sessions[0]!.workDir).toBe('');
});

it('skips imported_from_pythinker_cli sessions', async () => {
const { home, sessionDir, cleanup: c } = await buildSessionFixture('sample-main');
cleanup = c;
// mark as imported
const { readFile, writeFile } = await import('node:fs/promises');
const { join } = await import('node:path');
const state = JSON.parse(await readFile(join(sessionDir, 'state.json'), 'utf8'));
state.custom = { imported_from_pythinker_cli: true };
await writeFile(join(sessionDir, 'state.json'), JSON.stringify(state));
const sessions = await listSessions(home);
expect(sessions).toHaveLength(0);
});

it('marks a session incompatible when its wire file cannot be scanned', async () => {
const { home, sessionDir, cleanup: c } = await buildSessionFixture('sample-main');
cleanup = c;
Expand Down
12 changes: 1 addition & 11 deletions apps/dashboard/web/src/components/state/StateTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ interface StateJsonShape {
createdAt?: string;
updatedAt?: string;
agents?: Record<string, unknown>;
custom?: Record<string, unknown> & { imported_from_pythinker_cli?: boolean };
custom?: Record<string, unknown>;
}

/** State tab — renders the raw `state.json` blob from session detail.
Expand All @@ -33,8 +33,6 @@ export function StateTab({ state }: StateTabProps) {
const createdMs = parseIso(s.createdAt);
const updatedMs = parseIso(s.updatedAt);
const agentIds = s.agents !== undefined ? Object.keys(s.agents) : [];
const importedFromPythinkerCli = s.custom?.imported_from_pythinker_cli === true;

return (
<div className="min-h-0 flex-1 overflow-y-auto p-4">
<div className="flex items-center justify-between">
Expand All @@ -44,14 +42,6 @@ export function StateTab({ state }: StateTabProps) {
<CopyButton value={JSON.stringify(s, null, 2)} label="copy json" />
</div>

{importedFromPythinkerCli ? (
<div className="mt-3 border border-[var(--color-sev-warning)] bg-[color-mix(in_oklab,var(--color-sev-warning)_10%,transparent)] px-3 py-2 font-mono text-[11px] text-[var(--color-sev-warning)]">
warning · this session is marked
<code className="mx-1 px-1 bg-surface-0">imported_from_pythinker_cli</code>
and would normally be filtered out of the list.
</div>
) : null}

{/* Highlight cards */}
<div className="mt-3 grid grid-cols-1 gap-2 md:grid-cols-2">
<Card label="title">
Expand Down
34 changes: 9 additions & 25 deletions apps/desktop/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
ipcMain,
Menu,
nativeImage,
nativeTheme,
session,
shell,
Tray,
Expand Down Expand Up @@ -38,6 +39,7 @@ import {
quitAndInstallNow,
setAutoUpdate,
} from './updater'
import { windowAppearanceOptions } from './window-options'
import { createDesktopLifecycle, type DesktopLifecycle } from './window-lifecycle'

const APP_NAME = 'Pythinker'
Expand Down Expand Up @@ -192,31 +194,7 @@ async function createMainWindow(): Promise<BrowserWindow> {
minWidth: 960,
minHeight: 640,
show: false,
autoHideMenuBar: true,
frame: process.platform === 'win32',
titleBarStyle: process.platform === 'darwin' ? 'hiddenInset' : 'hidden',
...(process.platform === 'darwin' ? {} : {
titleBarOverlay: {
color: '#00000000',
symbolColor: '#7f858f',
height: 44,
},
}),
...(process.platform === 'darwin' ? {
trafficLightPosition: { x: 16, y: 18 },
vibrancy: 'sidebar' as const,
visualEffectState: 'followWindow' as const,
} : {}),
// Windows uses an opaque window so theme colors do not blend with desktop wallpaper.
...(process.platform === 'win32' ? {
backgroundColor: '#0d1117',
hasShadow: true,
roundedCorners: true,
thickFrame: true,
} : {
transparent: true,
backgroundColor: '#00000000',
}),
...windowAppearanceOptions(process.platform),
title: APP_NAME,
webPreferences: {
contextIsolation: true,
Expand Down Expand Up @@ -271,6 +249,12 @@ ipcMain.handle('pythinker:update:install', (event) => {
assertTrustedSender(event)
return quitAndInstallNow()
})
ipcMain.handle('pythinker:theme:set-source', (event, source: unknown) => {
assertTrustedSender(event)
if (source === 'dark' || source === 'light' || source === 'system') {
nativeTheme.themeSource = source
}
})

function createTray(images: TrayImages): void {
tray = new Tray(images.idle)
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/src/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ contextBridge.exposeInMainWorld('pythinkerDesktop', {
setAutoUpdate: (enabled: boolean) => ipcRenderer.invoke('pythinker:update:set-auto', enabled),
checkForUpdates: () => ipcRenderer.invoke('pythinker:update:check'),
quitAndInstall: () => ipcRenderer.invoke('pythinker:update:install'),
setThemeSource: (source: 'dark' | 'light' | 'system') =>
ipcRenderer.invoke('pythinker:theme:set-source', source),
onUpdateState: (cb: (state: unknown) => void) => {
const listener = (_event: unknown, state: unknown) => cb(state)
ipcRenderer.on('pythinker:update:state', listener)
Expand Down
33 changes: 33 additions & 0 deletions apps/desktop/src/window-options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { BrowserWindowConstructorOptions } from 'electron'

export function windowAppearanceOptions(platform: NodeJS.Platform): BrowserWindowConstructorOptions {
if (platform === 'darwin') {
return {
autoHideMenuBar: true,
titleBarStyle: 'hiddenInset',
trafficLightPosition: { x: 16, y: 16 },
// A transparent NSWindow is non-opaque with a clear background, which
// removes native corners and shadow. Vibrancy does not require it.
vibrancy: 'sidebar',
visualEffectState: 'followWindow',
}
}
if (platform === 'win32') {
return {
autoHideMenuBar: true,
titleBarStyle: 'hidden',
titleBarOverlay: { color: '#00000000', symbolColor: '#7f858f', height: 44 },
backgroundColor: '#161616',
hasShadow: true,
roundedCorners: true,
thickFrame: true,
}
}
return {
autoHideMenuBar: true,
frame: false,
titleBarStyle: 'hidden',
titleBarOverlay: { color: '#00000000', symbolColor: '#7f858f', height: 44 },
backgroundColor: '#161616',
}
}
18 changes: 0 additions & 18 deletions apps/desktop/tests/packaging-config.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,24 +73,6 @@ describe('desktop packaging configuration', () => {
})
})

it('keeps desktop download URLs derived from their published release version', () => {
const siteSource = readFileSync(resolve(repositoryRoot, 'apps/site/src/App.vue'), 'utf8')
const desktopVersionMatch = siteSource.match(/const DESKTOP_VERSION = '([^']+)'/)

expect(desktopVersionMatch).not.toBeNull()
expect(desktopVersionMatch![1]).not.toBe('')
expect(siteSource).not.toContain('Pythinker-0.1.0-arm64.dmg')
expect(siteSource).not.toContain('Pythinker-0.1.0-x64-Setup.exe')
expect(siteSource).toContain('Pythinker-${DESKTOP_VERSION}-arm64.dmg')
expect(siteSource).toContain('Pythinker-${DESKTOP_VERSION}-x64-Setup.exe')
expect(siteSource).toContain('releases/download/v${DESKTOP_VERSION}')
expect(siteSource).not.toContain('releases/download/v0.1.0')

const desktopShowcaseMatch = siteSource.match(/<section id="desktop"[\s\S]*?<\/section>/)
expect(desktopShowcaseMatch).not.toBeNull()
expect(desktopShowcaseMatch![0]).toContain('/brand/windows11.svg')
})

it('maps the staged Host node_modules directory as the copy root', () => {
expect(desktopPackage.build.extraResources).toEqual(expect.arrayContaining([
{ from: 'resources', to: 'desktop-resources' },
Expand Down
Loading
Loading