Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
9be32ca
perf(desktop): defer unused WorkHub and bot SDK initialization
M4n5ter Sep 10, 2026
e9289a6
fix(ui): release attachment previews when composers unmount
M4n5ter Sep 10, 2026
c864564
perf(runtime-host): compact completed message replay identities
M4n5ter Sep 10, 2026
d0f8b3e
fix(desktop): cancel completed display frame fallback callbacks
M4n5ter Sep 10, 2026
78f521d
perf(runtime): release retired graph epoch reconciliation snapshots
M4n5ter Sep 10, 2026
e5885fc
fix(runtime-host): detach transcript preparation close listeners
M4n5ter Sep 10, 2026
46c8211
fix(desktop): bound browser release fencing to pending acquires
M4n5ter Sep 10, 2026
34f4ea8
perf(runtime-host): compact historical root admission bodies
M4n5ter Sep 10, 2026
c66cffe
test(runtime-host): close admission fixture stores before cleanup
M4n5ter Sep 10, 2026
8908edd
perf(desktop): release activated subscription preparation roots
M4n5ter Sep 10, 2026
6eea5ba
refactor(desktop): move display scheduling into conversation ownership
M4n5ter Sep 10, 2026
f3fbce2
perf: release Zod recursive parse state after completion
M4n5ter Sep 10, 2026
0fa4626
docs: record Zod patch source license
M4n5ter Sep 10, 2026
d02caf9
perf(runtime): release processed WeChat SSE payloads while idle
M4n5ter Sep 10, 2026
31a5a44
fix(peer): bind WebRTC workers to connection lifetime
M4n5ter Sep 10, 2026
b634df8
perf(desktop): release delivery records during retry waits
M4n5ter Sep 10, 2026
e0dd3c8
perf(peer): cancel obsolete reconciliation timers
M4n5ter Sep 10, 2026
0e7aa4f
test(desktop): scope POSIX mode assertion to supported platforms
M4n5ter Sep 10, 2026
e055012
fix(ui): avoid retaining messages through unknown code languages
M4n5ter Sep 10, 2026
cd36563
fix(cli): release completed event waiters
M4n5ter Sep 10, 2026
fcd7e0a
fix(cli): share immutable text across editor undo snapshots
M4n5ter Sep 10, 2026
c809a7e
perf(core): bound Unicode truncation allocations
M4n5ter Sep 10, 2026
cb1ae62
perf(core): bound diagnostic truncation allocations
M4n5ter Sep 10, 2026
ae5d246
perf(runtime-host): release consumed peer receive buffers
M4n5ter Sep 10, 2026
4888ea8
perf(ui): defer reasoning bodies until first expansion
M4n5ter Sep 10, 2026
719b646
perf(runtime): detach truncated web search result text
M4n5ter Sep 10, 2026
f4c1a3a
perf(desktop): load update attestation dependencies on demand
M4n5ter Sep 10, 2026
5af4f26
fix(runtime): preserve cancellation through request customization
M4n5ter Sep 10, 2026
fdc1c82
perf(runtime): reuse buffered JSON request bodies
M4n5ter Sep 10, 2026
878520a
perf(computer-use): scope session cleanup metadata to live work
M4n5ter Sep 10, 2026
98e1944
fix(runtime-host): release uploads rejected after session removal
M4n5ter Sep 10, 2026
1789afe
perf(runtime-host): release retired Goal token metadata
M4n5ter Sep 10, 2026
9cbd018
test(runtime-host): close Goal fixture execution stores
M4n5ter Sep 10, 2026
40866de
perf(mcp): release settled request graphs behind blocked sends
M4n5ter Sep 10, 2026
2c282d2
perf(runtime-host): share identical turn skill inventories
M4n5ter Sep 10, 2026
3616b27
perf(runtime-host): share identical completed prompt text
M4n5ter Sep 10, 2026
c45d118
test(runtime-host): make prompt heap checks portable to Electron
M4n5ter Sep 10, 2026
3f6613e
perf(ui): detach bounded drafts from oversized input strings
M4n5ter Sep 10, 2026
1271fe4
perf(runtime): reuse private Responses continuation history
M4n5ter Sep 10, 2026
6c0eb68
perf(ui): detach capped completed stream text
M4n5ter Sep 10, 2026
0fa074d
perf(ui): simplify bounded draft string copying
M4n5ter Sep 10, 2026
be86089
perf(ui): detach oversized stream seed backings
M4n5ter Sep 10, 2026
dc8b79a
perf(runtime): detach bounded archive read results
M4n5ter Sep 10, 2026
00bbf5d
perf(runtime): release completed tool settlement batches
M4n5ter Sep 10, 2026
0dfa876
perf(ui): detach normalized sent prompt backing
M4n5ter Sep 10, 2026
848eaa8
perf(runtime): release unused plugin isolation labels
M4n5ter Sep 10, 2026
7d9bdaf
perf(runtime): materialize memory split candidates on demand
M4n5ter Sep 10, 2026
3f5b3eb
perf(runtime): bound memory localization prefix allocation
M4n5ter Sep 10, 2026
09bad37
perf(storage): bound session catalog preview allocation
M4n5ter Sep 10, 2026
bdd4fc6
perf(runtime): bound graph handoff prefix allocation
M4n5ter Sep 10, 2026
f949ea7
perf(runtime): bound child result prefix allocation
M4n5ter Sep 10, 2026
0daa01c
perf(runtime): bound workspace instruction prefix allocation
M4n5ter Sep 10, 2026
970fb26
perf(runtime): bound PTY model tail allocation
M4n5ter Sep 10, 2026
f7b3b2b
test(runtime): close shell result recovery fixture store
M4n5ter Sep 10, 2026
0ccd888
perf(runtime): consume discarded OAuth error bodies once
M4n5ter Sep 10, 2026
233e212
perf(storage): size stable read buffers to admitted files
M4n5ter Sep 10, 2026
f2585c7
perf(runtime-host): release fully consumed frame buffers
M4n5ter Sep 10, 2026
27565ac
test(runtime-host): isolate consumed buffers from shared pools
M4n5ter Sep 10, 2026
81ca6b2
perf(runtime-host): avoid recopying empty frame prefixes
M4n5ter Sep 10, 2026
67b04c4
perf(ui): initialize persisted composer history once per mount
M4n5ter Sep 10, 2026
e44d8a0
perf(runtime-host): release consumed peer byte-stream buffers
M4n5ter Sep 10, 2026
403ec16
perf(runtime): stream live PTY code-point chunks
M4n5ter Sep 10, 2026
d6f2dcf
perf(runtime-host): avoid length-only Base64 decoding
M4n5ter Sep 10, 2026
9686f2b
perf(runtime): bound research artifact character arrays
M4n5ter Sep 10, 2026
0d72fe7
perf(runtime): count edit lines without splitting text
M4n5ter Sep 10, 2026
2b497ec
perf(runtime): collect only localized edit diff windows
M4n5ter Sep 10, 2026
5bccbba
perf(runtime): collect only requested file read lines
M4n5ter Sep 10, 2026
063d915
fix(desktop): scope interaction hydration fences to pending reads
M4n5ter Sep 10, 2026
24c77a0
perf(runtime): avoid full line arrays in bounded tool output
M4n5ter Sep 10, 2026
5cb9da5
perf(runtime): bound oversized tool-line UTF-8 encoding
M4n5ter Sep 10, 2026
77aafae
perf(runtime): bound WebFetch preview UTF-8 encoding
M4n5ter Sep 10, 2026
c9635cd
perf(runtime-host): avoid duplicate resource result clones
M4n5ter Sep 11, 2026
44bc559
chore: trim memory optimization test additions
M4n5ter Sep 11, 2026
b64ec34
Merge main into perf/desktop-idle-memory
M4n5ter Sep 11, 2026
8f70f8f
fix: align memory changes with CI metadata gates
M4n5ter Sep 11, 2026
8429829
ci: include bounded line reader in Windows package inputs
M4n5ter Sep 11, 2026
ab5266c
fix(runtime): keep graph epoch handover independent of teardown
M4n5ter Sep 11, 2026
4aa9799
fix(bots): report lazy SDK initialization failures
M4n5ter Sep 11, 2026
e272198
fix(desktop): defer cold WorkHub summons until renderer ready
M4n5ter Sep 11, 2026
4bbe8fd
perf(memory): finish scoped lifecycle review follow-ups
M4n5ter Sep 11, 2026
b2c6333
test(desktop): wait for automatic naming before archive
M4n5ter Sep 11, 2026
2bb0095
test(memory): cover cancellation and recursive lifetime contracts
M4n5ter Sep 11, 2026
13f27da
test(peer): observe backpressure before testing teardown
M4n5ter Sep 11, 2026
48c5189
Merge origin/main into perf/desktop-idle-memory
M4n5ter Sep 11, 2026
6c62438
fix(ci): repin compatible allocation change to epoch 142
M4n5ter Sep 11, 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
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
# repository needs CRLF, so the working tree stays LF everywhere.
* text=auto eol=lf

# Unified diffs prefix upstream tab-indented context with a required space.
/patches/*.patch whitespace=-space-before-tab

# Repository-local agent configuration, visual review evidence, and incubation
# working notes are not source-release inputs.
/.claude export-ignore
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/release-windows-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ on:
- 'packages/runtime/src/file-stable-write.ts'
- 'packages/runtime/src/image-file.ts'
- 'packages/runtime/src/process-tree-terminator.ts'
- 'packages/runtime/src/text-line-window.ts'
- 'packages/runtime/src/unified-diff.ts'
- 'packages/core/src/absolute-path.ts'
- 'packages/core/src/artifacts.ts'
Expand Down
106 changes: 106 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,112 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Pi TUI dependency patch

Source: https://www.npmjs.com/package/@earendil-works/pi-tui/v/0.84.4
Repository: https://github.com/earendil-works/pi/tree/v0.84.4/packages/tui
Version: 0.84.4
Dependency patch: patches/@earendil-works+pi-tui+0.84.4.patch
Copyright (c) 2025 Mario Zechner
License: MIT

Maka redistributes a source patch that copies mutable editor undo state while
sharing immutable pasted strings. The following MIT License applies to that
material:

MIT License

Copyright (c) 2025 Mario Zechner

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Zod dependency patch

Source: https://www.npmjs.com/package/zod/v/4.5.4
Repository: https://github.com/colinhacks/zod
Version: 4.5.4
Dependency patch: patches/zod+4.5.4.patch
Copyright (c) 2025 Colin McDonnell
License: MIT

Maka redistributes a source patch that releases recursive parse state after
completion or failure. The following MIT License applies to that material:

MIT License

Copyright (c) 2025 Colin McDonnell

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Model Context Protocol client dependency patch

Source: https://www.npmjs.com/package/@modelcontextprotocol/client/v/2.0.0
Repository: https://github.com/modelcontextprotocol/typescript-sdk
Version: 2.0.0
Dependency patch: patches/@modelcontextprotocol+client+2.0.0.patch
Copyright (c) 2024-2025 Model Context Protocol a Series of LF Projects, LLC.
Declared package license: MIT

Maka redistributes a source patch that releases settled request observers under
transport backpressure. The upstream package's LICENSE describes a transition
from MIT to Apache-2.0: contributions with relicensing consent use Apache-2.0,
while contributions without that consent retain their original MIT license.
The Apache License, Version 2.0 is reproduced above. The upstream MIT notice is:

MIT License

Copyright (c) 2024-2025 Model Context Protocol a Series of LF Projects, LLC.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Sigstore dependency patch

Source: https://www.npmjs.com/package/@sigstore/core/v/4.0.1
Expand Down
8 changes: 4 additions & 4 deletions apps/desktop/renderer-architecture.json
Original file line number Diff line number Diff line change
Expand Up @@ -592,8 +592,7 @@
"importDeclarations": 2,
"bridgePaths": {},
"environmentCapabilities": {
"requestAnimationFrame": 1,
"window.setTimeout": 2
"window.setTimeout": 1
},
"hookCalls": {},
"lifecycleMethods": {},
Expand All @@ -603,12 +602,13 @@
"createAppShellSessionEventHandlers"
],
"dependencyPaths": {
"./features/conversation/index.js": 1,
"./locales/conversation-copy.js": 1,
"./model-connection-errors.js": 1,
"@maka/ui": 1
},
"importSpecifiers": 8,
"nonTriviaTokens": 2808
"nonTriviaTokens": 2755
},
"src/renderer/app-shell-session-start-actions.ts": {
"importDeclarations": 2,
Expand Down Expand Up @@ -872,7 +872,7 @@
"react": 1
},
"importSpecifiers": 108,
"nonTriviaTokens": 13696
"nonTriviaTokens": 13695
},
"src/renderer/use-app-shell-composer-quotes.ts": {
"importDeclarations": 2,
Expand Down
5 changes: 4 additions & 1 deletion apps/desktop/src/main/__tests__/session-local.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,10 @@ test('local acceptance survives restart with attachment bytes and an immutable d
const db = await database(t);
const record = db.store.enqueue('authority-1', intent());
assert.equal(record.state, 'saved');
assert.equal((await stat(db.path)).mode & 0o777, 0o600);
// POSIX permission bits do not describe Windows ACLs.
if (process.platform !== 'win32') {
assert.equal((await stat(db.path)).mode & 0o777, 0o600);
}
db.store.update({
...record,
state: 'sending',
Expand Down
40 changes: 38 additions & 2 deletions apps/desktop/src/main/__tests__/workhub-presentation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ test('animates from the current height, keeps the bottom anchored and survives r
const h = await harness(true);
await h.command(h.main.webContents, 'host', { visible: true, rect: { x: 0, y: 40, width: 1000, height: 760 } });
const view = h.views[0]!;
await h.command(view.webContents, 'ready');
await h.command(view.webContents, 'conversation-layout', { expanded: false, compactHeight: 110 });
await h.command(view.webContents, 'detach');
const floating = h.windows[1]!;
Expand Down Expand Up @@ -318,6 +319,7 @@ test('reparents one live conversation across docking, floating, hide and main-wi
h.main.show();
await h.command(h.main.webContents, 'host', { visible: true, rect: { x: 100, y: 40, width: 900, height: 760 } });
const view = h.views[0]!;
await h.command(view.webContents, 'ready');
await h.command(view.webContents, 'conversation-layout', { expanded: true, compactHeight: 96 });
assert.ok(h.main.children.has(view));
await h.command(view.webContents, 'detach');
Expand Down Expand Up @@ -515,6 +517,7 @@ test('all WorkHub entries obey the client enable setting and disabling retains t
await h.controller.show();
const view = h.views[0]!;
const floating = h.windows[1]!;
await h.command(view.webContents, 'ready');
assert.equal(floating.visible, true);
const opened = deferred<void>();
const opening = h.deferOpening(opened.promise);
Expand Down Expand Up @@ -550,13 +553,21 @@ test('all WorkHub entries obey the client enable setting and disabling retains t
});


test('prewarms once and the shortcut shows and hides synchronously', async () => {
test('creates on first shortcut, then shows and hides synchronously', async () => {
const h = await harness();
await h.controller.refreshSettings();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3 — apps/desktop/src/main/__tests__/workhub-presentation.test.ts:555

The rename to creates on first shortcut has nothing asserted between refreshSettings() and the first h.shortcut(). Item 01's only behavioural claim — enabling alone must not create the renderer — has no regression cover; re-adding prewarm leaves this test green.

assert.equal(h.views.length, 0, 'enabling alone must not create the renderer'). Same shape as the assertions at :492 and :508.

中文

改名成 creates on first shortcut,但 refreshSettings() 和第一次 h.shortcut() 之间什么都没断言。第 01 项唯一的行为主张——启用本身不创建渲染器——没有回归保护,把 prewarm 加回来这个测试照样绿。

assert.equal(h.views.length, 0, 'enabling alone must not create the renderer'),和 :492、:508 的断言同形状。

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

English

Accepted in e2721989f. The existing test now asserts views.length === 0 immediately after refreshSettings and before the first shortcut. It also checks that cold loading does not show/focus the window until ready, while subsequent warm toggles remain synchronous. All 24 presentation tests pass.

Automated follow-up by OpenAI Codex.

中文

接受,已在 e2721989f 的现有测试中,在 refreshSettings 后、第一次快捷键前断言 views.length === 0。还检查冷加载期间不显示/聚焦窗口、ready 后展示,以及后续热启动切换仍同步完成。24 项展示控制测试通过。

OpenAI Codex 自动跟进回复。

assert.equal(h.views.length, 0, 'enabling alone must not create the renderer');
h.shortcut();
assert.equal(h.windows[1]!.visible, false, 'a cold summon waits for the composer to mount');
assert.equal(h.windows[1]!.focused, 0, 'loading must not steal keyboard input');
await h.command(h.views[0]!.webContents, 'ready');
assert.equal(h.windows[1]!.visible, true);
assert.equal(h.views[0]!.webContents.sent.some(([channel]) => channel === 'workhub-presentation:focus-composer'), true);
h.shortcut();
const floating = h.windows[1]!;
const view = h.views[0]!;
assert.equal(floating.visible, false);
assert.equal(view.visible, false);
assert.equal(view.visible, true);
assert.ok(floating.children.has(view));
await h.controller.refreshSettings();
assert.equal(h.windows.length, 2);
Expand All @@ -570,6 +581,26 @@ test('prewarms once and the shortcut shows and hides synchronously', async () =>
h.controller.dispose();
});

test('a second shortcut or disabling cancels a cold summon before ready', async () => {
for (const cancel of ['shortcut', 'disable'] as const) {
const h = await harness();
await h.controller.refreshSettings();
h.shortcut();
const view = h.views[0]!;
const floating = h.windows[1]!;
if (cancel === 'shortcut') h.shortcut();
else {
h.setEnabled(false);
await h.controller.refreshSettings();
}
await h.command(view.webContents, 'ready');
assert.equal(floating.visible, false, cancel);
assert.equal(floating.focused, 0, cancel);
assert.equal(view.webContents.sent.some(([channel]) => channel === 'workhub-presentation:focus-composer'), false, cancel);
h.controller.dispose();
}
});

test('a pending backdrop capture and older hide cannot delay or undo the shortcut', async () => {
const h = await harness();
await h.controller.refreshSettings();
Expand Down Expand Up @@ -600,6 +631,7 @@ test('the shortcut supersedes a pending dock without waiting for the main window
await h.controller.refreshSettings();
h.shortcut();
const view = h.views[0]!;
await h.command(view.webContents, 'ready');
const floating = h.windows[1]!;
await h.command(view.webContents, 'conversation-layout', { expanded: true, compactHeight: 96 });
h.advance(80);
Expand Down Expand Up @@ -627,6 +659,7 @@ test('native resize callbacks do not submit duplicate view bounds and follow dis
const h = await harness(true, 120);
await h.controller.show();
const view = h.views[0]!;
await h.command(view.webContents, 'ready');
view.webContents.getZoomFactor = () => 2;
await h.command(view.webContents, 'conversation-layout', { expanded: true, compactHeight: 96 });
const before = view.boundsUpdates.length;
Expand Down Expand Up @@ -654,6 +687,7 @@ test('hiding returns the live view to Desktop and preserves floating geometry fo
h.shortcut();
const floating = h.windows[1]!;
const view = h.views[0]!;
await h.command(view.webContents, 'ready');
await h.command(view.webContents, 'conversation-layout', { expanded: false, compactHeight: 144 });
h.shortcut();
assert.equal(floating.visible, false);
Expand All @@ -676,6 +710,7 @@ test('hiding with Desktop closed keeps the conversation alive without reopening
const h = await harness();
await h.controller.show();
const view = h.views[0]!;
await h.command(view.webContents, 'ready');
h.main.destroy();
await h.controller.toggle();
assert.equal(h.controller.getSnapshot().placement, 'docked');
Expand Down Expand Up @@ -768,6 +803,7 @@ test('closing progress suppresses the current turn and old paint acknowledgement
test('the shortcut opens the normal composer from progress without waiting for its paint', async () => {
const h = await harness();
await h.controller.prepareControl('turn');
await h.command(h.views[0]!.webContents, 'ready');
const request = h.controller.getSnapshot().progressRequest!;
await h.controller.toggle(true);
const floating = h.windows[1]!;
Expand Down
11 changes: 8 additions & 3 deletions apps/desktop/src/main/app-update-attestation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@
* under the License.
*/

import { bundleFromJSON, type Bundle } from '@sigstore/bundle';
import { getTrustedRoot } from '@sigstore/tuf';
import { toSignedEntity, toTrustMaterial, Verifier } from '@sigstore/verify';
import type { Bundle } from '@sigstore/bundle';
import { createHash } from 'node:crypto';
import { createReadStream, readFileSync } from 'node:fs';
import { createRequire } from 'node:module';
import { basename, join } from 'node:path';

const require = createRequire(import.meta.url);

const PRODUCT_REPOSITORY = 'apache/maka';
const PRODUCT_RELEASE_WORKFLOW = '.github/workflows/release-cli-finalize.yml';
const PRODUCT_NIGHTLY_WORKFLOW = '.github/workflows/desktop-nightly.yml';
Expand Down Expand Up @@ -228,6 +229,7 @@ function parseBundle(bytes: Uint8Array): Bundle {
throw new Error('Update attestation is not valid JSON', { cause: error });
}
try {
const { bundleFromJSON } = require('@sigstore/bundle') as typeof import('@sigstore/bundle');
return bundleFromJSON(serialized as Parameters<typeof bundleFromJSON>[0]);
} catch (error) {
throw new Error('Update attestation is not a valid Sigstore bundle', { cause: error });
Expand Down Expand Up @@ -290,6 +292,9 @@ export async function verifyDownloadedUpdateAttestation(
if (options.verifyBundle) {
await options.verifyBundle(bundle);
} else {
const { getTrustedRoot } = require('@sigstore/tuf') as typeof import('@sigstore/tuf');
const { toSignedEntity, toTrustMaterial, Verifier } =
require('@sigstore/verify') as typeof import('@sigstore/verify');
const trustedRoot = await getTrustedRoot({
cachePath: options.trustRootCacheDirectory,
timeout: 10_000,
Expand Down
18 changes: 13 additions & 5 deletions apps/desktop/src/main/browser/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,11 +136,12 @@ const bySession = new Map<string, Connection>();
// so two concurrent first calls for one conversation must share one attempt
// instead of racing into a second connection (which the bridge would reject).
const pendingAcquires = new Map<string, Promise<Connection>>();
// Release epoch per conversation. A delete/archive cannot reliably see an
// Release epoch per in-flight acquire. A delete/archive cannot reliably see an
// in-flight acquire, so instead of the release waiting on the acquire, the
// acquire notices the bump after connecting and unwinds itself — otherwise its
// resolveEndpoint would resurrect the just-disposed view and the connection
// would outlive the conversation with nothing left to ever clean it up.
// would outlive the conversation with nothing left to ever clean it up. The
// entry only lives until the acquire settles, not for every released session.
const releaseEpochs = new Map<string, number>();
// In-flight actions per conversation, so the visible lease can REVOKE — not just
// preflight. canDrive gates the START on screen; this severs an action that was
Expand Down Expand Up @@ -244,8 +245,11 @@ async function acquire(sessionId: string): Promise<Connection> {
// call retries fresh; concurrent callers share the same outcome either way.
const inflight = pendingAcquires.get(sessionId);
if (inflight) return inflight;
const epoch = 0;
// Register before resolveEndpoint, which may synchronously release the session

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3 — apps/desktop/src/main/browser/session.ts:249

"Register before resolveEndpoint, which may synchronously release the session before this attempt can be registered in pendingAcquires" — the old code handled that too: epoch stays undefined, a synchronous release sets it to 1, and 1 !== undefined unwinds. The real motivation is that releaseEpochs kept one never-cleared entry per session.

The change itself is fine — there is no await between releaseEpochs.set and pendingAcquires.set, and not bumping with no acquire in flight is safe since the only reader is an in-flight acquire. Just make the comment describe the map's new lifetime instead of a correctness reason that does not hold.

中文

"Register before resolveEndpoint, which may synchronously release the session before this attempt can be registered in pendingAcquires"——旧代码同样处理得了:epoch 保持 undefined,同步 release 置 1,1 !== undefined 照样 unwind。真实动机是 releaseEpochs 每个会话留一条永不清理的记录。

改动本身没问题——releaseEpochs.setpendingAcquires.set 之间没有 await,无在飞 acquire 时不 bump 也安全,因为唯一的读者就是在飞的 acquire。只是把注释改成描述 map 的新生命周期,而不是一个不成立的正确性理由。

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

English

Keeping this comment. Agreed that the old implementation also handled synchronous release, and that the optimization removes historical map entries. The comment is not claiming an old correctness bug: it records an ordering requirement of the new pending-acquire-only representation.

Because release only advances a registered in-flight epoch now, registration must happen before a reentrant resolveEndpoint call, not after it. Describing that required order remains useful even though both implementations are correct. No functional change requested here is needed.

Automated follow-up by OpenAI Codex.

中文

保留这条注释。同意旧实现也能处理同步 release,也同意优化目的是移除历史 map 项。但注释没有声称旧实现存在正确性缺陷,而是在记录新“仅待决 acquire 持有状态”表示的顺序要求。

现在 release 只推进已注册的在飞 epoch,因此必须在可能重入的 resolveEndpoint 调用之前注册,不能放到之后。两版都正确,不影响该顺序说明仍有价值;这里无需功能修改。

OpenAI Codex 自动跟进回复。

// before this attempt can be registered in pendingAcquires.
releaseEpochs.set(sessionId, epoch);
const promise = (async () => {
const epoch = releaseEpochs.get(sessionId);
const endpoint = await browserViewHost().resolveEndpoint(sessionId);
let conn: Connection;
try {
Expand All @@ -272,7 +276,10 @@ async function acquire(sessionId: string): Promise<Connection> {
}
bySession.set(sessionId, conn);
return conn;
})().finally(() => pendingAcquires.delete(sessionId));
})().finally(() => {
pendingAcquires.delete(sessionId);
releaseEpochs.delete(sessionId);
});
pendingAcquires.set(sessionId, promise);
return promise;
}
Expand Down Expand Up @@ -412,7 +419,8 @@ export async function releaseBrowserSession(sessionId: string): Promise<void> {
// when it sees the new epoch (see acquire) — it cannot be awaited here because
// it may not have registered in pendingAcquires yet, and a hung endpoint
// resolution must not block the session's deletion.
releaseEpochs.set(sessionId, (releaseEpochs.get(sessionId) ?? 0) + 1);
const epoch = releaseEpochs.get(sessionId);
if (epoch !== undefined) releaseEpochs.set(sessionId, epoch + 1);
const conn = bySession.get(sessionId);
if (conn) {
bySession.delete(sessionId);
Expand Down
Loading
Loading