diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 06e11a8c..79974183 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -10,7 +10,7 @@ body: id: version attributes: label: T4 Code version - placeholder: "0.1.17" + placeholder: "0.1.18" validations: required: true - type: dropdown diff --git a/README.md b/README.md index 4f18c6c6..59f6fe85 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,13 @@ T4 Code is a free, open-source (MIT) desktop app for [Oh My Pi](https://github.c ![T4 Code main window](docs/assets/t4-code-main.png) -[**Download v0.1.17**](https://github.com/LycaonLLC/t4-code/releases/tag/v0.1.17) · [**Docs**](https://t4code.net/docs) · [**Get the source**](#build-from-source) +[**Download v0.1.18**](https://github.com/LycaonLLC/t4-code/releases/tag/v0.1.18) · [**Docs**](https://t4code.net/docs) · [**Get the source**](#build-from-source) ## Requirements -T4 Code needs an OMP build with desktop appserver support. For v0.1.17, use the public integration build below. +T4 Code needs an OMP build with desktop appserver support. For v0.1.18, use the public integration build below. -T4 Code v0.1.17 was verified with OMP 16.5.2 built from [`264958b2`](https://github.com/lyc-aon/oh-my-pi/commit/264958b23acac16baaf3bf0024129dc1a57f9d14), tagged [`t4code-16.5.2-appserver-4`](https://github.com/lyc-aon/oh-my-pi/tree/t4code-16.5.2-appserver-4). That public integration is based on the official upstream [`v16.5.2`](https://github.com/can1357/oh-my-pi/tree/v16.5.2) tag at [`7d02778c`](https://github.com/can1357/oh-my-pi/commit/7d02778c60f4b5db60f84bedbca79d6e64cb91f5). The transcript behavior introduced in appserver-2 remains unchanged: tool results preserve sanitized structured details, subagent RPC reads have byte and record ceilings, and child-transcript image digests use the same verified image-read path as the parent session. Appserver-4 fixes the appserver clean-source TypeScript/build check and makes the appserver type check plus full runtime tests mandatory before OMP release binaries can publish. It also carries forward T4's appserver, lifecycle, image, session-control, and atomic maintenance integration. The official upstream v16.5.2 tag has no `appserver` command, so it cannot host T4 Code. The verified runtime is a normal build from the public `lyc-aon/oh-my-pi` source; T4 Code does not depend on private home-directory files, an auth broker, or a custom Codex CLI fork. T4 Code vendors `@oh-my-pi/app-wire` 0.5.5 from integration commit [`6a87fa64`](https://github.com/lyc-aon/oh-my-pi/commit/6a87fa6407ebff20417b4d52885a6bb3091003ea), source tree `a2495fe8781c979184fe7fb9a6d37d8f33bad30f`. +T4 Code v0.1.18 was verified with OMP 17.0.0 built from [`6e2f2350`](https://github.com/lyc-aon/oh-my-pi/commit/6e2f2350cfe9e6f5db691c311333cae33cdb62ba), tagged [`t4code-17.0.0-appserver-1`](https://github.com/lyc-aon/oh-my-pi/tree/t4code-17.0.0-appserver-1). That public integration is based on the official upstream [`v17.0.0`](https://github.com/can1357/oh-my-pi/tree/v17.0.0) tag at [`d5cd24f3`](https://github.com/can1357/oh-my-pi/commit/d5cd24f39a951bfbd50dc8f50bcf095d59694d6c). It carries T4's bounded transcript replay, structured tool-result details, child-agent transcript and image projection, session lifecycle controls, ordered outbound frames, and atomic maintenance drain onto the 17.0.0 codebase. Fork CI rechecks the exact upstream ancestry before publishing binaries. The official upstream v17.0.0 tag has no `appserver` command, so it cannot host T4 Code. The verified runtime is a normal build from the public `lyc-aon/oh-my-pi` source; T4 Code does not depend on private home-directory files, an auth broker, or a custom Codex CLI fork. T4 Code vendors `@oh-my-pi/app-wire` 0.5.5 from integration commit [`6a87fa64`](https://github.com/lyc-aon/oh-my-pi/commit/6a87fa6407ebff20417b4d52885a6bb3091003ea), source tree `a2495fe8781c979184fe7fb9a6d37d8f33bad30f`. | Platform | Arch | Package | | -------- | --------------------- | ---------------------------------------- | @@ -18,21 +18,20 @@ T4 Code v0.1.17 was verified with OMP 16.5.2 built from [`264958b2`](https://git | Linux | x86_64 | `.deb`, AppImage | | macOS | Apple Silicon (arm64) | `.dmg`, `.zip` (**unsigned, see below**) | -No Windows build and no Intel Mac build in v0.1.17. The iOS TestFlight build is coming soon. +No Windows build and no Intel Mac build in v0.1.18. The iOS TestFlight build is coming soon. -## What changed in v0.1.17 +## What changed in v0.1.18 -- Empty working-folder rows now have a **Remove shortcut** action. It only hides the row in the current T4 Code client; it does not delete the folder or any OMP session. -- The preference survives a restart. Archived sessions stay visible, **Show shortcut** restores the row, and a new or restored current session makes the row visible again. -- T4 Code disables removal while the host's session inventory is incomplete, so an unseen current session cannot be hidden by mistake. -- The verified OMP appserver-4 runtime and app-wire 0.5.5 contract are unchanged. +- The verified runtime now uses the exact official OMP 17.0.0 base at `d5cd24f3`. +- The public appserver integration keeps bounded replay, image prompts and transcript images, two-client session control, reconnect-safe history, and atomic maintenance drain. +- The app-wire contract remains at 0.5.5, so this compatibility release does not introduce a client protocol migration. ## Install ### Android 1. On the Android phone, sign in to Tailscale with an account that can reach the T4 Code host. -2. Download [`T4-Code-0.1.17-android.apk`](https://github.com/LycaonLLC/t4-code/releases/download/v0.1.17/T4-Code-0.1.17-android.apk). +2. Download [`T4-Code-0.1.18-android.apk`](https://github.com/LycaonLLC/t4-code/releases/download/v0.1.18/T4-Code-0.1.18-android.apk). 3. If Android asks, allow your browser or file manager to install unknown apps, then install the APK. 4. Open T4 Code and enter the host's HTTPS Tailscale address, including its port. @@ -41,8 +40,8 @@ The APK does not contain an appserver or expose one to the public internet. It c ### Linux (Debian/Ubuntu) ```sh -wget https://github.com/LycaonLLC/t4-code/releases/download/v0.1.17/T4-Code-0.1.17-linux-amd64.deb -sudo apt install ./T4-Code-0.1.17-linux-amd64.deb +wget https://github.com/LycaonLLC/t4-code/releases/download/v0.1.18/T4-Code-0.1.18-linux-amd64.deb +sudo apt install ./T4-Code-0.1.18-linux-amd64.deb ``` Use `apt install` rather than `dpkg -i` so system dependencies resolve automatically. @@ -50,17 +49,17 @@ Use `apt install` rather than `dpkg -i` so system dependencies resolve automatic ### Linux (AppImage) ```sh -wget https://github.com/LycaonLLC/t4-code/releases/download/v0.1.17/T4-Code-0.1.17-linux-x86_64.AppImage -chmod +x T4-Code-0.1.17-linux-x86_64.AppImage -./T4-Code-0.1.17-linux-x86_64.AppImage +wget https://github.com/LycaonLLC/t4-code/releases/download/v0.1.18/T4-Code-0.1.18-linux-x86_64.AppImage +chmod +x T4-Code-0.1.18-linux-x86_64.AppImage +./T4-Code-0.1.18-linux-x86_64.AppImage ``` ### macOS (Apple Silicon) > [!WARNING] -> **The macOS v0.1.17 build is unsigned and unnotarized.** Apple has not signed or notarized it, so Gatekeeper can report a "damaged" app or an unidentified developer. Only continue if you trust the release from this repository. You can always build from source instead. +> **The macOS v0.1.18 build is unsigned and unnotarized.** Apple has not signed or notarized it, so Gatekeeper can report a "damaged" app or an unidentified developer. Only continue if you trust the release from this repository. You can always build from source instead. -1. Download [`T4-Code-0.1.17-mac-arm64.dmg`](https://github.com/LycaonLLC/t4-code/releases/download/v0.1.17/T4-Code-0.1.17-mac-arm64.dmg) (or [`T4-Code-0.1.17-mac-arm64.zip`](https://github.com/LycaonLLC/t4-code/releases/download/v0.1.17/T4-Code-0.1.17-mac-arm64.zip)). +1. Download [`T4-Code-0.1.18-mac-arm64.dmg`](https://github.com/LycaonLLC/t4-code/releases/download/v0.1.18/T4-Code-0.1.18-mac-arm64.dmg) (or [`T4-Code-0.1.18-mac-arm64.zip`](https://github.com/LycaonLLC/t4-code/releases/download/v0.1.18/T4-Code-0.1.18-mac-arm64.zip)). 2. Drag `T4 Code.app` into `/Applications`. 3. If Gatekeeper blocks the app and you choose to proceed, remove the quarantine attributes from the copied app bundle: diff --git a/SECURITY.md b/SECURITY.md index c4d11200..0c9923ac 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -26,4 +26,4 @@ We read every report and will reply to tell you what happens next. This is a sma - T4 Code is a desktop client. The OMP runtime is a separate project; runtime vulnerabilities belong at . - Pairing credentials are encrypted with the OS keychain via Electron `safeStorage`. Reports about credential handling, the pairing flow, or the `t4-code://` deep-link handler are especially welcome. -- The macOS v0.1.17 build is unsigned and unnotarized; that is a known, disclosed limitation, not a vulnerability report. Removing `com.apple.quarantine` changes Gatekeeper handling but does not sign, notarize, or verify the app. +- The macOS v0.1.18 build is unsigned and unnotarized; that is a known, disclosed limitation, not a vulnerability report. Removing `com.apple.quarantine` changes Gatekeeper handling but does not sign, notarize, or verify the app. diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 0e19198f..cc3d33f1 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,6 +1,6 @@ { "name": "@t4-code/desktop", - "version": "0.1.17", + "version": "0.1.18", "private": true, "type": "module", "main": "dist-electron/main.cjs", diff --git a/apps/desktop/src/target-manager.ts b/apps/desktop/src/target-manager.ts index 42cc6e5e..d5dc6d3d 100644 --- a/apps/desktop/src/target-manager.ts +++ b/apps/desktop/src/target-manager.ts @@ -339,7 +339,7 @@ export class DesktopTargetManager { capabilities: requestedCapabilities, requestedFeatures: REQUESTED_FEATURES, compatibilityRequestedFeatures: COMPATIBILITY_FEATURES, - client: { name: "T4 Code", version: "0.1.17", build: "desktop", platform: process.platform }, + client: { name: "T4 Code", version: "0.1.18", build: "desktop", platform: process.platform }, reconnect: { attemptCap: 12, baseMs: 250, maxMs: 10_000 }, }; const client = createOmpClient(clientOptions); diff --git a/apps/mobile/capacitor.config.json b/apps/mobile/capacitor.config.json index 1d615e90..76ba06a8 100644 --- a/apps/mobile/capacitor.config.json +++ b/apps/mobile/capacitor.config.json @@ -3,7 +3,7 @@ "appName": "T4 Code", "webDir": "dist", "loggingBehavior": "debug", - "appendUserAgent": " T4CodeMobile/0.1.17", + "appendUserAgent": " T4CodeMobile/0.1.18", "android": { "path": "android", "minWebViewVersion": 60, diff --git a/apps/mobile/package.json b/apps/mobile/package.json index 929c8953..365dedad 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -1,6 +1,6 @@ { "name": "@t4-code/mobile", - "version": "0.1.17", + "version": "0.1.18", "private": true, "type": "module", "scripts": { diff --git a/apps/site/package.json b/apps/site/package.json index 5609eef2..14e35610 100644 --- a/apps/site/package.json +++ b/apps/site/package.json @@ -1,6 +1,6 @@ { "name": "@t4-code/site", - "version": "0.1.17", + "version": "0.1.18", "private": true, "type": "module", "scripts": { diff --git a/apps/site/src/release.ts b/apps/site/src/release.ts index 8855e00c..0f6d544e 100644 --- a/apps/site/src/release.ts +++ b/apps/site/src/release.ts @@ -5,16 +5,16 @@ export const SITE_URL = "https://t4code.net"; export const DOCS_URL = `${SITE_URL}/docs`; export const REPO_URL = "https://github.com/LycaonLLC/t4-code"; export const OMP_URL = "https://github.com/can1357/oh-my-pi"; -export const OMP_RUNTIME_VERSION = "16.5.2"; -export const OMP_RUNTIME_COMMIT = "264958b23acac16baaf3bf0024129dc1a57f9d14"; -export const OMP_RUNTIME_TAG = "t4code-16.5.2-appserver-4"; +export const OMP_RUNTIME_VERSION = "17.0.0"; +export const OMP_RUNTIME_COMMIT = "6e2f2350cfe9e6f5db691c311333cae33cdb62ba"; +export const OMP_RUNTIME_TAG = "t4code-17.0.0-appserver-1"; export const OMP_RUNTIME_URL = `https://github.com/lyc-aon/oh-my-pi/tree/${OMP_RUNTIME_TAG}`; -export const OMP_UPSTREAM_TAG = "v16.5.2"; -export const OMP_UPSTREAM_COMMIT = "7d02778c60f4b5db60f84bedbca79d6e64cb91f5"; +export const OMP_UPSTREAM_TAG = "v17.0.0"; +export const OMP_UPSTREAM_COMMIT = "d5cd24f39a951bfbd50dc8f50bcf095d59694d6c"; export const OMP_UPSTREAM_URL = `${OMP_URL}/tree/${OMP_UPSTREAM_TAG}`; export const APP_WIRE_VERSION = "0.5.5"; -export const RELEASE_TAG = "v0.1.17"; -export const RELEASE_VERSION = "0.1.17"; +export const RELEASE_TAG = "v0.1.18"; +export const RELEASE_VERSION = "0.1.18"; export const RELEASES_URL = `${REPO_URL}/releases/tag/${RELEASE_TAG}`; export type Platform = "android" | "linux" | "mac"; @@ -48,11 +48,11 @@ function asset( } export const RELEASE_ASSETS: readonly ReleaseAsset[] = [ - asset("android", "apk", "universal", "T4-Code-0.1.17-android.apk", "Android APK"), - asset("linux", "deb", "x86_64", "T4-Code-0.1.17-linux-amd64.deb", "Linux .deb"), - asset("linux", "appimage", "x86_64", "T4-Code-0.1.17-linux-x86_64.AppImage", "Linux AppImage"), - asset("mac", "dmg", "arm64", "T4-Code-0.1.17-mac-arm64.dmg", "macOS .dmg"), - asset("mac", "zip", "arm64", "T4-Code-0.1.17-mac-arm64.zip", "macOS .zip"), + asset("android", "apk", "universal", "T4-Code-0.1.18-android.apk", "Android APK"), + asset("linux", "deb", "x86_64", "T4-Code-0.1.18-linux-amd64.deb", "Linux .deb"), + asset("linux", "appimage", "x86_64", "T4-Code-0.1.18-linux-x86_64.AppImage", "Linux AppImage"), + asset("mac", "dmg", "arm64", "T4-Code-0.1.18-mac-arm64.dmg", "macOS .dmg"), + asset("mac", "zip", "arm64", "T4-Code-0.1.18-mac-arm64.zip", "macOS .zip"), ]; export function assetsFor(platform: Platform): readonly ReleaseAsset[] { diff --git a/apps/site/test/release.test.ts b/apps/site/test/release.test.ts index 35596194..c7be4ad1 100644 --- a/apps/site/test/release.test.ts +++ b/apps/site/test/release.test.ts @@ -1,4 +1,4 @@ -// Release contract guard: exact v0.1.17 asset names and URLs, and the +// Release contract guard: exact v0.1.18 asset names and URLs, and the // platform-detection rule the hero download button relies on. import { describe, expect, it } from "vite-plus/test"; import { @@ -19,13 +19,13 @@ import { } from "../src/release.ts"; describe("release assets", () => { - it("carries the five contracted v0.1.17 filenames", () => { + it("carries the five contracted v0.1.18 filenames", () => { expect(RELEASE_ASSETS.map((a) => a.filename)).toEqual([ - "T4-Code-0.1.17-android.apk", - "T4-Code-0.1.17-linux-amd64.deb", - "T4-Code-0.1.17-linux-x86_64.AppImage", - "T4-Code-0.1.17-mac-arm64.dmg", - "T4-Code-0.1.17-mac-arm64.zip", + "T4-Code-0.1.18-android.apk", + "T4-Code-0.1.18-linux-amd64.deb", + "T4-Code-0.1.18-linux-x86_64.AppImage", + "T4-Code-0.1.18-mac-arm64.dmg", + "T4-Code-0.1.18-mac-arm64.zip", ]); }); @@ -37,8 +37,8 @@ describe("release assets", () => { it("targets the public LycaonLLC repo", () => { expect(REPO_URL).toBe("https://github.com/LycaonLLC/t4-code"); - expect(RELEASE_TAG).toBe("v0.1.17"); - expect(RELEASE_VERSION).toBe("0.1.17"); + expect(RELEASE_TAG).toBe("v0.1.18"); + expect(RELEASE_VERSION).toBe("0.1.18"); }); it("splits assets by platform with correct architectures", () => { @@ -59,14 +59,14 @@ describe("release assets", () => { describe("OMP integration contract", () => { it("pins the verified runtime tag, commit, and app-wire package", () => { - expect(OMP_RUNTIME_TAG).toBe("t4code-16.5.2-appserver-4"); - expect(OMP_RUNTIME_COMMIT).toBe("264958b23acac16baaf3bf0024129dc1a57f9d14"); + expect(OMP_RUNTIME_TAG).toBe("t4code-17.0.0-appserver-1"); + expect(OMP_RUNTIME_COMMIT).toBe("6e2f2350cfe9e6f5db691c311333cae33cdb62ba"); expect(OMP_RUNTIME_URL).toBe( - "https://github.com/lyc-aon/oh-my-pi/tree/t4code-16.5.2-appserver-4", + "https://github.com/lyc-aon/oh-my-pi/tree/t4code-17.0.0-appserver-1", ); - expect(OMP_UPSTREAM_TAG).toBe("v16.5.2"); - expect(OMP_UPSTREAM_COMMIT).toBe("7d02778c60f4b5db60f84bedbca79d6e64cb91f5"); - expect(OMP_UPSTREAM_URL).toBe("https://github.com/can1357/oh-my-pi/tree/v16.5.2"); + expect(OMP_UPSTREAM_TAG).toBe("v17.0.0"); + expect(OMP_UPSTREAM_COMMIT).toBe("d5cd24f39a951bfbd50dc8f50bcf095d59694d6c"); + expect(OMP_UPSTREAM_URL).toBe("https://github.com/can1357/oh-my-pi/tree/v17.0.0"); expect(APP_WIRE_VERSION).toBe("0.5.5"); }); }); diff --git a/apps/web/package.json b/apps/web/package.json index 16ef4f94..fd7a3ccd 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@t4-code/web", - "version": "0.1.17", + "version": "0.1.18", "private": true, "type": "module", "scripts": { diff --git a/apps/web/src/app.css b/apps/web/src/app.css index 747af106..991e740e 100644 --- a/apps/web/src/app.css +++ b/apps/web/src/app.css @@ -36,6 +36,7 @@ height: 100dvh; max-width: 100%; width: 100%; + isolation: isolate; overflow: hidden; } } diff --git a/apps/web/src/platform/browser-shell-port.ts b/apps/web/src/platform/browser-shell-port.ts index 21599e91..caa835cd 100644 --- a/apps/web/src/platform/browser-shell-port.ts +++ b/apps/web/src/platform/browser-shell-port.ts @@ -279,7 +279,7 @@ export function createBrowserShellPort( }, client: { name: "T4 Code", - version: "0.1.17", + version: "0.1.18", build: mobilePlatform ?? "browser", platform: mobilePlatform ?? (platform === "darwin" ? "darwin" : "linux"), }, diff --git a/compat/omp-app-matrix.json b/compat/omp-app-matrix.json index b6a3035e..847b51f3 100644 --- a/compat/omp-app-matrix.json +++ b/compat/omp-app-matrix.json @@ -12,14 +12,14 @@ }, "verifiedRuntime": { "package": "omp", - "version": "16.5.2", + "version": "17.0.0", "sourceRepository": "https://github.com/lyc-aon/oh-my-pi", - "sourceCommit": "264958b23acac16baaf3bf0024129dc1a57f9d14", - "sourceUrl": "https://github.com/lyc-aon/oh-my-pi/commit/264958b23acac16baaf3bf0024129dc1a57f9d14", - "sourceTag": "t4code-16.5.2-appserver-4", + "sourceCommit": "6e2f2350cfe9e6f5db691c311333cae33cdb62ba", + "sourceUrl": "https://github.com/lyc-aon/oh-my-pi/commit/6e2f2350cfe9e6f5db691c311333cae33cdb62ba", + "sourceTag": "t4code-17.0.0-appserver-1", "upstreamRepository": "https://github.com/can1357/oh-my-pi", - "upstreamTag": "v16.5.2", - "upstreamCommit": "7d02778c60f4b5db60f84bedbca79d6e64cb91f5", + "upstreamTag": "v17.0.0", + "upstreamCommit": "d5cd24f39a951bfbd50dc8f50bcf095d59694d6c", "integrationPatches": [ "bounded-growing-session-replay", "complete-session-event-projection", @@ -60,6 +60,6 @@ }, "desktop": { "package": "@t4-code/protocol", - "version": "0.1.17" + "version": "0.1.18" } } diff --git a/docs/CURRENT_RELEASE_NOTES.md b/docs/CURRENT_RELEASE_NOTES.md index 8decc221..88930247 100644 --- a/docs/CURRENT_RELEASE_NOTES.md +++ b/docs/CURRENT_RELEASE_NOTES.md @@ -1,18 +1,16 @@ -## Empty working-folder cleanup +## OMP 17 compatibility -T4 Code v0.1.17 adds **Remove shortcut** to the menu for a working folder with no current sessions. The action hides that row in the current T4 Code client. It does not delete the folder, archived transcripts, or any other OMP data. +T4 Code v0.1.18 moves the verified runtime to OMP 17.0.0 without changing the `omp-app/1` client contract. Existing T4 clients keep the same app-wire frames and capability negotiation. -The preference persists across restarts. Archived sessions remain available, and their folder menu provides **Show shortcut**. Creating or restoring a current session also makes the folder visible again. Other T4 Code clients keep their own shortcut preferences. +The public integration preserves bounded replay for growing sessions, structured tool-result details, child-agent transcript and image projection, session lifecycle controls, two-client convergence, reconnect-safe history, and atomic maintenance drain. -The removal action stays disabled until the host has supplied a complete session inventory. Keyboard focus moves to the next useful rail control after removal, and the menu keeps a 44-pixel touch target on narrow screens. +## Runtime provenance -## Runtime compatibility +T4 Code v0.1.18 vendors app-wire 0.5.5 from integration commit [6a87fa64](https://github.com/lyc-aon/oh-my-pi/commit/6a87fa6407ebff20417b4d52885a6bb3091003ea), source tree `a2495fe8781c979184fe7fb9a6d37d8f33bad30f`. Image prompts activate only when the host advertises the additive image capability. -T4 Code v0.1.17 vendors app-wire 0.5.5 from integration commit [6a87fa64](https://github.com/lyc-aon/oh-my-pi/commit/6a87fa6407ebff20417b4d52885a6bb3091003ea), source tree `a2495fe8781c979184fe7fb9a6d37d8f33bad30f`. Image prompts activate only when the host advertises the additive image capability; the compatibility handshake keeps older appservers available. +The matching OMP 17.0.0 runtime is built from [6e2f2350](https://github.com/lyc-aon/oh-my-pi/commit/6e2f2350cfe9e6f5db691c311333cae33cdb62ba) and tagged [t4code-17.0.0-appserver-1](https://github.com/lyc-aon/oh-my-pi/tree/t4code-17.0.0-appserver-1). Fork CI requires the release commit to descend from the exact official base before it publishes integration binaries. -The matching OMP 16.5.2 runtime is built from [264958b2](https://github.com/lyc-aon/oh-my-pi/commit/264958b23acac16baaf3bf0024129dc1a57f9d14) and tagged [t4code-16.5.2-appserver-4](https://github.com/lyc-aon/oh-my-pi/tree/t4code-16.5.2-appserver-4). The semantic runtime feature remains the appserver-2 implementation: bounded child-agent transcript streaming, structured tool-result details, bounded subagent reads, and verified child-transcript image reads. Appserver-4 fixes the clean-source TypeScript/build check and permanently adds the appserver type check plus full runtime tests to the gates that must pass before OMP release binaries publish. - -The integration is based on the official upstream [v16.5.2 tag](https://github.com/can1357/oh-my-pi/tree/v16.5.2), commit [7d02778c](https://github.com/can1357/oh-my-pi/commit/7d02778c60f4b5db60f84bedbca79d6e64cb91f5). Official upstream OMP v16.5.2 has no `appserver` command and cannot host T4 Code. +The integration is based on the official upstream [v17.0.0 tag](https://github.com/can1357/oh-my-pi/tree/v17.0.0), commit [d5cd24f3](https://github.com/can1357/oh-my-pi/commit/d5cd24f39a951bfbd50dc8f50bcf095d59694d6c). Official upstream OMP v17.0.0 has no `appserver` command and cannot host T4 Code. ## Packages diff --git a/e2e/site-mobile-docs.spec.ts b/e2e/site-mobile-docs.spec.ts index 04a449ec..64299913 100644 --- a/e2e/site-mobile-docs.spec.ts +++ b/e2e/site-mobile-docs.spec.ts @@ -129,7 +129,7 @@ test("offers the Android APK without hiding desktop downloads", async ({ page }) await expect(androidDownload).toBeVisible(); await expect(androidDownload).toHaveAttribute( "href", - "https://github.com/LycaonLLC/t4-code/releases/download/v0.1.17/T4-Code-0.1.17-android.apk", + "https://github.com/LycaonLLC/t4-code/releases/download/v0.1.18/T4-Code-0.1.18-android.apk", ); await expect(page.getByRole("link", { name: "Download for Linux" }).first()).toBeVisible(); await expect(page.getByRole("link", { name: "macOS build" }).first()).toBeVisible(); diff --git a/package.json b/package.json index 97c6fcdc..756796e7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@t4-code/root", - "version": "0.1.17", + "version": "0.1.18", "private": true, "type": "module", "scripts": { diff --git a/packages/client/package.json b/packages/client/package.json index e0de914a..7ae17400 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@t4-code/client", - "version": "0.1.17", + "version": "0.1.18", "private": true, "type": "module", "exports": { diff --git a/packages/client/src/omp-client-frames.ts b/packages/client/src/omp-client-frames.ts index 5e9f5223..d03009e7 100644 --- a/packages/client/src/omp-client-frames.ts +++ b/packages/client/src/omp-client-frames.ts @@ -66,7 +66,7 @@ export function sendClientHello( v: PROTOCOL_VERSION, type: "hello", protocol: { min: PROTOCOL_VERSION, max: PROTOCOL_VERSION }, - client: options.client ?? { name: "t4-code", version: "0.1.17", build: "client", platform: "electron" }, + client: options.client ?? { name: "t4-code", version: "0.1.18", build: "client", platform: "electron" }, requestedFeatures: [...(options.requestedFeatures ?? ["resume"])], savedCursors, ...(options.capabilities === undefined ? {} : { capabilities: { client: [...options.capabilities] } }), diff --git a/packages/fixture-server/package.json b/packages/fixture-server/package.json index 16189eb6..47393e9d 100644 --- a/packages/fixture-server/package.json +++ b/packages/fixture-server/package.json @@ -1,6 +1,6 @@ { "name": "@t4-code/fixture-server", - "version": "0.1.17", + "version": "0.1.18", "private": true, "type": "module", "exports": { diff --git a/packages/protocol/package.json b/packages/protocol/package.json index 7212c464..fb2378d1 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -1,6 +1,6 @@ { "name": "@t4-code/protocol", - "version": "0.1.17", + "version": "0.1.18", "private": true, "type": "module", "exports": { diff --git a/packages/remote/package.json b/packages/remote/package.json index ccedc82a..415b54d5 100644 --- a/packages/remote/package.json +++ b/packages/remote/package.json @@ -1,6 +1,6 @@ { "name": "@t4-code/remote", - "version": "0.1.17", + "version": "0.1.18", "private": true, "type": "module", "exports": { diff --git a/packages/service-manager/package.json b/packages/service-manager/package.json index 83ab670a..c4e82292 100644 --- a/packages/service-manager/package.json +++ b/packages/service-manager/package.json @@ -1,6 +1,6 @@ { "name": "@t4-code/service-manager", - "version": "0.1.17", + "version": "0.1.18", "private": true, "type": "module", "exports": { diff --git a/packages/ui/package.json b/packages/ui/package.json index 95cb4bc0..ce474cd0 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@t4-code/ui", - "version": "0.1.17", + "version": "0.1.18", "private": true, "type": "module", "exports": { diff --git a/provenance/t3code/imports/f1-shell-20260711.json b/provenance/t3code/imports/f1-shell-20260711.json index 749e23ef..a9bf10c1 100644 --- a/provenance/t3code/imports/f1-shell-20260711.json +++ b/provenance/t3code/imports/f1-shell-20260711.json @@ -30,7 +30,7 @@ "sourceBlobSha": "3ae42a3e61cac54e9fe75cb58a67313241989ec9", "targetPath": "apps/web/src/app.css", "classification": "adapted", - "checksum": "sha256:20dc7fd7b3051f5f49e0fa66fd179dd56e22a9cd7c493e7b77f4ca249811d6ae" + "checksum": "sha256:e8b50f4d76d53409deaad5793ea99a1b13065dd4f5c9d61f1e9cc7d98a75670d" }, { "sourcePath": "apps/web/src/hooks/useResizableWidth.ts", diff --git a/scripts/check-release-consistency.test.mjs b/scripts/check-release-consistency.test.mjs index be6d7778..8b09db49 100644 --- a/scripts/check-release-consistency.test.mjs +++ b/scripts/check-release-consistency.test.mjs @@ -17,28 +17,28 @@ function changed(path, replace) { } test("current source tree has one consistent release version", () => { - assert.deepEqual(collectReleaseConsistencyErrors(files, "v0.1.17"), []); + assert.deepEqual(collectReleaseConsistencyErrors(files, "v0.1.18"), []); }); test("rejects a tag that differs from the package version", () => { assert.ok( collectReleaseConsistencyErrors(files, "v9.9.9").some((error) => - error.includes("release tag v9.9.9 does not match v0.1.17"), + error.includes("release tag v9.9.9 does not match v0.1.18"), ), ); }); test("rejects workspace, site, README, and runtime version drift", () => { const cases = [ - ["apps/web/package.json", (text) => text.replace('"version": "0.1.17"', '"version": "0.1.3"')], + ["apps/web/package.json", (text) => text.replace('"version": "0.1.18"', '"version": "0.1.3"')], [ "apps/site/src/release.ts", - (text) => text.replace('RELEASE_TAG = "v0.1.17"', 'RELEASE_TAG = "v0.1.3"'), + (text) => text.replace('RELEASE_TAG = "v0.1.18"', 'RELEASE_TAG = "v0.1.3"'), ], - ["README.md", (text) => text.replace("Download v0.1.17", "Download v0.1.3")], + ["README.md", (text) => text.replace("Download v0.1.18", "Download v0.1.3")], [ "apps/desktop/src/target-manager.ts", - (text) => text.replace('version: "0.1.17"', 'version: "0.1.3"'), + (text) => text.replace('version: "0.1.18"', 'version: "0.1.3"'), ], [ "apps/site/src/docs/content.ts", @@ -105,13 +105,13 @@ test("rejects drift in verified OMP runtime provenance", () => { const cases = [ (text) => text.replace( - "264958b23acac16baaf3bf0024129dc1a57f9d14", + "6e2f2350cfe9e6f5db691c311333cae33cdb62ba", "0000000000000000000000000000000000000000", ), - (text) => text.replace('"sourceTag": "t4code-16.5.2-appserver-4"', '"sourceTag": "wrong-tag"'), + (text) => text.replace('"sourceTag": "t4code-17.0.0-appserver-1"', '"sourceTag": "wrong-tag"'), (text) => text.replace( - '"upstreamCommit": "7d02778c60f4b5db60f84bedbca79d6e64cb91f5"', + '"upstreamCommit": "d5cd24f39a951bfbd50dc8f50bcf095d59694d6c"', '"upstreamCommit": "0000000000000000000000000000000000000000"', ), (text) => text.replace('"complete-session-event-projection"', '"Wrong integration patch"'), @@ -173,7 +173,7 @@ test("rejects stale README release URLs while allowing historical prose", () => const staleLink = changed("README.md", (text) => `${text}\n[Old release](${oldReleaseUrl})\n`); assert.ok( collectReleaseConsistencyErrors(staleLink).some((error) => - error.includes("release URL for v0.1.3; expected v0.1.17"), + error.includes("release URL for v0.1.3; expected v0.1.18"), ), ); assert.deepEqual(collectReleaseConsistencyErrors(files), []); diff --git a/scripts/tailnet-gateway.mjs b/scripts/tailnet-gateway.mjs index 815da80e..04053d75 100644 --- a/scripts/tailnet-gateway.mjs +++ b/scripts/tailnet-gateway.mjs @@ -268,9 +268,17 @@ async function sendStatic(request, response, webRoot, path) { } } +function guardSocketErrors(socket) { + // Upgrade sockets and custom WebSocket transports can outlive ws' own + // listeners during a close race. Keep one terminal error listener attached + // so a peer reset cannot become an uncaught process-level exception. + socket.on("error", () => socket.destroy()); + return socket; +} + function rejectUpgrade(socket, status, message) { const body = `${message}\n`; - socket.end( + guardSocketErrors(socket).end( `HTTP/1.1 ${status}\r\nConnection: close\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Length: ${Buffer.byteLength(body)}\r\n\r\n${body}`, ); } @@ -289,7 +297,7 @@ function bridgeBrowser(browser, options, activeBrowsers) { const upstream = new WebSocket("ws://omp.local/ws", { perMessageDeflate: false, maxPayload: MAX_FRAME_BYTES, - createConnection: () => connectSocket({ path: options.resolvedAppSocket }), + createConnection: () => guardSocketErrors(connectSocket({ path: options.resolvedAppSocket })), }); const finish = (code = 1011, reason = "gateway connection closed") => { diff --git a/scripts/tailnet-gateway.test.mjs b/scripts/tailnet-gateway.test.mjs index cd99a200..641a063d 100644 --- a/scripts/tailnet-gateway.test.mjs +++ b/scripts/tailnet-gateway.test.mjs @@ -3,6 +3,7 @@ import { chmod, mkdtemp, mkdir, rm, symlink, writeFile } from "node:fs/promises" import { createServer } from "node:http"; import { tmpdir } from "node:os"; import { join } from "node:path"; +import { connect as connectSocket } from "node:net"; import { test } from "node:test"; import WebSocket, { WebSocketServer } from "ws"; @@ -256,6 +257,48 @@ test("gateway rejects cross-origin sockets and bridges only the web and native a } }); +test("gateway survives peer resets while rejecting websocket upgrades", async () => { + const running = await fixture(); + try { + const request = [ + "GET /v1/ws HTTP/1.1", + `Host: ${running.gateway.host}:${running.gateway.port}`, + "Connection: Upgrade", + "Upgrade: websocket", + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==", + "Sec-WebSocket-Version: 13", + "Origin: https://attacker.example-tailnet.ts.net", + "", + "", + ].join("\r\n"); + await Promise.all( + Array.from({ length: 32 }, () => + new Promise((resolvePromise, reject) => { + const socket = connectSocket({ + host: running.gateway.host, + port: running.gateway.port, + }); + socket.once("error", reject); + socket.once("connect", () => { + socket.write(request, () => { + socket.off("error", reject); + socket.on("error", () => {}); + socket.resetAndDestroy(); + resolvePromise(); + }); + }); + }), + ), + ); + await new Promise((resolvePromise) => setTimeout(resolvePromise, 50)); + const healthResponse = await fetch(`${running.url}/healthz`); + assert.equal(healthResponse.status, 200); + assert.equal((await healthResponse.json()).ok, true); + } finally { + await running.close(); + } +}); + test("gateway heartbeat removes a half-open browser whose proxy never forwards close", async () => { const running = await fixture("symlink", { heartbeatIntervalMs: 20 }); try {