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
118 changes: 118 additions & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ jobs:
run: >
./gradlew test
--tests "org.zoocode.jetbrains.terminal.RawOutputTtyConnectorTest"
--tests "org.zoocode.jetbrains.util.JcefSupportTest"
--tests "org.zoocode.jetbrains.descriptor.PluginDescriptorTest"
--tests "org.zoocode.jetbrains.terminal.TerminalPtyBuilderTest"
--tests "org.zoocode.jetbrains.terminal.ShellIntegrationOutputStateTest"
--tests "org.zoocode.jetbrains.terminal.TerminalShellReadinessTest"
--no-configuration-cache

- name: Build the plugin
Expand Down Expand Up @@ -123,3 +128,116 @@ jobs:
./gradlew smoke263Dispatch
-PsmokeLocalIde="build/ideaIC-${IDEA_263_BUILD}"
--no-configuration-cache

windows-verify:
name: Windows terminal tests and PowerShell 5.1 smoke
runs-on: windows-latest
timeout-minutes: 60

steps:
- name: Check out the JetBrains plugin
uses: actions/checkout@v5
with:
persist-credentials: false

# git rev-parse reports the exact checkout that ran. A pull_request event
# can test a merge commit, so the recorded value, not the branch head, is
# the validated one.
- name: Record the run identity
shell: powershell
run: |
$identity = @"
Validation: GitHub Actions Windows runner
Checkout: $env:GITHUB_WORKSPACE
Tested commit: $(git rev-parse HEAD)
OS: windows-latest
JDK: temurin 17
Run: $env:GITHUB_SERVER_URL/$env:GITHUB_REPOSITORY/actions/runs/$env:GITHUB_RUN_ID
"@
Write-Output $identity
Set-Content -Path windows-run-identity.txt -Value $identity

- name: Set up Java 17
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: "17"
cache: gradle

# The same build-only fixtures the Linux job creates: the Gradle build
# reads these paths while it configures prepareSandbox, and the pull
# request tests never package the real extension payload.
- name: Create the sandbox input paths the Gradle build needs
shell: powershell
run: |
New-Item -ItemType Directory -Force -Path jetbrains_plugin/plugins/zoo-code/extension | Out-Null
New-Item -ItemType File -Force -Path jetbrains_plugin/plugins/zoo-code/extension/.keep | Out-Null
New-Item -ItemType File -Force -Path jetbrains_plugin/prodDep.txt | Out-Null

# RawOutputTtyConnectorTest runs its stub connector tests and skips the
# Linux-only real-PTY test. WindowsConPtyProcessTest holds the real
# ConPTY process tests and is skipped on any non-Windows runner.
#
# The runner wraps `powershell` scripts in $ErrorActionPreference =
# 'stop'. In Windows PowerShell 5.1, `2>&1` on a native command turns
# every stderr line into an ErrorRecord, and under 'stop' the first
# Gradle stderr line would abort the step before the exit-code line
# runs. The 2>&1 merge therefore happens inside cmd.exe, before
# PowerShell sees any bytes, so the pipeline carries plain strings.
# cmd /c propagates the Gradle wrapper exit code into $LASTEXITCODE.
# A missing or empty log means the upload step would publish no
# evidence, so that failure is surfaced explicitly.
- name: Run the Windows terminal tests
working-directory: jetbrains_plugin
shell: powershell
run: |
$taskList = @(
'test'
'--tests org.zoocode.jetbrains.terminal.RawOutputTtyConnectorTest'
'--tests org.zoocode.jetbrains.terminal.TerminalPtyBuilderTest'
'--tests org.zoocode.jetbrains.terminal.ShellIntegrationOutputStateTest'
'--tests org.zoocode.jetbrains.terminal.TerminalShellReadinessTest'
'--tests org.zoocode.jetbrains.terminal.WindowsConPtyProcessTest'
'--no-configuration-cache'
)
cmd /c "gradlew.bat $($taskList -join ' ') 2>&1" | Tee-Object -FilePath ..\windows-test-log.txt
$gradleExit = $LASTEXITCODE
$testLog = Get-Item -LiteralPath ..\windows-test-log.txt -ErrorAction SilentlyContinue
if ($null -eq $testLog -or $testLog.Length -eq 0) {
Write-Output '::error::the Gradle test log is missing or empty, so the artifact step would publish no evidence'
exit 1
}
exit $gradleExit

# The "powershell" shell is Windows PowerShell 5.1; "pwsh" would run
# PowerShell 7 and would not validate the 5.1 parser path. The script
# signals failure with a terminating error, so a catch decides the step
# outcome; $LASTEXITCODE is stale here because the script's last native
# command is cmd.exe. The smoke is independent of the Gradle test step,
# so it still runs when that step failed; !cancelled() skips it only
# when the job itself is cancelled. A failed test step keeps failing the
# job, and this step keeps its own exit-code checks.
- name: Smoke check the PowerShell integration profile on Windows PowerShell 5.1
if: ${{ !cancelled() }}
shell: powershell
run: |
try {
& .\jetbrains_plugin\smoke\PowerShellIntegrationCheck.ps1 2>&1 | Tee-Object -FilePath windows-smoke-log.txt
} catch {
$_ | Out-String | Tee-Object -FilePath windows-smoke-log.txt -Append
exit 1
}
exit 0

- name: Store the test results, logs, and run identity
if: always()
uses: actions/upload-artifact@v4
with:
name: windows-pr-results-${{ github.sha }}-windows-latest-jdk17
path: |
jetbrains_plugin/build/test-results/test/*.xml
jetbrains_plugin/build/reports/tests/test/**
windows-test-log.txt
windows-smoke-log.txt
windows-run-identity.txt
if-no-files-found: warn
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,11 @@ deps/vscode/*
jetbrains_plugin/plugins
jetbrains_plugin/build
.gradle
/.gradle-user-home/
deps/vsix/package/*
deps/vsix/plugin/*
-e
# Roo build artifacts
.zoo-build/
dist-with-roo/
plans/
plans/
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Changelog

## Unreleased

### Terminal on Windows

- The plugin requests ConPTY explicitly through the public pty4j setter. The JetBrains registry key `terminal.use.conpty.on.windows` is no longer read.
- If the ConPTY native library fails to load, the process falls back to winpty and the session continues.
- Other process start failures propagate to the terminal error handling.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ The plugin targets IntelliJ Platform build **233** (JetBrains 2023.3) or newer.
- Android Studio 2026.1.4 or newer when using Android Studio; install a preview build until this version reaches the stable channel
- A JCEF-enabled IDE runtime, which Zoo Code requires to render its interface

Where JCEF support comes from depends on the IDE version. From IntelliJ Platform 2026.2 (build 262), JCEF ships as a separate bundled plugin. If that plugin is missing or disabled, Zoo Code shows a warning notification and the Zoo Code tool window cannot render. Enable the bundled JCEF plugin under **Settings → Plugins → Installed** and restart the IDE. On IDEs older than 2026.2, JCEF comes from the IDE runtime; see [Known Issues](docs/KNOWN_ISSUES.md).

On first launch the plugin automatically downloads a pinned, platform-specific Node.js runtime from [nodejs.org](https://nodejs.org) (checksum-verified) and uses it to run the extension host, so no local Node.js installation is required. If the download is unavailable, the plugin falls back to a bundled Node.js or a Node.js **20.6.0 or newer** found on your `PATH`.

### Building from source
Expand Down
35 changes: 28 additions & 7 deletions docs/KNOWN_ISSUES.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,18 @@ Android Studio's default startup runtime does not include JCEF capabilities, req
1. **Download a JCEF-Compatible JetBrains Runtime**

Download a JetBrains Runtime (JBR) with JCEF support:

👉 https://github.com/JetBrains/JetBrainsRuntime/releases

Choose a release with:
- `jbr_jcef` in the name
- Correct architecture (osx-aarch64, linux-x64, or windows-x64)

Example:
```
jbr_jcef-17.0.11-osx-aarch64-b1063.2.tar.gz
```

Unpack it somewhere, e.g.:
```
~/jbr/jbr_jcef
Expand All @@ -47,15 +47,15 @@ Android Studio's default startup runtime does not include JCEF capabilities, req
4. Select it and choose the folder where you extracted the JBR (e.g., ~/jbr/jbr_jcef).

3. **Restart Android Studio**

After selecting the new runtime, Android Studio will prompt you to restart. Confirm, and it will relaunch with the new runtime.

4. **Verify the Runtime**

Go to Help → About to confirm the IDE is now running with the new JBR. The version should indicate JCEF support (e.g., JetBrains Runtime jbr-17.0.11+7-b1238.56-jcef).

5. **Revert if Needed**

You can always use the same "Choose Boot Java Runtime for the IDE" action to switch back to the default runtime if needed.

##### Method 2: Using Edit Custom VM Options
Expand Down Expand Up @@ -91,6 +91,27 @@ On Linux ARM platforms, JCEF may fail to initialize native processes properly, l
#### Solutions
No solution available at this time.

### 1.3 JCEF Is Not Available on IDE 2026.2 or Newer

#### Problem Description

From IntelliJ Platform 2026.2 (build 262), JetBrains packages JCEF as a separate bundled plugin with the id `com.intellij.modules.jcef`. JCEF is no longer part of the IDE platform. When this bundled plugin is not present, the Zoo Code view cannot render.

#### Behavior

The plugin does not crash. It logs the reason and shows a warning notification. The Zoo Code tool window stays empty. The IDE log shows the state in the startup line `JCEF: CLASSES_MISSING`.

#### Affected Platforms
- IDEs based on IntelliJ Platform build 262 or newer without the bundled JCEF plugin

#### Solutions

1. Open **Settings → Plugins → Installed** and search for **JCEF**.
2. Enable the bundled JCEF plugin if it is disabled.
3. Restart the IDE.

If the bundled JCEF plugin is not in the list, this IDE build does not ship JCEF, and the Zoo Code view cannot run in it. IDEs older than 2026.2 get JCEF from the runtime; see section 1.1.

---

## 2. Node Related Issues
Expand Down
108 changes: 108 additions & 0 deletions docs/windows-validation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Windows validation status

This document records the Windows validation state of the `fix-intelliJ-compatibility`
branch. It separates evidence from automated GitHub runner coverage and evidence from
real IDE sessions. It records only evidence that was reported; it does not store logs
that do not exist in this repository.

## Recorded manual evidence (external Windows agent)

Commit `05dea49` on `fix-intelliJ-compatibility` was validated on an external Windows
machine. The requester reported the following results. The logs live on that agent and
are not locally accessible, so this repository cannot reproduce them.

- The two new Kotlin test classes passed: `ShellIntegrationOutputStateTest` and
`TerminalShellReadinessTest`, 4 tests in total.
- [`smoke/PowerShellIntegrationCheck.ps1`](../jetbrains_plugin/smoke/PowerShellIntegrationCheck.ps1)
passed under Windows PowerShell 5.1.
- A real IDE session passed: plugin execution, Unicode output, exact exit codes,
connector reuse, and five repeated terminal startups.

Status: the Windows requirement is **partially validated** by this manual evidence.
The agent checkout path is not recorded in this repository.

Scope note: the reported "connector reuse" is real IDE session reuse. The CI
coverage is narrower: several commands inside one live process session, and
repeated fresh sessions. The CI job never reuses a terminal session inside the
IDE, so the two reuse claims are not interchangeable.

## Windows CI validation (passed 2026-10-09)

The `windows-verify` job in [`.github/workflows/pr.yml`](../.github/workflows/pr.yml)
runs on every pull request. Hosted run logs and artifacts expire, so this
document records the result and not a run link. The job is the living evidence.

- Date: 2026-10-09
- Branch head: `97070c9` on `fix-intelliJ-compatibility` (pull request #10)
- Runner: `windows-latest`, JDK temurin 17
- Result: 6 of 6 `WindowsConPtyProcessTest` tests passed on the ConPTY
backend. None were skipped. Unicode execution, exact exit code, resize,
Ctrl+C/ETX interrupt, five repeated fresh startups, and bounded close all
passed.
- The PowerShell 5.1 smoke script passed.

History: an earlier run failed the interrupt test. The test sent the next
command right after the ETX byte, and no prompt returned. The test now installs
a counted prompt (`zp<N>`) and waits for a new prompt before the next command.
The interrupt then passed, so the ETX byte does reach the shell in this harness.

Each run records its own identity and uploads it as an artifact:

- `Checkout: $env:GITHUB_WORKSPACE`
- `Tested commit: $(git rev-parse HEAD)`; a pull request job can test a merge
commit, so this value can differ from the branch head
- `Run: $env:GITHUB_SERVER_URL/$env:GITHUB_REPOSITORY/actions/runs/$env:GITHUB_RUN_ID`

## Automated runner coverage

The `windows-verify` job runs on `windows-latest` with JDK 17:

- `RawOutputTtyConnectorTest`: connector stub tests; the Linux-only real-PTY test
skips on Windows.
- `TerminalPtyBuilderTest`: the explicit ConPTY selection and builder values.
- `ShellIntegrationOutputStateTest` and `TerminalShellReadinessTest`: marker parsing
and readiness.
- `WindowsConPtyProcessTest`: real ConPTY process tests that start through the
production `TerminalPtyBuilder` and assert the live process is the ConPTY
backend, so a silent winpty fallback fails. Teardown runs through one bounded
cleanup with a force-destroy watchdog, and cleanup problems are reported as
suppressed failures without masking the original assertion. Coverage: input
execution with Unicode, exact exit codes, resize through the connector with
executed width and height, the Ctrl+C/ETX pipeline interrupt with a documented
pipeline-interruption scope, five repeated fresh startups, and bounded close
with separate completion, process exit, and reader termination assertions.
All of it is process-level: several commands inside one live session and
repeated fresh sessions. The CI job never reuses a connector the way a real
IDE session does.
- [`smoke/PowerShellIntegrationCheck.ps1`](../jetbrains_plugin/smoke/PowerShellIntegrationCheck.ps1)
runs through `powershell.exe`, which is Windows PowerShell 5.1, not `pwsh`.
The step carries `if: ${{ !cancelled() }}`: it also runs after a failed
Gradle test step, and only a cancelled job skips it. A failed test step
still fails the job, and the smoke keeps its own exit-code checks.
- Test XML, HTML reports, logs, and the run identity upload with `if: always()`.

## Real IDE coverage (manual only)

The CI job never starts the IDE. Real IDE execution, interactive rendering,
connector reuse inside one IDE session, and workspace-close behavior need a
human session. The native PTY tests above prove process behavior only; they are
not real IDE workspace-close tests.

## Remaining gaps

| Item | Runner coverage | Real IDE evidence |
| --- | --- | --- |
| Input execution, Unicode, exact exit codes, five fresh startups | Passed 2026-10-09 | Reported for `05dea49` |
| Multi-command execution inside one live process session | Passed 2026-10-09 | None |
| Connector reuse in a real IDE session | None; out of CI scope | Reported for `05dea49` |
| Resize | Passed 2026-10-09 | None |
| Ctrl+C / ETX pipeline interrupt | Passed 2026-10-09: the shell returns to a new prompt after ETX | None |
| Interactive input | Process-level input only | None |
| Workspace-close cleanup | None | None |
| 263 JCEF matching native bundle | None | None; known gap |

## Local validation

No Windows executor exists in the local Linux workspace. Windows results come
from the hosted job only. The Linux job runs the same test classes, and the
Windows-only tests skip there.
20 changes: 16 additions & 4 deletions jetbrains_plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ fun Sync.prepareSandbox() {
}
depfile.readLines().let {
it.forEach { line ->
depPatterns.add(line.substringAfterLast("node_modules/") + "/**")
depPatterns.add(line.replace('\\', '/').substringAfterLast("node_modules/") + "/**")
}
}

Expand Down Expand Up @@ -363,17 +363,29 @@ tasks {
// linked against the 233 platform jars already on the compile classpath; this task
// never needs the 2026.3 IDE. The scan target is the instrumented jar in the
// sandbox, the exact bytes that the Plugin Verifier checks and that the published
// zip packages. The marker output makes the task up-to-date when the sandbox and
// the checker are unchanged.
// zip packages. The task first runs the checker --self-test, which feeds it
// synthetic clean and banned jars, so a scanner regression fails this task. The
// marker output makes the task up-to-date when the sandbox and the checker are
// unchanged.
register("verifyPackagedBytecode") {
group = "verification"
description = "Reject packaged references to terminal APIs removed in IntelliJ 2026.3"
description =
"Reject packaged references to terminal APIs removed in IntelliJ 2026.3 and to internal LocalPtyOptions API"
val checker = layout.projectDirectory.file("smoke/Smoke263DispatchCheck.java")
inputs.files(prepareSandbox.map { it.destinationDir })
inputs.file(checker)
outputs.file(layout.buildDirectory.file("verification/packaged-bytecode.ok"))
dependsOn(prepareSandbox)
doLast {
project.exec {
commandLine(
verificationJavaLauncher.get().executablePath.asFile.absolutePath,
"-cp",
configurations.getByName("compileClasspath").asPath,
checker.asFile.absolutePath,
"--self-test",
)
}
val pluginJar = packagedPluginJar()
project.exec {
commandLine(
Expand Down
Loading
Loading