diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f40afa1e..f1bfc0d9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -236,7 +236,25 @@ jobs: fi fi - bun run dist:desktop:artifact -- "${args[@]}" + build_desktop_artifact() { + bun run dist:desktop:artifact -- "${args[@]}" + } + + if [[ "${{ matrix.platform }}" == "mac" ]]; then + for attempt in 1 2; do + if build_desktop_artifact; then + break + fi + if [[ "$attempt" -eq 1 ]]; then + echo "macOS desktop packaging failed once; retrying after a short delay..." >&2 + sleep 30 + continue + fi + exit 1 + done + else + build_desktop_artifact + fi - name: Validate packaged artifact outputs shell: bash diff --git a/CHANGELOG.md b/CHANGELOG.md index 109f8e3d..24b10272 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.26.4] - 2026-04-22 + +See [docs/releases/v0.26.4.md](docs/releases/v0.26.4.md) for full notes and [docs/releases/v0.26.4/assets.md](docs/releases/v0.26.4/assets.md) for release asset inventory. + +### Added + +- Add out-of-memory session recovery. + +### Changed + +- Bundle diffs into the server cli. +- Inline diff parsing for cli release. + +### Fixed + +- Publish cli from cjs bin. +- Make npm publish rerun-safe. +- Verify published cli with okcode bin. +- Verify published cli with temp install. + ## [0.26.3] - 2026-04-22 See [docs/releases/v0.26.3.md](docs/releases/v0.26.3.md) for full notes and [docs/releases/v0.26.3/assets.md](docs/releases/v0.26.3/assets.md) for release asset inventory. diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 5b21016c..ef375a55 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,6 +1,6 @@ { "name": "@okcode/desktop", - "version": "0.26.3", + "version": "0.26.4", "private": true, "main": "dist-electron/main.js", "scripts": { diff --git a/apps/mobile/android/app/build.gradle b/apps/mobile/android/app/build.gradle index cedc2ad4..7a9119fd 100644 --- a/apps/mobile/android/app/build.gradle +++ b/apps/mobile/android/app/build.gradle @@ -8,7 +8,7 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 1 - versionName "0.26.3" + versionName "0.26.4" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" aaptOptions { // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps. diff --git a/apps/mobile/ios/App/App.xcodeproj/project.pbxproj b/apps/mobile/ios/App/App.xcodeproj/project.pbxproj index 1eeb11ba..b1e3eab8 100644 --- a/apps/mobile/ios/App/App.xcodeproj/project.pbxproj +++ b/apps/mobile/ios/App/App.xcodeproj/project.pbxproj @@ -306,7 +306,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.26.3; + MARKETING_VERSION = 0.26.4; OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; PRODUCT_BUNDLE_IDENTIFIER = com.openknots.okcode.mobile; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -331,7 +331,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.26.3; + MARKETING_VERSION = 0.26.4; PRODUCT_BUNDLE_IDENTIFIER = com.openknots.okcode.mobile; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = ""; diff --git a/apps/mobile/package.json b/apps/mobile/package.json index f0971127..5ce50bd0 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -1,6 +1,6 @@ { "name": "@okcode/mobile", - "version": "0.26.3", + "version": "0.26.4", "private": true, "type": "module", "scripts": { diff --git a/apps/server/package.json b/apps/server/package.json index 8f8ef2e4..5a010b58 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -1,6 +1,6 @@ { "name": "okcodes", - "version": "0.26.3", + "version": "0.26.4", "license": "MIT", "repository": { "type": "git", diff --git a/apps/server/src/checkpointing/Diffs.ts b/apps/server/src/checkpointing/Diffs.ts index c2f867b9..badedc21 100644 --- a/apps/server/src/checkpointing/Diffs.ts +++ b/apps/server/src/checkpointing/Diffs.ts @@ -1,11 +1,23 @@ -import { parsePatchFiles } from "@pierre/diffs"; - export interface TurnDiffFileSummary { readonly path: string; readonly additions: number; readonly deletions: number; } +interface MutableTurnDiffFileSummary { + path: string; + additions: number; + deletions: number; +} + +function normalizeDiffPath(path: string): string { + return path.replace(/^a\//, "").replace(/^b\//, ""); +} + +function createFileSummary(path: string): MutableTurnDiffFileSummary { + return { path, additions: 0, deletions: 0 }; +} + export function parseTurnDiffFilesFromUnifiedDiff( diff: string, ): ReadonlyArray { @@ -14,14 +26,48 @@ export function parseTurnDiffFilesFromUnifiedDiff( return []; } - const parsedPatches = parsePatchFiles(normalized); - const files = parsedPatches.flatMap((patch) => - patch.files.map((file) => ({ - path: file.name, - additions: file.hunks.reduce((total, hunk) => total + hunk.additionLines, 0), - deletions: file.hunks.reduce((total, hunk) => total + hunk.deletionLines, 0), - })), - ); + const files: MutableTurnDiffFileSummary[] = []; + let currentFile: MutableTurnDiffFileSummary | null = null; + let inHunk = false; + + for (const line of normalized.split("\n")) { + if (line.startsWith("diff --git ")) { + if (currentFile) files.push(currentFile); + + const match = /^diff --git a\/(.+?) b\/(.+)$/.exec(line); + currentFile = createFileSummary(normalizeDiffPath(match?.[2] ?? match?.[1] ?? line)); + inHunk = false; + continue; + } + + if (!currentFile) { + continue; + } + + if (line.startsWith("@@")) { + inHunk = true; + continue; + } + + if (!inHunk) { + continue; + } + + if (line.startsWith("+++ ") || line.startsWith("--- ")) { + continue; + } + + if (line.startsWith("+")) { + currentFile.additions += 1; + continue; + } + + if (line.startsWith("-")) { + currentFile.deletions += 1; + } + } + + if (currentFile) files.push(currentFile); return files.toSorted((left, right) => left.path.localeCompare(right.path)); } diff --git a/apps/server/tsdown.config.ts b/apps/server/tsdown.config.ts index c1470751..1c86b342 100644 --- a/apps/server/tsdown.config.ts +++ b/apps/server/tsdown.config.ts @@ -9,7 +9,7 @@ export default defineConfig({ outDir: "dist", sourcemap: true, clean: true, - noExternal: (id) => id.startsWith("@okcode/"), + noExternal: (id) => id.startsWith("@okcode/") || id === "@pierre/diffs", inlineOnly: false, banner: { js: "#!/usr/bin/env node\n", diff --git a/apps/web/package.json b/apps/web/package.json index 2018c6e5..7c70ee1b 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@okcode/web", - "version": "0.26.3", + "version": "0.26.4", "private": true, "type": "module", "scripts": { diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index 79eed3a6..2c0e24a4 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -3895,6 +3895,17 @@ export default function ChatView({ }); }; + const onRecoverFromOutOfMemory = async () => { + const api = readNativeApi(); + if (!api || !activeThread || isRemoteActionBlocked) return; + await api.orchestration.dispatchCommand({ + type: "thread.session.stop", + commandId: newCommandId(), + threadId: activeThread.id, + createdAt: new Date().toISOString(), + }); + }; + const onClearQueue = useCallback(() => { setOptimisticUserMessages((existing) => { for (const msg of existing) { diff --git a/docs/releases/README.md b/docs/releases/README.md index 8394f14b..70fc129f 100644 --- a/docs/releases/README.md +++ b/docs/releases/README.md @@ -9,6 +9,7 @@ Use this directory for versioned release notes and asset manifests only: | Version | Summary | Assets | | -------------------- | --------------------------------------------------------------------------------------------------------- | ----------------------------- | +| [0.26.4](v0.26.4.md) | Out-of-memory session recovery and CLI release hardening | [manifest](v0.26.4/assets.md) | | [0.26.3](v0.26.3.md) | Release with 2 new feature(s) | [manifest](v0.26.3/assets.md) | | [0.26.2](v0.26.2.md) | Release with 1 new feature(s), 4 fix(es), 3 improvement(s) | [manifest](v0.26.2/assets.md) | | [0.26.0](v0.26.0.md) | File-content search, desktop terminal docking, transport sna | [manifest](v0.26.0/assets.md) | diff --git a/docs/releases/v0.26.4.md b/docs/releases/v0.26.4.md new file mode 100644 index 00000000..c434e884 --- /dev/null +++ b/docs/releases/v0.26.4.md @@ -0,0 +1,38 @@ +# OK Code v0.26.4 + +**Date:** 2026-04-22 +**Tag:** [`v0.26.4`](https://github.com/OpenKnots/okcode/releases/tag/v0.26.4) + +## Summary + +Re-cut release after `v0.26.3` was already present on npm; ships the same branch state with out-of-memory session recovery and CLI release hardening. + +## Highlights + +- **Add out-of-memory session recovery.** +- **Bundle diffs into the server cli.** +- **Inline diff parsing for cli release.** +- **Publish cli from cjs bin.** +- **Make npm publish rerun-safe.** +- **Verify published cli with okcode bin.** +- **Verify published cli with temp install.** + +## Breaking changes + +- None. + +## Upgrade and install + +- **CLI:** `npm install -g okcodes@0.26.4` once the desktop/CLI release workflow finishes. +- **Desktop:** Download from [GitHub Releases](https://github.com/OpenKnots/okcode/releases/tag/v0.26.4). Filenames are listed in [assets.md](v0.26.4/assets.md). +- **iOS:** Available via TestFlight after the separate Release iOS workflow is dispatched for this tag. + +## Known limitations + +OK Code remains early work in progress. Expect rough edges around session recovery, streaming edge cases, and platform-specific desktop behavior. Report issues on GitHub. + +## Release operations + +- Review the [asset manifest](v0.26.4/assets.md) to confirm every expected GitHub Release attachment is present. +- Use the [rollout checklist](v0.26.4/rollout-checklist.md) to walk the desktop/CLI release plus the separate iOS TestFlight dispatch through post-release verification. +- Use the [soak test plan](v0.26.4/soak-test-plan.md) to validate the highest-risk surfaces after the tag is live. diff --git a/docs/releases/v0.26.4/assets.md b/docs/releases/v0.26.4/assets.md new file mode 100644 index 00000000..d9ee97f1 --- /dev/null +++ b/docs/releases/v0.26.4/assets.md @@ -0,0 +1,57 @@ +# v0.26.4 — Release assets (manifest) + +Binaries are **not** stored in this git repository; they are attached to the [GitHub Release for `v0.26.4`](https://github.com/OpenKnots/okcode/releases/tag/v0.26.4) by the [Release workflow](../../.github/workflows/release.yml). + +The GitHub Release also includes **documentation attachments** (same content as in-repo, stable filenames for download): + +| File | Source in repo | +| --------------------------- | ------------------------------------- | +| `okcode-CHANGELOG.md` | [CHANGELOG.md](../../../CHANGELOG.md) | +| `okcode-RELEASE-NOTES.md` | [v0.26.4.md](../v0.26.4.md) | +| `okcode-ASSETS-MANIFEST.md` | This file | + +After the workflow completes, expect **installer and updater** artifacts similar to the following (exact names may include the product name `OK Code` and version `0.26.4`). + +## Desktop installers and payloads + +| Platform | Kind | Typical pattern | +| ------------------- | -------------- | --------------- | +| macOS Apple Silicon | DMG (signed) | `*.dmg` (arm64) | +| macOS Intel | DMG (signed) | `*.dmg` (x64) | +| macOS | ZIP (updater) | `*.zip` | +| Linux x64 | AppImage | `*.AppImage` | +| Windows x64 | NSIS installer | `*.exe` | + +### macOS code signing and notarization + +All macOS DMG and ZIP payloads are **code-signed** with an Apple Developer ID certificate and **notarized** via the Apple notarization service. Gatekeeper will verify the signature on first launch. The hardened runtime is enabled with entitlements defined in `apps/desktop/resources/entitlements.mac.plist`. + +## Electron updater metadata + +| File | Purpose | +| ------------------ | --------------------------------------------------------- | +| `latest-mac.yml` | macOS update manifest (merged from per-arch builds in CI) | +| `latest-linux.yml` | Linux update manifest | +| `latest.yml` | Windows update manifest | +| `*.blockmap` | Differential download block maps | + +## iOS (TestFlight) + +The iOS build is uploaded directly to App Store Connect / TestFlight by the separately dispatched [Release iOS workflow](../../.github/workflows/release-ios.yml). No IPA artifact is attached to the GitHub Release. + +| Detail | Value | +| ----------------- | ------------------------------------------ | +| Bundle ID | `com.openknots.okcode.mobile` | +| Marketing version | `0.26.4` | +| Build number | Set from `GITHUB_RUN_NUMBER` at build time | + +## Checksums + +SHA-256 checksums are not committed here; verify downloads via GitHub's release UI or `gh release download` if you use the GitHub CLI. + +## Operational references + +| File | Purpose | +| -------------------------------------------- | ----------------------------------------------------------------- | +| [rollout-checklist.md](rollout-checklist.md) | Step-by-step release playbook from preflight through post-release | +| [soak-test-plan.md](soak-test-plan.md) | Structured release validation for the highest-risk surfaces | diff --git a/docs/releases/v0.26.4/rollout-checklist.md b/docs/releases/v0.26.4/rollout-checklist.md new file mode 100644 index 00000000..6369c225 --- /dev/null +++ b/docs/releases/v0.26.4/rollout-checklist.md @@ -0,0 +1,67 @@ +# v0.26.4 Rollout Checklist + +Step-by-step playbook for the v0.26.4 release. Each phase must complete before advancing. + +## Phase 0: Pre-flight + +- [ ] Verify all release package versions are `0.26.4`: + - `apps/server/package.json` + - `apps/desktop/package.json` + - `apps/web/package.json` + - `apps/mobile/package.json` + - `packages/contracts/package.json` +- [ ] Verify Android `versionName` and iOS `MARKETING_VERSION` both match `0.26.4`. +- [ ] Confirm `CHANGELOG.md` has `## [0.26.4] - 2026-04-22`. +- [ ] Confirm `docs/releases/v0.26.4.md` exists with Summary, Highlights, Upgrade and install, and Release operations sections. +- [ ] Confirm `docs/releases/v0.26.4/assets.md` exists and lists every expected attachment class. +- [ ] Confirm `docs/releases/v0.26.4/rollout-checklist.md` and `docs/releases/v0.26.4/soak-test-plan.md` exist. +- [ ] Confirm `docs/releases/README.md` includes the v0.26.4 row. +- [ ] Run `bun run release:validate 0.26.4`. +- [ ] Confirm the working tree is clean. +- [ ] Confirm you are on `main`. + +### Quality gates + +- [ ] `bun run fmt:check` +- [ ] `bun run lint` +- [ ] `bun run typecheck` +- [ ] `bun run test` +- [ ] `bun run --cwd apps/web test:browser` +- [ ] `bun run test:desktop-smoke` +- [ ] `bun run release:smoke` + +## Phase 1: Publish + +- [ ] Push the release-prep commit to `main`. +- [ ] Create and push tag `v0.26.4`. +- [ ] Verify the coordinated `release.yml` workflow starts. +- [ ] Trigger `release-ios.yml` manually for `v0.26.4` (or the matching release ref if the tag is unavailable). +- [ ] Monitor `release.yml` through Preflight, Desktop builds, Publish CLI, Publish GitHub Release, and Finalize release. +- [ ] Monitor `release-ios.yml` through Preflight, iOS signing preflight, and iOS TestFlight. + +### Asset verification + +- [ ] GitHub Release body matches `docs/releases/v0.26.4.md`. +- [ ] `okcode-CHANGELOG.md` is attached. +- [ ] `okcode-RELEASE-NOTES.md` is attached. +- [ ] `okcode-ASSETS-MANIFEST.md` is attached. +- [ ] macOS release artifacts are attached: DMG, ZIP, updater manifest, and blockmaps. +- [ ] Linux release artifacts are attached: AppImage and updater manifest if generated. +- [ ] Windows release artifacts are attached: installer, updater manifest, and blockmaps. +- [ ] If the Intel compatibility workflow is run, confirm the x64 macOS DMG is attached separately. + +## Phase 2: Post-release verification + +- [ ] `npm exec --yes --package okcodes@0.26.4 -- okcode --version` returns `0.26.4`. +- [ ] macOS installer launches and passes Gatekeeper. +- [ ] Linux AppImage launches. +- [ ] Windows installer installs and launches. +- [ ] Desktop auto-update metadata is present for supported platforms. +- [ ] If iOS signing was enabled, confirm the new TestFlight build appears. +- [ ] Confirm the finalize job did not need to push another version-alignment commit, or review its no-op output if versions were already aligned before tagging. + +## Phase 3: Follow-through + +- [ ] Trigger the Intel compatibility workflow if macOS x64 artifacts are required for this train. +- [ ] Update external release references or announcements. +- [ ] Monitor reports for regressions in provider onboarding, auth flows, release packaging, and cross-platform install/update behavior. diff --git a/docs/releases/v0.26.4/soak-test-plan.md b/docs/releases/v0.26.4/soak-test-plan.md new file mode 100644 index 00000000..62cf7feb --- /dev/null +++ b/docs/releases/v0.26.4/soak-test-plan.md @@ -0,0 +1,39 @@ +# v0.26.4 Soak Test Plan + +Structured validation plan for the highest-risk surfaces in v0.26.4. + +## 1. Provider onboarding and auth flows + +| Step | Expected | Pass | +| ----------------------------------------------------------------- | ---------------------------------------------------------------------------- | ---- | +| Configure each primary provider from Settings | Provider setup screens save cleanly and validation messages stay actionable | [ ] | +| Exercise Claude and OpenClaw auth flows after reload | Saved credentials and provider state restore without stale or conflicting UI | [ ] | +| Start a Codex or Copilot-backed conversation after provider setup | Turn creation, streaming, and provider selection remain consistent | [ ] | +| Trigger an auth failure intentionally | Errors surface clearly without leaking secrets or breaking follow-up retries | [ ] | + +## 2. Settings and configuration surfaces + +| Step | Expected | Pass | +| ---------------------------------------------------------- | --------------------------------------------------------------------- | ---- | +| Open the settings route on desktop and narrow layouts | Navigation stays stable and each section is reachable | [ ] | +| Change provider availability and default options | Picker filtering and availability controls update without stale state | [ ] | +| Use hotkey configuration controls and reset actions | Shortcuts persist, restore, and do not regress the editor UI | [ ] | +| Open the browser-preview-related settings and helper links | The helper flow launches correctly and does not break the app shell | [ ] | + +## 3. Runtime and review workflows + +| Step | Expected | Pass | +| --------------------------------------------------------------- | ------------------------------------------------------------------ | ---- | +| Run a thread that emits runtime events and reconnect mid-stream | Session state and event feeds remain consistent after reconnect | [ ] | +| Open the PR review dashboard with recent review history | Dashboard loads quickly and shows the expected recent activity | [ ] | +| Navigate between threads, projects, and restored sessions | Cached lookups, projections, and route transitions stay responsive | [ ] | +| Trigger browser preview and workspace activity during a turn | The app avoids flicker, stale panes, and blocked input | [ ] | + +## 4. Desktop, CLI, and release packaging + +| Step | Expected | Pass | +| ----------------------------------------------------------------------- | ------------------------------------------------------ | ---- | +| Run `bun run test:desktop-smoke` on the release branch | Desktop packaging smoke remains green | [ ] | +| Run `bun run release:smoke` before and after tagging | Release-specific workflow checks remain green | [ ] | +| Verify a packaged desktop artifact launches and reports the new version | Installed app opens cleanly and reports v0.26.4 | [ ] | +| Verify the CLI package after publish or from a packed tarball | `okcode --version` and help commands resolve correctly | [ ] | diff --git a/packages/contracts/package.json b/packages/contracts/package.json index ba60f294..f6422589 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -1,6 +1,6 @@ { "name": "@okcode/contracts", - "version": "0.26.3", + "version": "0.26.4", "private": true, "files": [ "dist"