Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
fa83431
feat(desktop): hand off live terminals for private user input
testikun Sep 26, 2026
4256bfc
fix(desktop): clarify private terminal feedback and agent discovery
testikun Sep 26, 2026
c7f5473
merge: refresh terminal handoff on main
testikun Sep 28, 2026
6a8e6f3
style: format terminal handoff sources
testikun Sep 28, 2026
4adb5b1
test(runtime-host): refresh terminal handoff tool contract
testikun Sep 28, 2026
39601af
fix(runtime): fence private PTY writes on fd reuse
testikun Sep 28, 2026
b87ec54
Merge remote-tracking branch 'upstream/main' into codex/issue-5309-te…
testikun Sep 28, 2026
43f952b
test(runtime): register PTY fd reuse coverage
testikun Sep 28, 2026
1ced798
refactor(desktop): keep session event debt flat
testikun Sep 28, 2026
83e6972
fix: bind private terminal input to PTY lifetime and retain review su…
testikun Sep 28, 2026
77be37d
fix(desktop): simplify terminal completion and let users inspect priv…
testikun Sep 28, 2026
18400ac
Merge upstream/main and regenerate the Windows test inventory
testikun Sep 28, 2026
9df75fc
fix(desktop): simplify terminal handoff and remove manual sharing
testikun Sep 28, 2026
5017885
fix(desktop): simplify terminal handoff restoration and controls
testikun Sep 28, 2026
f6d1fe7
test(desktop): capture handoff details and follow-up turns
testikun Sep 28, 2026
5d90d87
docs: refresh terminal handoff PR interaction screenshots
testikun Sep 28, 2026
0ae24f1
Merge main and fix terminal handoff review races
testikun Oct 8, 2026
a08c6af
Merge upstream/main into codex/issue-5309-terminal-handoff
testikun Oct 13, 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
19 changes: 10 additions & 9 deletions .github/workflows/windows-recovery.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,14 @@ on:
pull_request:
branches: [main]
paths:
- 'package-lock.json'
- 'patches/**'
- 'scripts/apply-dependency-patches.mjs'
- 'scripts/install-electron-with-retry.mjs'
- 'scripts/run-electron-installer.cjs'
- 'scripts/clean-build.mjs'
- 'scripts/clean-paths.mjs'
- 'scripts/windows-runtime-host-local-ipc-trust.ps1'
- "package-lock.json"
- "patches/**"
- "scripts/apply-dependency-patches.mjs"
- "scripts/install-electron-with-retry.mjs"
- "scripts/run-electron-installer.cjs"
- "scripts/clean-build.mjs"
- "scripts/clean-paths.mjs"
- "scripts/windows-runtime-host-local-ipc-trust.ps1"
# Every file in the import closure of the suites below that branches on
# `process.platform === 'win32'`. Generated, not curated:
# `scripts/windows-package-source-closure.test.mjs` recomputes this set
Expand Down Expand Up @@ -92,6 +92,7 @@ on:
- 'packages/runtime-host/src/protocol/skill-catalog.ts'
- 'packages/runtime-host/src/server/access-credential-store.ts'
- 'packages/runtime-host/src/server/host-resource-probe.ts'
- 'packages/runtime-host/src/server/runtime-resource-coordinator.ts'
- 'packages/runtime-host/src/server/skill-catalog-repository.ts'
- 'packages/runtime-host/src/server/skill-catalog-transaction.ts'
- 'packages/runtime/src/__tests__/owned-child-process.test.ts'
Expand Down Expand Up @@ -141,7 +142,7 @@ on:
branches: [main]
schedule:
# Offset from windows-sandbox-w0 so the Windows lanes do not overlap.
- cron: '17 8 * * *'
- cron: "17 8 * * *"
workflow_dispatch:

# Pull request pushes supersede each other, keyed on the pull request number
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,41 @@ import { buildManagedArtifactPreviewTools } from '../managed-artifact-preview-to
import { browserOriginAdmission } from '../browser/browser-origin-admission.js';
import { buildRiveWorkflowTool } from '../rive-workflow-tool.js';
import { createDesktopNativeCapabilityProvider } from '../runtime-host-native-capabilities.js';
import { clearPrivateTerminalSurfaces, drainDesktopCaptures, setPrivateTerminalSurface } from '../private-terminal-surfaces.js';

function jsonSchema(schema: Record<string, unknown>): {
jsonSchema: Record<string, unknown>;
} {
return { jsonSchema: schema };
}

test('private terminal visibility fences new computer calls and waits for in-flight capture', async () => {
let finish!: () => void;
const barrier = new Promise<void>((resolve) => { finish = resolve; });
let calls = 0;
const provider = createDesktopNativeCapabilityProvider({
browserTools: [], resolveBrowserUrl: () => 'https://example.com/', releaseBrowserSession() {},
computerUseTools: computerTools(async () => { calls++; await barrier; return { text: 'captured before private input' }; }),
releaseDesktopInteractionSession() {},
});
try {
const active = call(provider, computerFrame());
setPrivateTerminalSurface(5309, 'private-card', true);
let drained = false;
const drain = drainDesktopCaptures().then(() => { drained = true; });
await Promise.resolve();
assert.equal(drained, false);
await assert.rejects(call(provider, computerFrame()), /private terminal/);
finish();
await active;
await drain;
assert.equal(calls, 1);
clearPrivateTerminalSurfaces(5309);
await call(provider, computerFrame());
assert.equal(calls, 2);
} finally { finish(); clearPrivateTerminalSurfaces(5309); }
});

test('Artifact preview is discoverable and admitted without a pre-existing browser origin', async () => {
let invoked = false;
const provider = createDesktopNativeCapabilityProvider({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import assert from "node:assert/strict";
import { test } from "node:test";
import type { IpcHandler } from "../ipc-reconnect-policy.js";
import {
clearPrivateTerminalSurfaces,
hasPrivateTerminalSurface,
} from "../private-terminal-surfaces.js";
import { registerRuntimeHostShellRunsIpc } from "../runtime-host-shell-runs-ipc-main.js";

const identity = {
action: "ready" as const,
sessionId: "session-1",
requestId: "request-1",
controllerId: "controller-1",
};

function harness(controlTerminalHandoff: (input: unknown) => Promise<unknown>) {
const handlers = new Map<string, IpcHandler>();
const registration = registerRuntimeHostShellRunsIpc(
{
client: { controlTerminalHandoff } as never,
terminalCloses: {} as never,
sessionObserver: {
async observe() {},
async unobserve() {},
},
},
{
handle(channel, handler) {
handlers.set(channel, handler);
},
},
);
const handler = handlers.get("shell-runs:handoff");
assert.ok(handler);
const sender = {
id: 5309,
once() {},
on() {},
};
return {
invoke: (input: unknown) => handler({ sender } as never, input),
close: () => registration.close(),
};
}

test("ready rolls back the private-surface fence when Host admission fails", async () => {
clearPrivateTerminalSurfaces(5309);
const fixture = harness(async () => {
throw new Error("controller conflict");
});
try {
await assert.rejects(fixture.invoke(identity), /controller conflict/);
assert.equal(hasPrivateTerminalSurface(), false);
} finally {
await fixture.close();
clearPrivateTerminalSurfaces(5309);
}
});

test("ready retains the fence for human and resumed private displays", async () => {
clearPrivateTerminalSurfaces(5309);
const replies = [
{ status: "closed", phase: "closed", nextSequence: 1, closure: "exited" },
{ status: "ready", phase: "human", nextSequence: 1 },
{ status: "observed", phase: "human", nextSequence: 1 },
{ status: "ready", phase: "resumed", nextSequence: 1 },
{ status: "observed", phase: "resumed", nextSequence: 1 },
];
const fixture = harness(async () => replies.shift());
try {
await fixture.invoke(identity);
assert.equal(hasPrivateTerminalSurface(), false);
await fixture.invoke(identity);
assert.equal(hasPrivateTerminalSurface(), true);
await fixture.invoke({ ...identity, action: "release" });
assert.equal(hasPrivateTerminalSurface(), false);
await fixture.invoke(identity);
assert.equal(hasPrivateTerminalSurface(), true);
await fixture.invoke({ ...identity, action: "release" });
assert.equal(hasPrivateTerminalSurface(), false);
} finally {
await fixture.close();
clearPrivateTerminalSurfaces(5309);
}
});
Loading