diff --git a/bun.lock b/bun.lock
index 2c18bde1..eeea1ce9 100644
--- a/bun.lock
+++ b/bun.lock
@@ -30,7 +30,7 @@
"@ibm/plex": "^6.4.1",
"@lezer/highlight": "^1.2.3",
"@octokit/rest": "^22.0.1",
- "@opencode-ai/sdk": "^1.1.53",
+ "@opencode-ai/sdk": "^1.1.65",
"@radix-ui/react-collapsible": "^1.1.12",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16",
@@ -95,7 +95,7 @@
},
"packages/desktop": {
"name": "@openchamber/desktop",
- "version": "1.6.6",
+ "version": "1.6.8",
"devDependencies": {
"@tauri-apps/cli": "^2",
"@types/node": "^24.3.1",
@@ -104,7 +104,7 @@
},
"packages/ui": {
"name": "@openchamber/ui",
- "version": "1.6.6",
+ "version": "1.6.8",
"dependencies": {
"@codemirror/autocomplete": "^6.20.0",
"@codemirror/commands": "^6.10.1",
@@ -132,8 +132,8 @@
"@fontsource/ibm-plex-sans": "^5.1.1",
"@ibm/plex": "^6.4.1",
"@lezer/highlight": "^1.2.3",
- "@opencode-ai/sdk": "^1.1.53",
- "@pierre/diffs": "^1.0.5",
+ "@opencode-ai/sdk": "^1.1.65",
+ "@pierre/diffs": "1.1.0-beta.13",
"@radix-ui/react-collapsible": "^1.1.12",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16",
@@ -198,10 +198,10 @@
},
"packages/vscode": {
"name": "openchamber",
- "version": "1.6.6",
+ "version": "1.6.8",
"dependencies": {
"@openchamber/ui": "workspace:*",
- "@opencode-ai/sdk": "^1.1.53",
+ "@opencode-ai/sdk": "^1.1.65",
"adm-zip": "^0.5.16",
"jsonc-parser": "^3.3.1",
"react": "^19.1.1",
@@ -221,7 +221,7 @@
},
"packages/web": {
"name": "@openchamber/web",
- "version": "1.6.6",
+ "version": "1.6.8",
"bin": {
"openchamber": "./bin/cli.js",
},
@@ -230,7 +230,7 @@
"@fontsource/ibm-plex-sans": "^5.1.1",
"@ibm/plex": "^6.4.1",
"@octokit/rest": "^22.0.1",
- "@opencode-ai/sdk": "^1.1.53",
+ "@opencode-ai/sdk": "^1.1.65",
"@radix-ui/react-collapsible": "^1.1.12",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16",
@@ -843,9 +843,9 @@
"@openchamber/web": ["@openchamber/web@workspace:packages/web"],
- "@opencode-ai/sdk": ["@opencode-ai/sdk@1.1.53", "", {}, "sha512-RUIVnPOP1CyyU32FrOOYuE7Ge51lOBuhaFp2NSX98ncApT7ffoNetmwzqrhOiJQgZB1KrbCHLYOCK6AZfacxag=="],
+ "@opencode-ai/sdk": ["@opencode-ai/sdk@1.1.65", "", {}, "sha512-XlpgQJQ5WwO4tYgyyHoTT0NAB5/1StXonabVUAYVpW0JdtbwWFSdFEaLkWx6CU7MNW6ELP+SMC4n6wWO6zRW8Q=="],
- "@pierre/diffs": ["@pierre/diffs@1.0.10", "", { "dependencies": { "@shikijs/core": "^3.0.0", "@shikijs/engine-javascript": "^3.0.0", "@shikijs/transformers": "^3.0.0", "diff": "8.0.3", "hast-util-to-html": "9.0.5", "lru_map": "0.4.1", "shiki": "^3.0.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-ahkpfS30NfaB+PBxnf0/Mc20ySBRTQmM28a7Ojpd0UZixmTyhGhJfBFjvmhX8dSzR22lB3h3OIMMxpB4yYTIOQ=="],
+ "@pierre/diffs": ["@pierre/diffs@1.1.0-beta.13", "", { "dependencies": { "@shikijs/transformers": "^3.0.0", "diff": "8.0.3", "hast-util-to-html": "9.0.5", "lru_map": "0.4.1", "shiki": "^3.0.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-D35rxDu5V7XHX5aVGU6PF12GhscL+I+9QYgxK/i3h0d2XSirAxDdVNm49aYwlOhgmdvL0NbS1IHxPswVB5yJvw=="],
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
diff --git a/package.json b/package.json
index f6bd1c75..0312130d 100644
--- a/package.json
+++ b/package.json
@@ -83,7 +83,7 @@
"@ibm/plex": "^6.4.1",
"@lezer/highlight": "^1.2.3",
"@octokit/rest": "^22.0.1",
- "@opencode-ai/sdk": "^1.1.53",
+ "@opencode-ai/sdk": "^1.1.65",
"@radix-ui/react-collapsible": "^1.1.12",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16",
diff --git a/packages/ui/package.json b/packages/ui/package.json
index d8580cbd..8f9f4a72 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -37,8 +37,8 @@
"@fontsource/ibm-plex-sans": "^5.1.1",
"@ibm/plex": "^6.4.1",
"@lezer/highlight": "^1.2.3",
- "@opencode-ai/sdk": "^1.1.53",
- "@pierre/diffs": "^1.0.5",
+ "@opencode-ai/sdk": "^1.1.65",
+ "@pierre/diffs": "1.1.0-beta.13",
"@radix-ui/react-collapsible": "^1.1.12",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16",
diff --git a/packages/ui/src/components/comments/InlineCommentInput.tsx b/packages/ui/src/components/comments/InlineCommentInput.tsx
index 995f011e..82226186 100644
--- a/packages/ui/src/components/comments/InlineCommentInput.tsx
+++ b/packages/ui/src/components/comments/InlineCommentInput.tsx
@@ -38,24 +38,62 @@ export function InlineCommentInput({
}
}, [lineRange]);
+ const normalizeRange = (range?: { start: number; end: number; side?: 'additions' | 'deletions' }) => {
+ if (!range) return undefined;
+ const start = Math.min(range.start, range.end);
+ const end = Math.max(range.start, range.end);
+ return { ...range, start, end };
+ };
+
+ const displayRange = normalizeRange(lineRange);
+
// Focus on mount (desktop only) or when becoming visible
useEffect(() => {
- if (!isMobile && textareaRef.current) {
- textareaRef.current.focus();
- // Move cursor to end
- const len = textareaRef.current.value.length;
- textareaRef.current.setSelectionRange(len, len);
- } else if (isMobile && textareaRef.current) {
- // Scroll into view on mobile
- textareaRef.current.scrollIntoView({ behavior: 'smooth', block: 'center' });
+ const textarea = textareaRef.current;
+ if (!textarea) return;
+
+ const scrollContainer = textarea.closest('.overlay-scrollbar-container') as HTMLElement | null;
+ const prevScrollTop = scrollContainer?.scrollTop ?? window.scrollY;
+ const prevScrollLeft = scrollContainer?.scrollLeft ?? window.scrollX;
+
+ if (isMobile) {
+ textarea.scrollIntoView({ behavior: 'auto', block: 'nearest' });
+ try {
+ textarea.focus({ preventScroll: true });
+ } catch {
+ textarea.focus();
+ }
+ return;
}
+
+ try {
+ textarea.focus({ preventScroll: true });
+ } catch {
+ textarea.focus();
+ }
+
+ const len = textarea.value.length;
+ try {
+ textarea.setSelectionRange(len, len);
+ } catch (err) {
+ void err;
+ }
+
+ requestAnimationFrame(() => {
+ if (scrollContainer) {
+ scrollContainer.scrollTop = prevScrollTop;
+ scrollContainer.scrollLeft = prevScrollLeft;
+ } else {
+ window.scrollTo({ top: prevScrollTop, left: prevScrollLeft });
+ }
+ });
}, [isMobile]);
const handleKeyDown = (e: React.KeyboardEvent) => {
if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {
e.preventDefault();
if (text.trim()) {
- onSave(text, stableRangeRef.current);
+ onSave(text, normalizeRange(stableRangeRef.current));
}
} else if (e.key === 'Escape') {
e.preventDefault();
@@ -67,7 +105,7 @@ export function InlineCommentInput({
// Stop propagation to prevent parent selection clearing before save
e.stopPropagation();
if (text.trim()) {
- onSave(text, stableRangeRef.current);
+ onSave(text, normalizeRange(stableRangeRef.current));
}
};
@@ -90,7 +128,7 @@ export function InlineCommentInput({
{fileLabel && {fileLabel}}
{fileLabel && lineRange && •}
- {lineRange && Lines {lineRange.start}-{lineRange.end}}
+ {displayRange && Lines {displayRange.start}-{displayRange.end}}
)}
diff --git a/packages/ui/src/components/views/DiffView.tsx b/packages/ui/src/components/views/DiffView.tsx
index eda371ac..224f7f9a 100644
--- a/packages/ui/src/components/views/DiffView.tsx
+++ b/packages/ui/src/components/views/DiffView.tsx
@@ -1082,6 +1082,8 @@ export const DiffView: React.FC = () => {
outerClassName="flex-1 min-h-0 h-full"
className="pr-2"
disableHorizontal
+ data-diff-virtual-root
+ data-diff-virtual-content
>
{changedFiles.map((file, index) => (
@@ -1144,7 +1146,7 @@ export const DiffView: React.FC = () => {
}
return (
-
+
{renderSelectedDiffViewer()}
{isCurrentFileLoading && !hasCurrentDiff && (
diff --git a/packages/ui/src/components/views/PierreDiffViewer.tsx b/packages/ui/src/components/views/PierreDiffViewer.tsx
index 1d5ca241..58ba45ad 100644
--- a/packages/ui/src/components/views/PierreDiffViewer.tsx
+++ b/packages/ui/src/components/views/PierreDiffViewer.tsx
@@ -1,6 +1,16 @@
import React, { useMemo, useRef, useState, useCallback, useEffect } from 'react';
import { createPortal } from 'react-dom';
-import { FileDiff as PierreFileDiff, type FileContents, type FileDiffOptions, type SelectedLineRange, type DiffLineAnnotation, type AnnotationSide } from '@pierre/diffs';
+import {
+ FileDiff as PierreFileDiff,
+ VirtualizedFileDiff,
+ Virtualizer,
+ type FileContents,
+ type FileDiffOptions,
+ type SelectedLineRange,
+ type DiffLineAnnotation,
+ type AnnotationSide,
+ type VirtualFileMetrics,
+} from '@pierre/diffs';
import { InlineCommentCard, InlineCommentInput } from '@/components/comments';
import { useOptionalThemeSystem } from '@/contexts/useThemeSystem';
@@ -120,8 +130,10 @@ const extractSelectedCode = (original: string, modified: string, range: Selected
const lines = content.split('\n');
// Ensure bounds
- const startLine = Math.max(1, range.start);
- const endLine = Math.min(lines.length, range.end);
+ const from = Math.min(range.start, range.end);
+ const to = Math.max(range.start, range.end);
+ const startLine = Math.max(1, from);
+ const endLine = Math.min(lines.length, to);
if (startLine > endLine) return '';
@@ -138,6 +150,82 @@ type AnnotationData =
| { type: 'saved' | 'edit'; draft: InlineCommentDraft }
| { type: 'new'; selection: SelectedLineRange };
+type SharedVirtualizer = {
+ virtualizer: Virtualizer;
+ release: () => void;
+};
+
+type VirtualizerTarget = {
+ key: Document | HTMLElement;
+ root: Document | HTMLElement;
+ content: HTMLElement | undefined;
+};
+
+type VirtualizerEntry = {
+ virtualizer: Virtualizer;
+ refs: number;
+};
+
+const virtualizerCache = new WeakMap
();
+
+const VIRTUAL_METRICS: Partial = {
+ lineHeight: 24,
+ hunkSeparatorHeight: 24,
+ fileGap: 0,
+};
+
+function resolveVirtualizerTarget(container: HTMLElement): VirtualizerTarget {
+ const root = container.closest('[data-diff-virtual-root]');
+ if (root instanceof HTMLElement) {
+ const content = root.querySelector('[data-diff-virtual-content]');
+ return {
+ key: root,
+ root,
+ content: content instanceof HTMLElement ? content : undefined,
+ };
+ }
+
+ return {
+ key: document,
+ root: document,
+ content: undefined,
+ };
+}
+
+function acquireSharedVirtualizer(container: HTMLElement): SharedVirtualizer | null {
+ if (typeof document === 'undefined') return null;
+
+ const target = resolveVirtualizerTarget(container);
+ let entry = virtualizerCache.get(target.key);
+
+ if (!entry) {
+ const virtualizer = new Virtualizer();
+ virtualizer.setup(target.root, target.content);
+ entry = { virtualizer, refs: 0 };
+ virtualizerCache.set(target.key, entry);
+ }
+
+ entry.refs += 1;
+ let released = false;
+
+ return {
+ virtualizer: entry.virtualizer,
+ release: () => {
+ if (released) return;
+ released = true;
+
+ const current = virtualizerCache.get(target.key);
+ if (!current) return;
+
+ current.refs -= 1;
+ if (current.refs > 0) return;
+
+ current.virtualizer.cleanUp();
+ virtualizerCache.delete(target.key);
+ },
+ };
+}
+
export const PierreDiffViewer: React.FC = ({
original,
modified,
@@ -161,46 +249,54 @@ export const PierreDiffViewer: React.FC = ({
const removeDraft = useInlineCommentDraftStore((state) => state.removeDraft);
const allDrafts = useInlineCommentDraftStore((state) => state.drafts);
const currentSessionId = useSessionStore((state) => state.currentSessionId);
- const newSessionDraftOpen = useSessionStore((state) => state.newSessionDraft?.open);
const getSessionKey = useCallback(() => {
- return currentSessionId ?? (newSessionDraftOpen ? 'draft' : null);
- }, [currentSessionId, newSessionDraftOpen]);
+ return currentSessionId ?? 'draft';
+ }, [currentSessionId]);
const [selection, setSelection] = useState(null);
const [commentText, setCommentText] = useState('');
const [editingDraftId, setEditingDraftId] = useState(null);
+ const selectionRef = useRef(null);
+ const editingDraftIdRef = useRef(null);
// Use a ref to track if we're currently applying a selection programmatically
// to avoid loop with onLineSelected callback
const isApplyingSelectionRef = useRef(false);
const lastAppliedSelectionRef = useRef(null);
+ useEffect(() => {
+ selectionRef.current = selection;
+ }, [selection]);
+
+ useEffect(() => {
+ editingDraftIdRef.current = editingDraftId;
+ }, [editingDraftId]);
+
const handleSelectionChange = useCallback((range: SelectedLineRange | null) => {
// Ignore callbacks while we're programmatically applying selection
if (isApplyingSelectionRef.current) {
return;
}
-
+
+ const prevSelection = selectionRef.current;
+
// Mobile tap-to-extend: if selection exists and new tap is on same side, extend range
- if (isMobile && selection && range && range.side === selection.side) {
- const start = Math.min(selection.start, range.start);
- const end = Math.max(selection.end, range.end);
+ if (isMobile && prevSelection && range && range.side === prevSelection.side) {
+ const start = Math.min(prevSelection.start, range.start);
+ const end = Math.max(prevSelection.end, range.end);
setSelection({ ...range, start, end });
- return;
+ } else {
+ setSelection(range);
}
-
- setSelection(range);
-
+
// Clear editing state when selection changes user-driven
if (range) {
- // Don't clear if we're just updating the selection for the same draft?
- // For now, simple behavior: new selection = new comment flow
- if (!editingDraftId) {
+ if (!editingDraftIdRef.current) {
setCommentText('');
}
}
- }, [editingDraftId, isMobile, selection]);
+ }, [isMobile]);
const handleCancelComment = useCallback(() => {
setCommentText('');
@@ -258,6 +354,14 @@ export const PierreDiffViewer: React.FC = ({
const targetRange = rangeOverride ?? selection;
if (!targetRange || !textToSave.trim()) return;
+ const normalizedStart = Math.min(targetRange.start, targetRange.end);
+ const normalizedEnd = Math.max(targetRange.start, targetRange.end);
+ const normalizedRange: SelectedLineRange = {
+ ...targetRange,
+ start: normalizedStart,
+ end: normalizedEnd,
+ };
+
const sessionKey = getSessionKey();
if (!sessionKey) {
toast.error('Select a session to save comment');
@@ -267,16 +371,16 @@ export const PierreDiffViewer: React.FC = ({
// Pierre selection range: { start, end, side }
// Store needs { startLine, endLine, side: 'original'|'modified' }
// Pierre side: 'additions' (right) | 'deletions' (left)
- const storeSide = targetRange.side === 'deletions' ? 'original' : 'modified';
+ const storeSide = normalizedRange.side === 'deletions' ? 'original' : 'modified';
// Use deterministic code extraction instead of instance.getSelectedText()
- const selectedText = extractSelectedCode(original, modified, targetRange);
+ const selectedText = extractSelectedCode(original, modified, normalizedRange);
if (editingDraftId) {
updateDraft(sessionKey, editingDraftId, {
fileLabel: fileName || 'unknown',
- startLine: targetRange.start,
- endLine: targetRange.end,
+ startLine: normalizedRange.start,
+ endLine: normalizedRange.end,
side: storeSide,
code: selectedText,
language: language,
@@ -287,8 +391,8 @@ export const PierreDiffViewer: React.FC = ({
sessionKey,
source: 'diff',
fileLabel: fileName || 'unknown',
- startLine: targetRange.start,
- endLine: targetRange.end,
+ startLine: normalizedRange.start,
+ endLine: normalizedRange.end,
side: storeSide,
code: selectedText,
language: language,
@@ -325,6 +429,7 @@ export const PierreDiffViewer: React.FC = ({
const diffRootRef = useRef(null);
const diffContainerRef = useRef(null);
const diffInstanceRef = useRef | null>(null);
+ const sharedVirtualizerRef = useRef(null);
const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
const workerPool = useWorkerPool();
@@ -417,6 +522,9 @@ export const PierreDiffViewer: React.FC = ({
hunkSeparators: 'line-info' as const,
// Perf: disable intra-line diff (word-level) globally.
lineDiffType: 'none' as const,
+ maxLineDiffLength: 1000,
+ maxLineLengthForHighlighting: 1000,
+ expansionLineCount: 20,
overflow: wrapLines ? ('wrap' as const) : ('scroll' as const),
disableFileHeader: true,
enableLineSelection: true,
@@ -469,7 +577,12 @@ export const PierreDiffViewer: React.FC = ({
return anns;
}, [allDrafts, getSessionKey, fileName, editingDraftId, selection]);
- // Imperative render (like upstream OpenCode): avoids `parseDiffFromFile` on main thread.
+ const lineAnnotationsRef = useRef(lineAnnotations);
+
+ useEffect(() => {
+ lineAnnotationsRef.current = lineAnnotations;
+ }, [lineAnnotations]);
+
useEffect(() => {
if (typeof window === 'undefined') return;
@@ -480,9 +593,21 @@ export const PierreDiffViewer: React.FC = ({
// Dispose previous instance
diffInstanceRef.current?.cleanUp();
diffInstanceRef.current = null;
+ sharedVirtualizerRef.current?.release();
+ sharedVirtualizerRef.current = null;
container.innerHTML = '';
- const instance = new PierreFileDiff(options as unknown as FileDiffOptions, workerPool);
+ const sharedVirtualizer = acquireSharedVirtualizer(container);
+ sharedVirtualizerRef.current = sharedVirtualizer;
+
+ const instance = sharedVirtualizer
+ ? new VirtualizedFileDiff(
+ options as unknown as FileDiffOptions,
+ sharedVirtualizer.virtualizer,
+ VIRTUAL_METRICS,
+ workerPool,
+ )
+ : new PierreFileDiff(options as unknown as FileDiffOptions, workerPool);
diffInstanceRef.current = instance;
lastAppliedSelectionRef.current = null;
@@ -502,7 +627,7 @@ export const PierreDiffViewer: React.FC = ({
instance.render({
oldFile,
newFile,
- lineAnnotations,
+ lineAnnotations: lineAnnotationsRef.current,
containerWrapper: container,
});
@@ -514,9 +639,29 @@ export const PierreDiffViewer: React.FC = ({
if (diffInstanceRef.current === instance) {
diffInstanceRef.current = null;
}
+ sharedVirtualizer?.release();
+ if (sharedVirtualizer && sharedVirtualizerRef.current === sharedVirtualizer) {
+ sharedVirtualizerRef.current = null;
+ }
container.innerHTML = '';
};
- }, [diffThemeKey, fileName, language, modified, options, original, workerPool, lineAnnotations]);
+ }, [diffThemeKey, fileName, language, modified, options, original, workerPool]);
+
+ useEffect(() => {
+ const instance = diffInstanceRef.current;
+ if (!instance) return;
+
+ instance.setLineAnnotations(lineAnnotations);
+ requestAnimationFrame(() => {
+ if (diffInstanceRef.current !== instance) return;
+ try {
+ instance.rerender();
+ } catch (err) {
+ void err;
+ }
+ forceUpdate();
+ });
+ }, [lineAnnotations]);
useEffect(() => {
const instance = diffInstanceRef.current;
@@ -545,6 +690,64 @@ export const PierreDiffViewer: React.FC = ({
}
}, [selection]);
+ useEffect(() => {
+ const container = diffContainerRef.current;
+ if (!container) return;
+
+ let rafId: number | null = null;
+ let cleanup = () => {};
+
+ const setup = () => {
+ const host = container.querySelector('diffs-container');
+ const shadowRoot = host?.shadowRoot;
+ if (!shadowRoot) {
+ rafId = requestAnimationFrame(setup);
+ return;
+ }
+
+ const onClickCapture = (event: Event) => {
+ if (!(event instanceof MouseEvent) || event.button !== 0) return;
+ if (!(event.target instanceof Element)) return;
+
+ const numberCell = event.target.closest('[data-column-number]');
+ if (!(numberCell instanceof HTMLElement)) return;
+
+ const lineRaw = numberCell.getAttribute('data-column-number');
+ const lineNumber = lineRaw ? parseInt(lineRaw, 10) : NaN;
+ if (Number.isNaN(lineNumber)) return;
+
+ const lineType =
+ numberCell.closest('[data-line-type]')?.getAttribute('data-line-type')
+ ?? numberCell.getAttribute('data-line-type');
+
+ const side: AnnotationSide = lineType === 'change-deletion' ? 'deletions' : 'additions';
+
+ handleSelectionChange({
+ start: lineNumber,
+ end: lineNumber,
+ side,
+ });
+
+ event.preventDefault();
+ event.stopPropagation();
+ };
+
+ shadowRoot.addEventListener('click', onClickCapture, true);
+ cleanup = () => {
+ shadowRoot.removeEventListener('click', onClickCapture, true);
+ };
+ };
+
+ setup();
+
+ return () => {
+ if (rafId !== null) {
+ cancelAnimationFrame(rafId);
+ }
+ cleanup();
+ };
+ }, [diffThemeKey, fileName, handleSelectionChange]);
+
// MutationObserver to trigger re-renders when annotation DOM nodes are added/removed
useEffect(() => {
const container = diffContainerRef.current;
@@ -669,12 +872,13 @@ export const PierreDiffViewer: React.FC = ({
if (layout === 'fill') {
return (
-
+
@@ -696,5 +900,3 @@ export const PierreDiffViewer: React.FC
= ({
);
};
-
-
diff --git a/packages/vscode/package.json b/packages/vscode/package.json
index 1a3949db..3ff8a543 100644
--- a/packages/vscode/package.json
+++ b/packages/vscode/package.json
@@ -229,7 +229,7 @@
},
"dependencies": {
"@openchamber/ui": "workspace:*",
- "@opencode-ai/sdk": "^1.1.53",
+ "@opencode-ai/sdk": "^1.1.65",
"adm-zip": "^0.5.16",
"jsonc-parser": "^3.3.1",
"react": "^19.1.1",
diff --git a/packages/web/package.json b/packages/web/package.json
index 98bd5012..a97b74b2 100644
--- a/packages/web/package.json
+++ b/packages/web/package.json
@@ -26,7 +26,7 @@
"@fontsource/ibm-plex-sans": "^5.1.1",
"@ibm/plex": "^6.4.1",
"@octokit/rest": "^22.0.1",
- "@opencode-ai/sdk": "^1.1.53",
+ "@opencode-ai/sdk": "^1.1.65",
"@radix-ui/react-collapsible": "^1.1.12",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16",