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
6 changes: 5 additions & 1 deletion .github/workflows/terminal-compatibility.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ jobs:
defaults write com.googlecode.iterm2 ApplePersistenceIgnoreState -bool true
- run: bun install --frozen-lockfile
- name: Verify native automation boundary and disposable profiles
run: bun run test tests/testing/nativeTerminal.test.ts
run: bun run test tests/testing/nativeTerminal.test.ts tests/testing/resizeWriteMetadata.test.ts tests/testing/resizeStreamGate.test.ts
- run: bun run build
- name: Run Tuistory with native XCTest window and accessibility checks
run: bun run test:tuistory tests/tuistory/native-terminal.tuistory.test.ts
Expand All @@ -120,6 +120,10 @@ jobs:
.artifacts/native-terminal/**/*.xcresult
.artifacts/native-terminal/**/screenshots
.artifacts/native-terminal/**/xcodebuild.log
.artifacts/native-terminal/**/write-metadata-*.jsonl
.artifacts/native-terminal/**/write-metadata-*.jsonl.pending
.artifacts/native-terminal/**/copy-samples-*.jsonl
.artifacts/native-terminal/**/stream-control-*.json*
.artifacts/native-terminal/**/vscode-profile/**/settings.json
.artifacts/native-terminal/**/vscode-profile/**/keybindings.json
if-no-files-found: error
Expand Down
2 changes: 2 additions & 0 deletions docs/extension-authoring.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,8 @@ api.commands.register({

Every view receives `close(value?)`, `workspaceRoot`, and the command `args`, plus the properties supplied to `context.ui.open`. Autohand owns modal pause/resume, alternate-screen cleanup, Escape, and Ctrl+C behavior. Components may build menus, dialogs, custom renderers, or editor-like interfaces with normal Ink composition.

Closing a view restores the composer. Finishing, cancelling, or failing the earlier command preserves existing drafts, later typing, and command prefills.

## Status and help lines

Append, hide, or replace line segments through the existing line-extension contract:
Expand Down
3 changes: 2 additions & 1 deletion docs/maintenance/cli-pr-integration-2026-11-10.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

This candidate combines the remaining Igor-authored work from [#745](https://github.com/autohandai/code-cli/pull/745), [#743](https://github.com/autohandai/code-cli/pull/743), [#742](https://github.com/autohandai/code-cli/pull/742), [#727](https://github.com/autohandai/code-cli/pull/727), [#690](https://github.com/autohandai/code-cli/pull/690), [#669](https://github.com/autohandai/code-cli/pull/669), and [#747](https://github.com/autohandai/code-cli/pull/747), together with the saved-session input fix from `df44f764`.

The integration starts from main at `150be8938f3bacd4264f0622e88dcffc465bfc8f`. It preserves the intervening explicit Git-initialization change, atomic configuration writes, partial cloud-history protection, extension reload coordination, and permission enforcement. The first validation revision was paused when #747 was added to the requested scope; the expanded revision receives fresh combined checks.
The integration starts from main at `150be8938f3bacd4264f0622e88dcffc465bfc8f`. It preserves the intervening explicit Git-initialization change, atomic configuration writes, partial cloud-history protection, extension reload coordination, and permission enforcement. The combined revision receives fresh tests, lint, full proof, and terminal compatibility checks before merge.

| Area | Resulting behavior | Reference |
| --- | --- | --- |
Expand All @@ -13,6 +13,7 @@ The integration starts from main at `150be8938f3bacd4264f0622e88dcffc465bfc8f`.
| Command output | Cap model observations while preserving complete oversized output in private session artifacts; streamed and nested command paths use the same cap. | [Configuration reference](../config-reference.md) |
| Computer Use | Temporarily enter AUTO for the computer task and restore the preceding mode, preserving subsequent manual selections, explicit denials, hooks, and restricted mode. | [Computer control](../computer-control.md) |
| Saved sessions | Accept composer edits while restoring history and defer submitted work until restoration completes. | [Resume terminal tests](../../tests/tuistory/resume.tuistory.test.ts) |
| Composer ownership | Keep an existing or newly typed draft when an older slash command or extension view completes, cancels, or fails. Input submission is consumed at dispatch, so delayed completion cannot clear a later draft. | [Extension authoring](../extension-authoring.md) |
| CLI contract | Bootstrap the current 199 option definitions across 52 command paths, and compare future changes with the committed main snapshot. | [Option contract](../compatibility/claude-code/README.md) |
| macOS distribution | Check transported CLI and trace binaries for notarization in addition to existing signature checks. | [Release workflow](../../.github/workflows/release.yml) |
| Providers | Keep the active provider when removing an inactive custom entry; preserve it if replacement is cancelled. Cloud restoration clears previous local transport settings. Bound OpenAI-family native tool requests, including delegated agents, and use compatible Azure reasoning parameters. | [Provider reference](../providers.md) |
Expand Down
10 changes: 8 additions & 2 deletions docs/terminal-compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,17 @@ The tmux suite launches a compiled CLI through a real tmux server on a unique te

First-run dialogs are handled before fixture input. The offline fixture denies only the macOS request to find devices on local networks through XCTest's interruption handler. The handler checks accessibility values, titles, and labels: macOS permission text can have an empty label, while its visible text is in the value or title. The iTerm2 scenario opens a fresh window after handling interruptions, so a restored session cannot consume fixture input. All terminal text and resize assertions refer to the same window. Application termination and interruption-monitor removal use XCTest teardown blocks so failed assertions also release them.

VS Code uses separate disposable profiles for idle and streaming cases under a short `/private/tmp/ah-vsc-*` directory. Its [main IPC socket](https://github.com/microsoft/vscode/blob/main/src/vs/base/parts/ipc/node/ipc.net.ts) lives under the user-data directory, so putting profiles below a long checkout path can exceed macOS's Unix socket limit and make Code exit before creating a window. Settings evidence stays under the checkout's `.artifacts/native-terminal/` directory, and bounded main-log tails are attached to XCTest results. The profiles enable [`chat.disableAIFeatures`](https://code.visualstudio.com/docs/agents/reference/ai-settings) to suppress built-in Copilot onboarding. VS Code also launches with [`--force-renderer-accessibility`](https://www.chromium.org/developers/design-documents/accessibility/) to expose Chromium's native accessibility tree; the editor's screen-reader setting alone does not ensure that tree is available to XCTest. The scenario opens the terminal through its native Terminal > New Terminal menu. If the specific Copilot sign-in dialog still appears, the scenario closes that dialog and opens the integrated terminal. It does not sign in or grant network access. Window, workbench, and terminal-view failures preserve bounded accessibility diagnostics and screenshots; the workflow also retains the Xcode output and generated VS Code settings.
VS Code uses separate disposable profiles for idle and streaming cases under a short `/private/tmp/ah-vsc-*` directory. Its [main IPC socket](https://github.com/microsoft/vscode/blob/main/src/vs/base/parts/ipc/node/ipc.net.ts) lives under the user-data directory, so putting profiles below a long checkout path can exceed macOS's Unix socket limit and make Code exit before creating a window. Settings evidence stays under the checkout's `.artifacts/native-terminal/` directory, and bounded main-log tails are attached to XCTest results. The profiles enable [`chat.disableAIFeatures`](https://code.visualstudio.com/docs/agents/reference/ai-settings) to suppress built-in Copilot onboarding. The disposable profiles set `editor.accessibilitySupport: "off"` and omit `--force-renderer-accessibility` for ordinary graphical terminal coverage. [VS Code maps this setting to xterm screen-reader mode](https://github.com/microsoft/vscode/blob/1.144.0/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts#L2047); [automatic detection can enable that mode when native accessibility is active](https://github.com/microsoft/vscode/blob/1.144.0/src/vs/platform/accessibility/browser/accessibilityService.ts#L145). Explicit `off` prevents the test observer from enabling the parallel screen-reader row mirror. Native AppKit window/menu checks and the authoritative raw buffer copy remain required. This graphical scenario does not cover screen-reader mode, and it changes only disposable fixture profiles. The scenario opens the terminal through its native Terminal > New Terminal menu. If the specific Copilot sign-in dialog still appears, the scenario closes that dialog and opens the integrated terminal. It does not sign in or grant network access. Window, workbench, and terminal-view failures preserve bounded accessibility diagnostics and screenshots; the workflow also retains the Xcode output and generated VS Code settings.

Native scenarios first size and position their fixture window within the reported display's visible bounds, leaving room for all four physical resize gestures. Setup shrinks oversized dimensions before moving the window, then grows it at the target position, so a full-width window's resize handle never moves off-screen. VS Code setup starts after terminal readiness and verifies the actual width and height accepted by each gesture; a partially accepted shrink cannot authorize an off-screen move. VS Code's input readiness uses the native Split Terminal command becoming enabled after New Terminal, which establishes terminal focus; it does not require xterm's input to expose the same TextView role as standalone terminals. The fixture's rendered output remains the mandatory proof that paste, Enter, and terminal rendering worked. All scenarios retain the exact transcript, streaming, draft, cursor, resize, and exit assertions.

VS Code's readout preflight captures one immutable app snapshot, scopes it to the fixture's current native window frame, and searches at most 2,048 nodes. Its native process ID, Core Graphics window ID, and single-window count remain fixed through all gestures and reads, while accessibility window references are refreshed after resize. [xterm's accessibility tree](https://github.com/xtermjs/xterm.js/blob/master/src/browser/AccessibilityManager.ts) exposes a screen-row list separately from its live announcement region. In the recorded macOS tree this is an Other node containing consecutive, equal-height Group rows, each with one StaticText value. The adapter requires one unambiguous, complete row list in the native window; it preserves row order, blank rows, and repeated values. xterm scales each accessibility row to its text and cell width, so rows can be wider than the list container after resize; the adapter validates their vertical grid without requiring horizontal containment. It never selects rows based on fixture content or concatenates announcement siblings. Native TextView buffers remain supported. Ambiguous, oversized, malformed, or stale snapshots fail closed to the buffer-copy preflight. If accessibility lacks it, a disposable-profile shortcut uses [`runCommands`](https://code.visualstudio.com/docs/configure/keybindings#_running-multiple-commands) to select the full terminal buffer and [copy and clear its selection](https://github.com/microsoft/vscode/blob/main/src/vs/workbench/contrib/terminalContrib/clipboard/browser/terminal.clipboard.contribution.ts). The preflight uses that raw copied buffer only if it contains the actual fixture marker and a second accessibility read still lacks it. Each subsequent copy clears the pasteboard and requires a new pasteboard generation; old frames cannot satisfy later resize checks. A temporarily unavailable copy produces no frame; the original startup, resize, cursor, or exit predicate retains its deadline. Once resize readiness accepts a fresh raw frame, all exact counts and streaming assertions inspect that same frame without issuing a second copy. Resize readiness requires the header, history, draft, and the stream marker for streaming scenarios, without examining their counts. This avoids accepting a partial accessibility update before the header returns; duplicated accepted frames still fail immediately against the exact-count assertions. Permanent marker loss fails the original settling deadline. The command clears terminal selection while preserving focus and the PTY cursor, which the existing cursor-edit assertion verifies. No terminal output is filtered or deduplicated. Readout samples, selected backend, settings, and keybindings are retained with native results.
VS Code's authoritative readout uses its native terminal buffer copy. Hosted recordings showed the canvas progressing from stream 377 to 382 while correctly shaped accessibility rows remained on stream 313 with missing transcript text throughout the settling deadline. Accessibility content is therefore retained only as bounded failure diagnostics. A disposable-profile shortcut uses [`runCommands`](https://code.visualstudio.com/docs/configure/keybindings#_running-multiple-commands) to focus the existing terminal, select its full buffer, and [copy and clear its selection](https://github.com/microsoft/vscode/blob/main/src/vs/workbench/contrib/terminalContrib/clipboard/browser/terminal.clipboard.contribution.ts). The binding is scoped to `terminalIsOpen`, so it can restore terminal focus after a resize. Each observation clears the pasteboard and requires a new pasteboard generation with text; a stale value cannot pass even if all expected markers are present. A fresh generation and string already available after the key event return immediately; otherwise the existing waiter allows up to two seconds and revalidates freshness. The copied bytes are preserved without filtering, trimming, or deduplication. A temporarily unavailable copy produces no frame within the original readiness deadlines. Resize readiness requires the header, history, draft, and stream marker for streaming scenarios, without examining their counts; all exact assertions inspect that same accepted raw frame. Duplicate markers fail the unchanged count assertions, and permanent marker loss fails the original settling deadline. The native process ID, Core Graphics window ID, single-window count, current geometry, cursor-edit and exit checks remain mandatory. The command clears selection while preserving the PTY cursor. Backend, bounded preflight samples, pasteboard generation metadata, settings, and keybindings are retained with native results.

Native-copy diagnostics retain each attempt's phase, timestamps, pasteboard generations and types, raw text, availability, and native window identity and geometry. A bounded tail of 128 records and 2 MiB is persisted after each attempt, with explicit eviction counts, so late resize failures remain inspectable even if the application exits. The fixture also writes asynchronous, bounded write metadata: sequence, viewport, stream counter and bytes before stdout delegation, then separate return/backpressure and callback-completion timestamps. Metadata-only observation forwards the original string or binary chunk and its encoding unchanged. That metadata contains no terminal output, never waits in the producer, and retains the latest records within 1 MiB. The workflow retains both the committed metadata file and any in-flight `.pending` file, alongside copy samples and XCTest evidence. Diagnostic instrumentation does not establish that an intermittent failure is repaired.

VS Code's streaming scenario keeps response chunks flowing through each physical resize. After the gesture, a disposable control file (512 bytes maximum, command IDs 1–8) requests observation. Only after a newer post-command chunk at changed/current TTY dimensions does the fixture gate new response chunks. A late TTY resize reopens the producer until it emits at the new dimensions and refreshes the same observation command. The request and working spinner stay active; no renderer operation is triggered. A separate atomic current-state snapshot (512 bytes maximum) records command, emitted-command, revision, counter, dimensions and pause state. Native checks that state before and after each fresh copy, independently of diagnostic-history eviction. The copy must contain the exact paused counter and all original markers before unchanged exact-count assertions run. Resuming and proving a newer emitted chunk at the same viewport share the original single 10-second settling budget with acknowledgement and copying, including after the final resize. Screenshots follow resumed-progress proof. Asynchronous control reads are single-flight and closed on every path; disposal suppresses late work and drains pending state writes. Unused Code predicate objects are omitted to avoid implicit accessibility debug snapshots during polling; explicit failure evidence remains.

Code baseline preparation resizes width from the right-edge midpoint, then height from the bottom-edge midpoint. Each accepted size, native identity and display bounds is verified before moving. This avoids the rounded-corner hit target selecting only the bottom edge on an initially screen-width window. The four scenario corner drags remain physical and retain their original deltas and assertions.

The strict visible-history assertions require room for both transcript exchanges and the multiline composer. At a compact 57×19 terminal, the full-screen transcript correctly follows its newest exchange and clips older rows; a separate PTY case checks that the history reappears on growth, the draft and cursor survive, and Ctrl+C exits cleanly. This distinguishes viewport clipping from lost transcript data. Standalone terminal references bind to their accessibility identities. VS Code refreshes its accessibility window before setup steps and after every setup or scenario gesture, guarded by the same native process ID, window ID, single-window count, and current frame.

Expand Down
7 changes: 0 additions & 7 deletions src/core/agent/AgentCommandRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,6 @@ const INTERACTIVE_SLASH_COMMANDS = new Set([
const FIXED_COMPOSER_RESULT_COMMANDS = new Set(['/tasks', '/team', '/teams', '/squad']);
const MARKDOWN_REPORT_COMMANDS = new Set(['/pr-review', '/deslop', '/tester']);

export function shouldPreserveComposerDraftAfterCommand(command: string): boolean {
return command === '/whatityped' || command === '/peers' || command === '/feedback' || command === '/status' || command === '/sync';
}

export function shouldRenderSlashCommandResultInComposer(command: string): boolean {
return FIXED_COMPOSER_RESULT_COMMANDS.has(command);
}
Expand Down Expand Up @@ -274,9 +270,6 @@ export async function runAgentSlashCommandWithInput(host: AgentCommandRuntimeHos
host.persistentInputActiveTurn = false;
}
cleanupConsoleBridge();
if (isInteractive && !shouldPreserveComposerDraftAfterCommand(command) && host.inkRenderer?.isRunning()) {
host.inkRenderer.clearInput();
}
}
}

Expand Down
7 changes: 3 additions & 4 deletions src/core/agent/AgentLifecycleRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ import { shouldForceAgentIdleLogout } from './AgentSessionAccounting.js';
import {
consumeAgentInkSubmittedInstructionEcho,
} from './AgentUIRuntime.js';
import { renderAgentSlashCommandResult, shouldPreserveComposerDraftAfterCommand } from './AgentCommandRuntime.js';
import { renderAgentSlashCommandResult } from './AgentCommandRuntime.js';
import {
createQueuedAgentInstruction,
resolveActiveGoalContinuation,
Expand Down Expand Up @@ -1960,10 +1960,9 @@ export async function runAgentInteractiveLoop(host: AgentLifecycleHost): Promise
host.writeDebugLine?.bind(host)
);
if (host.ui || host.inkRenderer) {
// Submission already consumed its text; current input belongs
// to a later draft or a command prefill.
host.setComposerIdle();
if (!shouldPreserveComposerDraftAfterCommand(command)) {
host.clearComposerInput();
}
// Return to the top of the loop so the idle-wait path can await
// the next Composer submission without falling through to
// instruction.startsWith('/') which would throw on null.
Expand Down
Loading
Loading