Skip to content

Commit 4496c10

Browse files
committed
Merge PR #118: stop session snapshots timing out and recover when one fails
2 parents 30e1dfc + 4508861 commit 4496c10

59 files changed

Lines changed: 2994 additions & 141 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@pymodel/pythinker-code': patch
3+
---
4+
5+
Repaint the desktop chrome. The sidebar footer now carries a pill button, so Settings and the way back out of it match New Session and stay visible. The transcript reserves room for the floating work chips instead of letting them sit on the last line. Windows gets round window controls on the trailing edge, in place of the native caption buttons that could not be styled.

.changeset/snapshot-stall.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@pymodel/pythinker-code': patch
3+
---
4+
5+
Stop the session snapshot request from timing out on busy sessions. Each recorded event no longer pays a fresh file open and close, the watermark is read without waiting for pending writes, and the session list is scanned in parallel, so opening or refreshing a session stays fast even with a long history. This was most visible on Windows, where the per-event file cost is highest.

.changeset/web-codex-login.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@pymodel/pythinker-code': minor
3+
---
4+
5+
Add OpenAI Codex sign-in to the web and desktop app. The provider dialog now offers "Sign in with ChatGPT" next to the API-key form: the server runs the OAuth exchange, writes the credentials, and reports only which model it selected. When port 1455 is taken, the dialog asks for the redirect URL instead.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@pymodel/pythinker-code': patch
3+
---
4+
5+
Recover the web and desktop app when a session snapshot request fails. It is now retried with a growing delay instead of leaving the todo list and the sub-agent list frozen until a reload, and a failed task refresh reports itself rather than failing in silence.

apps/desktop/src/main.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,24 @@ ipcMain.handle('pythinker:update:install', (event) => {
249249
assertTrustedSender(event)
250250
return quitAndInstallNow()
251251
})
252+
// Windows has no native caption buttons any more, so the renderer drives the
253+
// window. `close()` is used rather than `destroy()` so the tray lifecycle still
254+
// intercepts it and only hides the window.
255+
ipcMain.handle('pythinker:window:minimize', (event) => {
256+
assertTrustedSender(event)
257+
mainWindow?.minimize()
258+
})
259+
ipcMain.handle('pythinker:window:toggle-maximize', (event) => {
260+
assertTrustedSender(event)
261+
const window = mainWindow
262+
if (window === undefined) return
263+
if (window.isMaximized()) window.unmaximize()
264+
else window.maximize()
265+
})
266+
ipcMain.handle('pythinker:window:close', (event) => {
267+
assertTrustedSender(event)
268+
mainWindow?.close()
269+
})
252270
ipcMain.handle('pythinker:theme:set-source', (event, source: unknown) => {
253271
assertTrustedSender(event)
254272
if (source === 'dark' || source === 'light' || source === 'system') {

apps/desktop/src/preload.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ contextBridge.exposeInMainWorld('pythinkerDesktop', {
66
setAutoUpdate: (enabled: boolean) => ipcRenderer.invoke('pythinker:update:set-auto', enabled),
77
checkForUpdates: () => ipcRenderer.invoke('pythinker:update:check'),
88
quitAndInstall: () => ipcRenderer.invoke('pythinker:update:install'),
9+
minimizeWindow: () => ipcRenderer.invoke('pythinker:window:minimize'),
10+
toggleMaximizeWindow: () => ipcRenderer.invoke('pythinker:window:toggle-maximize'),
11+
closeWindow: () => ipcRenderer.invoke('pythinker:window:close'),
912
setThemeSource: (source: 'dark' | 'light' | 'system') =>
1013
ipcRenderer.invoke('pythinker:theme:set-source', source),
1114
onUpdateState: (cb: (state: unknown) => void) => {

apps/desktop/src/window-options.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ export function windowAppearanceOptions(platform: NodeJS.Platform): BrowserWindo
1414
}
1515
if (platform === 'win32') {
1616
return {
17+
// No `titleBarOverlay`: the renderer draws macOS-style round controls in
18+
// the top-right instead. `thickFrame` keeps the native resize border,
19+
// shadow and drag-to-snap that `frame: false` would remove.
1720
autoHideMenuBar: true,
1821
titleBarStyle: 'hidden',
19-
titleBarOverlay: { color: '#00000000', symbolColor: '#7f858f', height: 44 },
2022
backgroundColor: '#161616',
2123
hasShadow: true,
2224
roundedCorners: true,

apps/desktop/tests/window-appearance.spec.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,22 @@ describe('desktop window appearance configuration', () => {
1616
expect(opts['vibrancy']).toBe('sidebar')
1717
})
1818

19-
it('keeps the Windows window configuration unchanged', () => {
19+
it('leaves the Windows caption buttons to the renderer', () => {
2020
const opts = windowAppearanceOptions('win32')
2121

2222
expect(opts).toMatchObject({
2323
autoHideMenuBar: true,
2424
titleBarStyle: 'hidden',
25-
titleBarOverlay: { color: '#00000000', symbolColor: '#7f858f', height: 44 },
2625
backgroundColor: '#161616',
2726
hasShadow: true,
2827
roundedCorners: true,
2928
thickFrame: true,
3029
})
30+
// The overlay would draw native caption buttons on top of the round ones
31+
// the renderer paints, so it has to stay off.
32+
expect('titleBarOverlay' in opts).toBe(false)
33+
// `thickFrame` without `frame: false` keeps the native resize border.
34+
expect('frame' in opts).toBe(false)
3135
})
3236

3337
it('keeps the Linux window configuration unchanged', () => {

apps/pythinker-code/src/generated/dashboard-web-asset.ts

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

apps/pythinker-code/test/utils/open-url.test.ts

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,28 @@ import { describe, expect, it } from 'vitest';
22

33
import { openUrlCommandFor } from '#/utils/open-url';
44

5-
const AUTHORIZE_URL =
6-
'https://auth.openai.com/oauth/authorize?client_id=app_test&response_type=code&state=abc';
5+
const oauthUrl =
6+
'https://auth.openai.com/oauth/authorize?client_id=app_test&redirect_uri=http%3A%2F%2Flocalhost%3A1455%2Fauth%2Fcallback&state=state';
77

88
describe('openUrlCommandFor', () => {
9-
it('keeps every query parameter on Windows', () => {
10-
const { command, args } = openUrlCommandFor(AUTHORIZE_URL, 'win32');
11-
expect(command).toBe('rundll32');
12-
// `cmd /c start` cuts the URL at the first `&`, so the launcher must not
13-
// hand the URL to a command interpreter.
14-
expect(command).not.toBe('cmd');
15-
expect(args.at(-1)).toBe(AUTHORIZE_URL);
9+
it('passes the complete OAuth URL to the Windows URL handler', () => {
10+
expect(openUrlCommandFor(oauthUrl, 'win32')).toEqual({
11+
command: 'rundll32',
12+
args: ['url.dll,FileProtocolHandler', oauthUrl],
13+
});
1614
});
1715

18-
it('uses the platform launcher elsewhere', () => {
19-
expect(openUrlCommandFor(AUTHORIZE_URL, 'darwin')).toEqual({
16+
it('uses the native opener on macOS', () => {
17+
expect(openUrlCommandFor(oauthUrl, 'darwin')).toEqual({
2018
command: 'open',
21-
args: [AUTHORIZE_URL],
19+
args: [oauthUrl],
2220
});
23-
expect(openUrlCommandFor(AUTHORIZE_URL, 'linux')).toEqual({
21+
});
22+
23+
it('uses xdg-open on Linux', () => {
24+
expect(openUrlCommandFor(oauthUrl, 'linux')).toEqual({
2425
command: 'xdg-open',
25-
args: [AUTHORIZE_URL],
26+
args: [oauthUrl],
2627
});
2728
});
2829
});

0 commit comments

Comments
 (0)