fix(crx): enforce ToolDefinition.parameters at runtime via schema-driven validation - #5
Closed
Postroggy wants to merge 102 commits into
Closed
fix(crx): enforce ToolDefinition.parameters at runtime via schema-driven validation#5Postroggy wants to merge 102 commits into
Postroggy wants to merge 102 commits into
Conversation
…-ai#104) Fixes superduck-ai#101 Co-authored-by: Codex <noreply@openai.com>
* fix(crx): fix language submenu clipped when sidebar is narrow Replace the fly-out submenu (absolute right-full) with an inline accordion that expands below the Language row. The previous positioning overflowed the left edge of the side-panel viewport whenever the sidebar width was too small, making the list invisible. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * a11y(crx): add aria-expanded and aria-controls to language menu toggle Addresses Copilot review suggestion on PR superduck-ai#102: the language disclosure button now exposes its expanded/collapsed state to assistive technology via aria-expanded, and references the submenu container via aria-controls. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: yueqi.guo <guo_yueqi@qq.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(crx): fix modal backdrop opaque black and close-on-drag-out bugs Two bugs in the shared Modal component: 1. Backdrop was fully opaque black because bg-always-black (solid) and the [background-color:hsl(...)/0.5] arbitrary class were both applied and the solid class was winning. Remove bg-always-black so only the semi-transparent arbitrary value applies. 2. Modal closed when the user started a drag inside (e.g. selecting text in an input) and released the mouse on the backdrop. Replaced the onClick-only guard with an onMouseDown tracker: onClose() now fires only when both mousedown and mouseup land on the overlay. Also remove a stale eslint-disable comment referencing the uninstalled react-hooks plugin, which was causing lint-staged to fail on this file. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(crx): address Copilot review — use onPointerDownCapture for overlay close guard Two issues raised in code review: 1. onMouseDown fires in the bubble phase, so child elements that call stopPropagation() (e.g. SimpleSelect option buttons) prevent the overlay from recording the pointer-down target, making the stale ref cause incorrect close behaviour. Switch to onPointerDownCapture so the overlay always records the originating target regardless of stopPropagation in descendants. 2. onMouseDown does not fire for touch or pen input. onPointerDownCapture covers mouse, touch, and stylus uniformly. Also reset overlayMouseDownTarget to null after each onClick evaluation to prevent stale values from affecting subsequent interactions. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: yueqi.guo <guo_yueqi@qq.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…tHog flow (superduck-ai#106) * fix(analytics): unify distinct_id across extension and cli Co-authored-by: Claude <noreply@anthropic.com> * fix(crx): 存量用户迁移到 native-host 统一 analytics ID 已有用户的 chrome.storage.local 中存有旧的 randomUUID 格式 anonymousId, 更新扩展后不会自动替换为 native-host 的 anon-* ID。 增加迁移逻辑:如果现有 ID 不是 anon-* 格式,尝试从 native host 获取统一 ID 并覆盖旧值,确保存量用户也能与 CLI 共享同一个 distinct_id。 * fix(analytics): 修复 PR superduck-ai#106 评审问题——import 缺失、native probe 无缓存、禁用时仍写磁盘 1. mcpRuntime/analytics.ts: 恢复 FeatureFlagManager 所需的 import(getConfig、 getStorageValue、removeStorageValues、StorageKeys),修复编译错误。 2. extensionServices/analytics.ts: 为 getNativeHostAnalyticsId 添加模块级缓存, 避免 native host 不可用时每次事件发射都触发 3s 超时。 3. posthog.go GetOrCreateDistinctID: 尊重 SUPERDUCK_ANALYTICS_DISABLED / CI 环境变量,禁用时返回空字符串而不创建持久化文件。 * fix(analytics): 修复 PR superduck-ai#106 第二轮评审问题 1. extensionServices/analytics.ts: fallback UUID 加 `anon-` 前缀,避免每次 重启都误判为旧 ID 触发迁移逻辑;只缓存成功的 native host 探测结果, 失败不缓存,允许后装 native host 后重新探测。 2. mcpRuntime/analytics.ts: initializeAnalytics 改为 fire-and-forget,不阻塞 UI 渲染;FeatureFlagManager.fetchAndStore 在 token 缺失时清除 initPromise, 允许登录后重新拉取 feature flags。 3. native-host/main.go: 日志文件权限从 0644 改为 0600,防止共享机器上其他 用户读取 debug 日志中的 distinct_id。 4. AGENTS.md: 修复分号后缺少空格的格式问题。 * fix(analytics): 修复 PR superduck-ai#106 第三轮评审问题 1. extensionServices/analytics.ts: 迁移逻辑改为基于 nativeIdCache 判断, 不再依赖 `anon-` 前缀,确保后装 native host 时能正确迁移到共享 ID。 2. mcpRuntime/analytics.ts: 401 响应时同步清除 initPromise 和 features, 避免重新登录后 feature flags 卡在默认值。 3. 将 .github/AGENT_SKILLS/issue-fill.md 和 .factory/skills/issue-fill/SKILL.md 纳入版本控制,修复 AGENTS.md 中的断链。 * fix(analytics): 并发去重、unhandled rejection、feature flags 重试 1. extensionServices/analytics.ts: getOrCreateAnonymousId 加 in-flight 去重,并发调用共享同一个 Promise,避免生成多个不同 distinct_id。 2. mcpRuntime/analytics.ts: initializeAnalytics 的 fire-and-forget 加 .catch(() => {}) 吞掉错误,防止 MV3 SW unhandled rejection。 3. mcpRuntime/analytics.ts: fetchAndStore 在非 200 响应时也清除 initPromise,允许后续重试而非永久返回默认值。 --------- Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Claude <noreply@anthropic.com>
…#109) * fix(native-host): preserve active cli socket Co-authored-by: Codex <noreply@openai.com> * feat(analytics): 统一三端 analytics ID 为 sdid-* 格式,同步扩展与 native-host 身份 - 新增 EnsureInstallID / ConfirmInstallID / AdoptInstallID 机制,确保 CLI、native-host、扩展共享同一个 distinct_id - CLI 设置 RequireConfirmedID,在扩展与 native-host 完成 ID 同步前不上报,防止 split identity - 扩展连接 native-host 时通过 sync_analytics_id / get_analytics_id 协议同步 ID - 自动迁移旧的 anon-* / sdext-* 格式 ID 为 sdid-* - PostHog 事件按来源区分 $lib(superduck-cli / superduck-mcp / superduck-sidepanel / superduck-bridge / superduck-extension) - npm wrapper 增加 ensureAnalyticsId 预创建 analytics-id 文件 - 更新 allowed_origins 为新的 extension ID - 版本升至 0.2.6 * feat(cli): 新增 superduck update 命令和后台版本检查 - 新增 internal/selfupdate 包:semver 解析、npm registry 查询、缓存管理、自更新逻辑 - superduck update: 自动检测安装方式(npm/直接下载),执行对应更新流程 - superduck update --check: 仅检查是否有新版本 - 后台版本检查:每 24h 查询 npm registry,命令执行后非阻塞提示有新版本可用 - 支持 GitHub Release tarball 下载 + 原子替换二进制 * fix: 修复 PR review 指出的问题 - native-host: ID 未确认时降级为警告而非拒绝请求,兼容旧版扩展 - analytics.ts: 先注册 storage listener 再读取,消除竞态窗口 - cmd_update: 使用实际安装版本更新缓存和 telemetry - selfupdate: tar 解压增加 TypeReg 检查,跳过目录/symlink - selfupdate_test: TestDetectInstallMethodNPM 实际测试路径检测逻辑 * fix: 修复 PR review 第二轮问题 - native-host: ID sync 等待改为 sync.Once,只在首次请求等待一次,后续不阻塞 - cmd_update: UpdateViaNPM 返回空时 fallback 到已知 latest 版本 - selfupdate: UpdateViaNPM 失败时不再返回字面 "latest" 字符串 - mcp-server: 日志文件权限收紧为 0600 --------- Co-authored-by: Codex <noreply@openai.com>
- README 新增 SuperDuck vs Claude for Chrome 对比表、CLI 命令示例、架构图 - 中文 README 同步更新,修正旧仓库名 - 官网能力列表新增 CLI + MCP server - 终端演示修正不存在的 fetch 命令 - 架构图从 ASCII art 改为 SVG 绘制 - 版本号更新为 v0.2.5 Co-authored-by: Claude <noreply@anthropic.com>
* feat(crx): agent indicator i18n 实时切换及英文默认值兜底 监听 chrome.storage.onChanged,用户切换语言后 indicator 文案实时 刷新,无需重载页面;同时新增 DEFAULT_I18N_MESSAGES 英文兜底, 避免 JSON 加载失败时显示空白。 * fix(crx): 防止快速切换语言时旧请求覆盖新翻译 加入 i18nLoadVersion 版本守卫,每次 await 后校验版本号, 确保只有最新一次 loadI18n 调用能写入状态。
Remove system.display and declarativeNetRequestWithHostAccess from the MV3 manifest after Chrome Web Store review flagged both as unused. Co-authored-by: Codex <noreply@openai.com>
fix(crx): remove unused store permissions
* fix(crx): 支持模型配置 API URL 输入纯域名并校验非法输入 - 裸域名自动补全 https://,完整 URL 与 endpoint 后缀裁剪保持兼容 - 新增 isValidProviderBaseURL,非法输入返回空字符串且阻止保存 - 添加/编辑模型弹窗展示错误提示,非法 URL 时不请求模型列表 Fixes superduck-ai#112 Co-authored-by: Cursor <noreply@cursor.com> * fix(crx): address PR review on provider base URL validation - Keep invalid API URL on blur instead of clearing to empty string - Reject URLs with embedded credentials in parseProviderBaseURLInput - Guard handleSubmit when base URL is invalid Co-authored-by: Cursor <noreply@cursor.com> * fix(crx): allow single-label hostnames with explicit http(s) scheme Docker/local gateway URLs like http://ollama:11434/v1 are valid when the user provides a scheme; bare single-label names still require a dotted domain. Co-authored-by: Cursor <noreply@cursor.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Cursor <noreply@cursor.com>
Fixes superduck-ai#120 Co-authored-by: Codex <codex@openai.com>
* fix(crx): align permission mode menu typography with composer UI Shrink the side panel permission mode dropdown to match the 11px trigger button and other compact menus (narrower width, smaller icons/text, tighter padding). Remove leftover debug logging on the menu toggle. Fixes superduck-ai#165 Co-authored-by: Cursor <noreply@cursor.com> * fix(crx): keep permission mode menu descriptions on one line Remove max-width cap and apply whitespace-nowrap so Chinese descriptions do not wrap mid-sentence in the dropdown. Fixes superduck-ai#165 Co-authored-by: Cursor <noreply@cursor.com> * refactor(crx): extract PermissionModeMenu with docstrings Move the composer permission mode dropdown into a documented component so CodeRabbit docstring coverage passes. Cap menu width at 280px and truncate overflow while keeping single-line labels. Fixes superduck-ai#165 Co-authored-by: Cursor <noreply@cursor.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Cursor <noreply@cursor.com>
* fix(crx): support OpenAI Responses GPT gateways Co-authored-by: Codex <noreply@openai.com> * docs: relax PR issue linkage rules Co-authored-by: Codex <noreply@openai.com> * docs: default PRs to ready for review Co-authored-by: Codex <noreply@openai.com> * fix(crx): tighten OpenAI Responses id handling Co-authored-by: Codex <noreply@openai.com> * test(crx): align OpenAI mock constant naming Co-authored-by: Codex <noreply@openai.com> --------- Co-authored-by: Codex <noreply@openai.com>
…ai#167) DatePicker serialized selected dates with toISOString(), which can shift YYYY-MM-DD by one day for non-UTC timezones. Use local calendar components for serialize/parse and align today comparisons. Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Cursor <noreply@cursor.com>
…ck-ai#172) * feat(crx): keep agent cursor visible in tool-use screenshots When HIDE_FOR_TOOL_USE runs for screenshot/computer tools, hide glow, ripple, stop bar, and blocking overlay but leave the proxy cursor in the DOM so Page.captureScreenshot can show where the agent is pointing. Closes superduck-ai#168 Co-authored-by: Cursor <noreply@cursor.com> * fix(crx): address review feedback on tool-use indicator hide Use display:none for blocking overlay instead of detaching it, null the overlay reference on full hide even when already detached, and re-hide interruptive UI after async showAgentIndicators completes if a screenshot started during i18n load. Co-authored-by: Cursor <noreply@cursor.com> * fix(crx): hide interruptive UI before i18n when tool-use hidden When showAgentIndicators runs while isHiddenForToolUse is already true, create glow/ripple/blocking/stop with display:none before awaiting i18n so screenshots cannot capture decorations in the pre-await window. docs: add PR review thread closure guidance to AGENTS.md Co-authored-by: Cursor <noreply@cursor.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Cursor <noreply@cursor.com>
- Generate per-session auth token (crypto/rand, 256-bit) written to ~/.superduck/uds-token (mode 0600) at native-host startup - Require UDS clients to authenticate before processing tool requests - Both CLI client and MCP bridge authenticate automatically - Restrict UDS socket permissions to 0700 (owner-only) - Cap concurrent UDS connections at 16 to prevent resource exhaustion - Remove silent duration ms→s conversion in normalizeArgs, validate only - Tighten audit.log file permissions from 0644 to 0600 Fixes: UDS unauthenticated access, audit log world-readable, connection exhaustion, duration conversion masking invalid input Co-authored-by: Codex <noreply@openai.com>
- cmd_wait: Add duration range validation (0-30 seconds) - cmd_resize: Add width/height range validation (1-7680, 1-4320 pixels) - cmd_scroll: Add direction enum validation and amount range check (1-100) - cmd_key: Add repeat range validation (1-100) - flags: Support --tab=N syntax in addition to --tab N Co-authored-by: Codex <noreply@openai.com>
- Extract handleImageCapture helper to cmd_computer.go - Replaces ~40 lines of duplicate base64 decode, file write, JSON output logic - cmd_screenshot and cmd_zoom now both call handleImageCapture - Future bug fixes to image handling only need to be made in one place Co-authored-by: Codex <noreply@openai.com>
- Download and verify .sha256 checksum file before extracting - Read entire tarball into memory to verify before extraction - Reject updates with mismatched checksums - Protects against MITM attacks and corrupted downloads Co-authored-by: Codex <noreply@openai.com>
- Add nil check in ToMCPContent to return empty TextContent
- Improve buildCallToolResult to handle []interface{} results
- Ensures MCP clients always receive valid Content even for edge cases
Co-authored-by: Codex <noreply@openai.com>
Previously, 'superduck type --help' would treat '--help' as text to type into the focused element. Now it uses flag.NewFlagSet to properly parse flags and display help when requested. Fixes: 维度二#5 - cmd_type 不支持 --help Co-authored-by: Codex <noreply@openai.com>
Previously, read_page accepted any string value for --filter without validation. Now it explicitly checks that the value is either 'interactive' or 'all', returning a clear error message for invalid values. Fixes: 维度五superduck-ai#6 - read_page --filter 不校验枚举值 Co-authored-by: Codex <noreply@openai.com>
Previously, cmdLog with --tail N would read the entire file into memory using a ring buffer, which is inefficient for large audit logs. Now: - Use reverse reading strategy: seek to end and read backwards - Read in 8KB chunks from file end - Only read enough data to collect N lines - Dramatically reduces memory usage and I/O for large files Fixes: 维度七#1 - cmdLog tail 性能优化 Co-authored-by: Codex <noreply@openai.com>
Previously, ReadMessage allocated a new byte slice for every message, causing high GC pressure in long-running processes like native-host. Now: - Use sync.Pool to reuse byte buffers across message reads - Start with 64KB default buffer size for most messages - Automatically grow buffer when larger messages are encountered - Return buffers to pool after use - Significantly reduces memory allocations in high-throughput scenarios Fixes: 维度七#2 - ReadMessage buffer 复用 Co-authored-by: Codex <noreply@openai.com>
Previously, BackgroundCheck spawned a goroutine that could not be cancelled, potentially blocking process exit if the HTTP request hung. Now: - Return CancelFunc alongside the channel for explicit cancellation - Use context.WithTimeout for HTTP requests (5s timeout) - Support context cancellation throughout the check flow - Caller can cancel with defer cancelUpdate() to prevent goroutine leaks Fixes: 维度七#5 - BackgroundCheck goroutine 无法取消 Co-authored-by: Codex <noreply@openai.com>
- InvalidToken: wrong token rejected - MalformedJSON: malformed JSON rejected - MissingType: missing type field rejected - WrongType: wrong type value rejected - ValidToken: correct token accepted - ClientDisconnects: client disconnect handled - EmptyMessage: empty message rejected - Integration: full end-to-end auth flow Co-authored-by: Codex <noreply@openai.com>
- Remove 0-30s duration limit from cmd_wait.go (CLI should not enforce MCP schema constraints) - Remove --tab= syntax support from flags.go (UX improvement, not a bug fix)
Only validate that dimensions are positive numbers. Let Chrome handle its own window size constraints rather than imposing CLI-level limits that may not match Chrome's actual capabilities. This follows the same principle as the wait command fix: CLI should not enforce MCP schema constraints on end users who may have legitimate use cases for larger values (multi-monitor setups, virtual displays, etc.).
* fix: improve Edge and Brave browser compatibility - cmd_doctor: check Chrome, Edge, and Brave manifest paths (not just Chrome) - install.sh: install native messaging manifest for all supported browsers - Extension: add edge:// and brave:// to URL validation blocklists - Update error messages to mention all supported browsers This ensures superduck works correctly when installed in Edge or Brave browsers, not just Chrome. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address PR superduck-ai#204 review feedback - Restore accidentally deleted chrome-native-host/superduck symlink - Make error message browser-agnostic ('in your browser' instead of 'in Chrome') - Fix cmd_doctor.go: skip manifest check on unsupported OS instead of false failure - Add brave://extensions/ to install.sh instructions - Replace 'Chrome Native Host' with browser-agnostic wording in install.sh Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: yueqi.guo <guo_yueqi@qq.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…ent hook (superduck-ai#205) * refactor(crx): extract provider client management into useProviderClient hook Move provider client lifecycle (MessagesClient creation, tier-specific fallback, server model info fetching) from SidepanelApp.tsx into a dedicated useProviderClient hook in src/sidepanel/provider/. No logic changes — pure extraction. - Created src/sidepanel/provider/useProviderClient.ts - Created src/sidepanel/provider/index.ts - Replaced ~75 lines of state/effects in SidepanelApp.tsx with single hook call Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(crx): derive hasProviderConfig and fix React type import Address review feedback from PR superduck-ai#205: - Derive hasProviderConfig from effectiveMessagesClient !== null instead of using separate state, fixing a critical bug where direct config (apiKey+apiBaseUrl) left hasProviderConfig as false and could trigger SetupGate incorrectly. - Import MutableRefObject type directly from 'react' instead of using the React namespace, which is not imported in this module. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: yueqi.guo <guo_yueqi@qq.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Extract all type definitions and interfaces from SidepanelApp.tsx into a dedicated types.ts file. This reduces SidepanelApp.tsx by ~190 lines and improves type discoverability. Extracted types: - ChatRole, VisibleChatRole, ChatMessage - PermissionPromptData, PermissionGrantScope - RuntimeMessage, PairingPromptState, PendingPromptPayload - BlockedTabInfo, SessionSnapshot, SessionIndexEntry - ToolUseBlock, ToolInputRecord, Base64ImageSource, Base64ImageBlock - ToolResultDisplayContent, LightningContentArray, etc. - MessageGroup, TimelineGroupData, GroupedContentBlock - StreamingTextStore, AnnouncementConfig - PERMISSION_ACTION_TYPES constant Co-authored-by: yueqi.guo <guo_yueqi@qq.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
superduck-ai#208) * refactor(sidepanel): extract UserMessageRow and StreamingTextBlock components Extract pure display components from SidepanelApp.tsx into a new MessageComponents directory: - UserMessageRow.tsx: User message bubble with image preview, copy, expand/collapse, and shortcut chip rendering (~170 lines) - StreamingTextBlock.tsx: Streaming text renderer using useSyncExternalStore (~50 lines) - index.ts: Barrel exports Also moved utility functions needed by these components to sidepanelUtils.ts: - getTextFromBlockContent - getBase64ImageBlocks - isBase64ImageSource - isBase64ImageBlock SidepanelApp.tsx reduced by ~240 lines. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(sidepanel): extract tool display components from SidepanelApp Extract ~1500 lines of tool display and message rendering components into MessageComponents/ContentBlocksRenderer.tsx: - getStringField helper - PermissionActionButton (shared with InlinePermissionPrompt) - PlanApprovalModal (plan approval/rejection modal) - UpdatePlanCell (plan display with portal) - BrowserToolCell (browser tool display with screenshot thumbnails) - ToolUseItem (generic tool display with Request/Result badges) - isTimelineBlock type guard - ContentBlocksRenderer (splits blocks at turn_answer_start) - BlockRenderer (dispatches to right renderer per block type) - AssistantMessageRow (assistant response with copy + feedback) - MessageList (groups and renders all messages) Also clean up ~50 unused imports from SidepanelApp.tsx. SidepanelApp.tsx reduced from 8502 to 6567 lines across PR 1-3. --------- Co-authored-by: yueqi.guo <guo_yueqi@qq.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
- Break barrel cycle: import StreamingTextBlock and UserMessageRow directly from their files instead of ./index to avoid circular dependency in MessageComponents - Fix read-only PlanApprovalModal backdrop click: add stopPropagation on modal content wrapper so clicking backdrop triggers handleBackdropClick while clicking modal content does not close the modal - Security: add noopener,noreferrer to window.open() call to prevent reverse tabnabbing when opening external URLs
…ponents (superduck-ai#211) * refactor(sidepanel): extract SidepanelHeader and SidepanelBanners components Extract two major JSX sub-components from SidepanelApp.tsx into dedicated files: - SidepanelHeader (265 lines): Model selector dropdown, quick mode toggle, clear chat button, and header menu with settings/language/convert options - SidepanelBanners (265 lines): Error, refusal, message limit, high risk, notification, and announcement banners with AnimatePresence, plus the ModelFallbackCard SidepanelApp.tsx reduced from 6557 to 6239 lines (318 lines extracted). All tests pass (78/78), build succeeds, typecheck clean for new code. * fix(sidepanel): tighten type safety in extracted JSX components - SidepanelBanners: Use proper types for announcementConfig (AnnouncementConfig), fallbackConfig (ModelFallbackConfig | undefined), and modelConfig (ModelsConfigFeatureValue) instead of any - SidepanelBanners: Narrow activeBanner to string literal union instead of generic string - SidepanelHeader: Use SupportedLocale for SUPPORTED_LOCALES, LOCALE_DISPLAY_NAMES, and locale instead of string - SidepanelHeader: Use minimal IntlShape interface for intl instead of any Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: yueqi.guo <guo_yueqi@qq.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…Messages hook (superduck-ai#210) * refactor(sidepanel): extract permission prompt components Move InlinePermissionPrompt and isPermissionPromptData to a dedicated PermissionPrompt.tsx file to improve code organization and reduce the size of SidepanelApp.tsx. Extracted: - InlinePermissionPrompt component (~300 lines) - isPermissionPromptData type guard SidepanelApp.tsx reduced from ~6567 to ~6239 lines. * refactor(sidepanel): extract utility functions and type guards Move utility functions, type guards, and constants to a dedicated sidepanelGuards.ts file to improve code organization and reduce the size of SidepanelApp.tsx. Extracted: - Type guards: isChatRole, isChatMessage, isApiConversationMessage, isSessionSnapshot, isStringRecord - Utility functions: getLightningScreenshotReminder, normalizeToolResultContent, getStreamHeaders, getRuntimeEvaluateValue, normalizeImageMediaType - Hooks: createStreamingTextStore, usePrefersReducedMotion - Constants: SESSION_CONVERSATION_MAP_KEY, SESSION_REMOTE_MAP_KEY, SESSION_INDEX_KEY, CUSTOM_API_URL_KEY, CUSTOM_API_KEY_KEY SidepanelApp.tsx reduced from ~6239 to ~6107 lines. * refactor(sidepanel): extract useLightningMode hook Move the useLightningMode hook (the largest single extraction at ~1305 lines) to a dedicated file to improve code organization. Extracted: - UseLightningModeProps interface - useLightningMode hook: config management, system prompt building, API client initialization, sendMessage main loop (streaming, command parsing, execution, screenshots, page settle), cancel, clearMessages, clearError, createApiMessage, trackToolCall SidepanelApp.tsx reduced from ~6107 to ~4753 lines. * refactor(sidepanel): extract auth and model config hooks Move authentication and model configuration logic to dedicated hooks to improve code organization and testability. Extracted: - useAuth hook: apiKey, apiBaseUrl, authLoading, authError state management, refreshAuth callback, storage change listeners - useModelConfig hook: selectedModel, modelMapping state, storage loading/listening, handleModelChange callback SidepanelApp.tsx reduced from ~4753 to ~4627 lines. * refactor(sidepanel): extract session persistence hook Extract session persistence logic into useSessionPersistence hook: - upsertSessionIndex helper function - loadSnapshotForSession callback - restoreSnapshotFromRemoteSession callback - Session-loading effect (activeSessionId change) - Session persistence effect (debounced 2000ms writes) SidepanelApp.tsx reduced from 4627 to 4339 lines. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(sidepanel): extract agent loop into useAgentLoop hook Extract the main agent loop logic into useAgentLoop hook: - sendPrompt function (message sending + tool execution loop) - compactConversation callback - sendCompletionNotification callback - generateStatusSummary callback - generateConversationTitle callback SidepanelApp.tsx reduced from 4339 to 3558 lines (~780 lines extracted). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(sidepanel): extract runtime message listener into useRuntimeMessages hook Extract the Chrome runtime message listener logic from SidepanelApp.tsx into a dedicated useRuntimeMessages hook. This includes: - PANEL_OPENED/PANEL_CLOSED messaging effects - Visibility change listener - shouldHandleTaskForCurrentContext callback - Main runtime.onMessage listener handling: - PING_SIDEPANEL - show_pairing_prompt - MAIN_TAB_ACK_REQUEST - POPULATE_INPUT_TEXT (with attachment decoding and auto-send) - LOAD_CONVERSATION (with session map lookups) - EXECUTE_TASK (with context filtering) - STOP_AGENT (with abort controller) SidepanelApp.tsx reduced from 3558 to 3289 lines (~270 lines extracted). All 78 tests pass, build succeeds. * fix(sidepanel): address bot review issues in useRuntimeMessages Fix two issues identified by CodeRabbit and ChatGPT Codex: 1. Clear timeout on effect unmount: Store the setTimeout ID and clear it in the cleanup function to prevent state updates after unmount. 2. Fix LOAD_CONVERSATION race condition: Move sendResponse inside the async IIFE and return true to indicate async response, ensuring the caller knows when the conversation is fully loaded. All 78 tests pass, build succeeds. * fix(sidepanel): address bot review feedback for PR superduck-ai#210 - Remove debug console.log in useSessionPersistence, use selectedModelRef instead of selectedModel in session load effect to avoid stale closure - Add intl.locale to compactConversation dependency array - Replace hardcoded Chinese text with i18n in useAgentLoop - Add queryTabId to sendPrompt dependency array to prevent stale tab IDs - Add user feedback for /share command instead of silent return - Replace non-null assertion with explicit null guard in useLightningMode - Align platform detection in PermissionPrompt with useLightningMode (check both navigator.platform and navigator.userAgent for Mac detection) - Add i18n keys for new messages in en-US and zh-CN locale files Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(sidepanel): restore full event message parsing in useSessionPersistence Import pickEventMessage from sessionHistory.ts instead of using a simplified local version. The original implementation handles nested event structures (event.data.message, event.payload.message, event.item.message) which are needed for restoring remote sessions with various API response formats. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: yueqi.guo <guo_yueqi@qq.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…perduck-ai#212) - Fix read-only PlanApprovalModal backdrop click: add onClick handler to the backdrop div itself (e.target === e.currentTarget on wrapper never fired because backdrop is absolute-positioned child) - Add noopener,noreferrer to window.open() calls in MarkdownComponents to prevent reverse tabnabbing attacks Co-authored-by: yueqi.guo <guo_yueqi@qq.com>
superduck-ai#213) - Extract 551-line ChatInputArea component into components/ChatInputArea.tsx - Reduces SidepanelApp.tsx from 2969 to 2471 lines (17% reduction) - Manages internal state: permission menu, actions menu, command menu - Props interface groups related dependencies logically - All 78 tests pass, no new TypeScript errors introduced Co-authored-by: yueqi.guo <guo_yueqi@qq.com>
* feat: add Edge extension build support - Add build script with target parameter (chrome/edge) - Transform manifest.json for Edge: - Remove Chrome-specific 'key' field - Remove Chrome 'update_url' - Change 'minimum_chrome_version' to 'minimum_edge_version' - Update description to mention Edge - Add build:chrome and build:edge npm scripts - Update install.sh to support configurable extension IDs - Document environment variables for custom IDs Now both Chrome Web Store and Edge Add-ons can be built from the same codebase. * fix: move manifest transformation to vite.config.ts Move manifest transformation logic into vite.config.ts so that @crxjs/vite-plugin sees the correct manifest from the start of the build process, not after. This prevents the plugin from using Chrome-specific fields (like 'key') during Edge builds. Changes: - vite.config.ts: Add transformManifest() that runs before crx() plugin - scripts/build.mjs: Simplified to just set BUILD_TARGET env var - No longer modifies dist/manifest.json post-build * fix(install): validate extension ID format before writing native host manifest Addresses Factory Droid P1 security concern: validate EXTENSION_ID against Chromium extension ID format (32 lowercase letters a-p) before interpolating into JSON manifest to prevent injection attacks. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(edge): correct manifest keys and use bun for build scripts - Remove incorrect minimum_chrome_version → minimum_edge_version rename (Edge supports minimum_chrome_version as a Chromium-based browser) - Use bun instead of node for build script invocation (CodeRabbit suggestion) - Update shebang to #!/usr/bin/env bun Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix: handle signal termination and validate BUILD_TARGET at runtime - build.mjs: propagate non-zero exit when Vite is killed by signal (SIGINT etc.) - vite.config.ts: validate BUILD_TARGET at runtime, throw on invalid values instead of silently falling back to chrome behavior Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix: update usage comment to reflect bun instead of node Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: yueqi.guo <guo_yueqi@qq.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…tool calls (superduck-ai#215) clearAllPendingToolCalls resolved all pending permission requests with false when the bridge disconnected, making 'user denied' indistinguishable from 'infrastructure failed.' The model sees a permission denial and may prompt the user, when the real issue is the bridge connection. Per RoboCFO: structured actionable errors for model self-correction. Changes: - Add reason parameter to clearAllPendingToolCalls (bridge_disconnected vs manual_disconnect) - Add console.warn on each cleared request with the reason - Pass explicit reason at both call sites (ws.onclose and reconnectMcp) Reference: RoboCFO 'Inside an Agent Harness: Technical Guide' — tool errors must provide enough context for the model to self-correct Co-authored-by: yueqi.guo <guo_yueqi@qq.com>
…ignoring (superduck-ai#216) The navigate tool's domain category check (pageTools.ts:419) had a bare catch {} that silently swallowed all errors. If the category service becomes unavailable, the safety gate is bypassed with no diagnostic. Per RoboCFO: 'tool errors must never be silently swallowed.' Changes: - Replace empty catch with console.warn that logs the URL and error - Add explanatory comment about fail-open rationale (permission check below still enforces per-host grants as a second line of defense) - Improve URL validation error message to be model-actionable: includes the invalid input, expected format, and supported schemes Reference: RoboCFO 'Inside an Agent Harness: Technical Guide' — structured actionable errors for model self-correction Co-authored-by: yueqi.guo <guo_yueqi@qq.com>
…nt ignore (superduck-ai#217) When enableConsoleTracking or enableNetworkTracking failed (CDP attach error, debugger detached, etc.), the error was silently swallowed. The subsequent getConsoleMessages/getNetworkRequests returned [], and the model saw 'No messages found' — a misleading response that hides the real failure. Per Addy Osmani: 'success is silent, failures are verbose.' Changes: - Console tracking: replace empty catch with error return including the failure reason and recovery suggestion - Network tracking: same pattern Reference: Addy Osmani 'Agent Harness Engineering' — hooks and feedback loops must make failures actionable for the model Co-authored-by: yueqi.guo <guo_yueqi@qq.com>
…superduck-ai#218) Add chrome.tabs.onRemoved listener in ChromeDebuggerProtocol that cleans up tabLocks, consoleMessagesByTab, networkRequestsByTab, consoleTrackingEnabled, and networkTrackingEnabled when a tab is closed. Also fix withTabLock to self-delete the map entry when no successor is queued, preventing unbounded promise chain accumulation per tab. Without this fix, every tab that ever used CDP features (screenshots, console tracking, network tracking) leaks Map/Set entries for the entire service worker lifetime. With MAX_LOGS_PER_TAB=10000 and MAX_REQUESTS_PER_TAB=1000, a single leaked tab can hold significant memory. Agent harness principle: Deterministic Lifecycle Hooks — per-resource state must be cleaned up when the resource is destroyed. Co-authored-by: yueqi.guo <guo_yueqi@qq.com>
…teTool (superduck-ai#221) Between reconnect() releasing connMu and ExecuteTool re-acquiring it, a concurrent Close() call could set b.conn to nil. The subsequent b.conn.SetDeadline() would then panic with a nil pointer dereference. Add a nil check on b.conn after acquiring the lock, returning a clear error instead of panicking. Agent harness principle: Structured actionable errors — surface real failures with clear messages instead of crashing. Co-authored-by: yueqi.guo <guo_yueqi@qq.com>
…k-ai#224) The browser_batch tool accepted an unbounded actions array with no upper limit. A model generating a large batch (e.g., 100+ actions) could consume excessive resources and create long-running, impossible-to-cancel operations. Now caps batches at 20 actions (MAX_BATCH_ACTIONS), returns a clear error when exceeded, and advertises the limit in the tool schema (maxItems) and description. The tool already stopped on first error, so smaller batches fail fast. Inspired by agent harness best practices: deterministic input validation at the harness layer prevents runaway agent loops. Co-authored-by: yueqi.guo <guo_yueqi@qq.com>
The tool call audit events (superduck.chat.tool_called and superduck.mcp.tool_called) recorded the tool name, session, and success/failure, but NOT the tool's input arguments. This made it impossible to answer 'what did the agent do' from telemetry alone. Per RoboCFO: 'record every action, tool call, approval decision.' Changes: - Add safe, low-cardinality input fields to both audit event paths: action (computer), filter, depth, limit, clear, diff, newTab, full, allowCrossOrigin - Intentionally exclude PII-bearing fields (URLs, selectors, code, text) — these are available in session history if needed - Apply to both the sidepanel (processToolResults) and MCP (executeToolInner) code paths Reference: RoboCFO 'Inside an Agent Harness: Technical Guide' — audit trail must capture what the agent did, with what data, and why Co-authored-by: yueqi.guo <guo_yueqi@qq.com>
…duck-ai#222) validateComputerArgs only logged warnings for out-of-range duration values (>30s or <0) but still forwarded them to the Chrome extension. This meant invalid tool calls consumed bridge and extension resources before failing with a confusing downstream error. Now returns a clear validation error at the bridge layer so the agent receives an actionable message ('duration 45.0 exceeds schema maximum of 30 seconds') and can self-correct. This follows the agent harness principle of rejecting rather than clamping to avoid 'over-shackling' where the model never learns correct bounds. Co-authored-by: yueqi.guo <guo_yueqi@qq.com>
…perduck-ai#223) The Bridge WebSocket reconnection had no upper limit — retryCount grew indefinitely with exponential backoff capped at 20s. This caused endless reconnection attempts that wasted resources and masked permanent disconnections. Now stops after MAX_BRIDGE_RETRIES (15) attempts (~4-5 minutes) and emits a 'reconnect_exhausted' telemetry event. Manual connectBridge() calls reset the counter so users can restart the cycle. Inspired by agent harness best practices: retry budgets per turn are not optional in production. Co-authored-by: yueqi.guo <guo_yueqi@qq.com>
) Previously, when CDP debugger attachment failed (e.g., user clicked Cancel on Chrome's debugger prompt), the error was silently swallowed. This caused all downstream CDP-dependent tools (screenshot, click, read_page) to fail with confusing errors like 'No debugger attached'. Now reports attach failures as actionable errors so the agent can decide to retry or inform the user. Chrome internal pages (chrome://, edge://, etc.) are still silently skipped since they cannot be debugged by design. Inspired by agent harness best practices: tool errors must be structured and actionable, never silent. Co-authored-by: yueqi.guo <guo_yueqi@qq.com>
…i#219) * fix(crx): clean up screenshotContextManager on tab close Add chrome.tabs.onRemoved listener to clear screenshotContextManager entries when tabs are closed. Without this, every tab that takes a screenshot leaks its viewport/screenshot dimensions in the Map for the entire service worker lifetime. Stale entries could also cause incorrect click coordinate mappings if Chrome reuses tab IDs. Agent harness principle: Deterministic Lifecycle Hooks — per-resource state must be cleaned up when the resource is destroyed. * test: add coverage for chrome.tabs.onRemoved listener Add test file to verify the screenshot context cleanup listener registered at module load time. Uses dynamic import to ensure chrome mock is set up before shared.ts is loaded. This brings branch coverage from 83.33% back above the 85% threshold. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: yueqi.guo <guo_yueqi@qq.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…perduck-ai#226) The attachDebuggerInner method was unconditionally detaching and re-attaching the debugger on every call, even when already attached. Each chrome.debugger.attach() triggers Chrome to display a new "X is debugging this browser" banner, causing overlapping banners when switching tabs during agent operation. Now check wasAttached first and skip the detach/attach cycle if the debugger is already attached. Only perform the initial attach when the debugger is not yet attached to the tab. This prevents the banner duplication issue while preserving the correct behavior for console/network tracking re-enablement. Co-authored-by: yueqi.guo <guo_yueqi@qq.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Document the step-by-step process for batch-submitting low-risk PRs: risk assessment, sequential submission, bot comment monitoring, 10-minute silent window before merge, CI pass requirement, and cleanup. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This reverts commit a0e2853.
…uck-ai#227) The visual_test.sh used --amount 15 which exceeds the schema maximum of 10 wheel ticks. This was valid when CLI allowed 1-100 but became silently broken after validation was tightened to match the MCP schema. Changed to --amount 10 (schema maximum) to maintain strong scroll effect. Co-authored-by: yueqi.guo <guo_yueqi@qq.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
… fixes (superduck-ai#229) * feat(sidepanel): add session history panel + persistence - Restore chat history on sidepanel reopen (close + reopen shows the prior assistant reply, fixes reported Bug 1) - New SessionHistoryPanel for browsing / loading past conversations - Persist session state when the sidepanel iframe is destroyed on tab switch (instead of relying on React state surviving) - Guard Enter / Escape handlers against IME composition to avoid accidental submits in CJK input - Hoist useTabEvent properties array to module scope to prevent infinite re-render loop on tab switch - Drop 7 broken P0 specs that fail due to a Playwright sidepanel fixture bug; see e2e/STATUS.md for context and recovery path * feat(crx): make sidepanel window-bound to survive tab switches - Sidepanel is now window-scoped, not tab-scoped, so it stays open when the user switches tabs - useActiveTabId dynamically tracks chrome.tabs.onActivated to re-target the conversation to the new active tab - elementSelectorInjector adapts to the new active tab context (useModelConfig no longer captures a stale tabId) * fix(crx): tighten tab group management and sidePanel gesture handling - Lock the tab ID during agent execution so the tab group can't get reassigned when state refreshes - Stop refreshSecondaryState from creating a new tab group on tab switch - Set lockedTabIdRef synchronously in effectiveSendPrompt - Stop the detach / attach cycle that stacks debugger banners - When the user explicitly ungroups the main tab, don't rebuild the group from cached metadata - PANEL_READY now promotes the active tab to main if it opens inside an existing group (matches pre-setPanelBehavior flow) - chrome.sidePanel.open() is now fire-and-forget so it stays in the user-gesture chain (otherwise Ctrl+E rejects with 'must be called in response to a user gesture') - Add side_panel.default_path so the action click uses setPanelBehavior({openPanelOnActionClick}) and never hits the gesture rejection in the first place * test(e2e): add regression specs, headless default, helpers, and STATUS.md Adds three regression specs (perf render, sidepanel open flow, session history), a headless default that keeps --load-extension working, an initialTabId param on the openSidepanel helper for fixture-based tab targeting, and STATUS.md documenting the 7 P0 specs that were dropped because of an upstream Playwright sidepanel fixture bug. * fix(sidepanel): tighten type predicates and screenshot options - sidepanelGuards.normalizeToolResultContent: narrow the type predicate from ApiToolResultContentBlock to ApiTextContentBlock | ApiImageContentBlock, which are the only blocks the filter actually accepts and which are cleanly a subset of BetaContentBlockParam. The wider union triggered TS2677 + TS2322 because SDK v1 / v2 type definitions don't fully overlap. - hooks.useActiveTabId: inline the onActivated listener shape ({ tabId: number; windowId: number }) because the @types/chrome version pinned in this project does not export TabActiveInfo. - useLightningMode: drop the format / quality fields from the screenshot() options object — they are no-ops (cdp.screenshot hard-codes jpeg / INITIAL_JPEG_QUALITY) and ScreenshotOptions doesn't declare them. * fix(sidepanel): address P2 codex reviews on hooks and SessionHistoryPanel - hooks.ts: track sidepanel's windowId on mount and ignore chrome.tabs.onActivated events from other windows, so the panel cannot retarget to a different window's tab when the user switches tabs elsewhere - SessionHistoryPanel.tsx: replace the row's outer <button> with a div+role=button+tabIndex+onKeyDown so the nested delete <button> is no longer invalid HTML and stays reachable for keyboard / screen-reader users * fix(sidepanel): avoid remapping prior tab session + clean up aliases on delete - SidepanelApp.tsx: drop the standalone tab→session write effect. Writing on every (activeSessionId, query.tabId) change would rebind the *new* tab to the *old* tab's session whenever the user switched tabs while the sidepanel stayed open, silently overwriting the new tab's prior mapping. The tab→session mapping is now written only inside the resolver, at the moment a session is actually chosen / restored / created for the current tab. - sidepanelGuards.ts: add collectTabSessionKeysToRemove() helper that scans chrome.storage.local for sidepanel_tab_session_<n> entries whose value equals a given sessionId. - SessionHistoryPanel.tsx: on delete, also remove the tab→session aliases (and the global last-active key) pointing at the deleted session so future sidepanel opens on those tabs do not resurrect an empty session with the same id. --------- Co-authored-by: yueqi.guo <guo_yueqi@qq.com> Co-authored-by: yueqi.guo <yueqi.guo@users.noreply.github.com>
…ven validation Closes architectural problem E in docs/architectural-improvements-todo.md. The parameters field on every ToolDefinition carries a full JSON schema (type, required, minimum, maximum, enum, minItems, maxItems, maxLength, minLength), but the schema was only used to generate the definition sent to the LLM. The runtime path went through coerceToolInputTypes (pageToolsSupport/helpers.ts:91-113), which only did string-to-number and string-to-boolean coercion. There was no range check, no enum check, no required-field check, no length check. Concretely: a model could pass duration: 999 to the computer wait action (declared maximum: 30) and the bad value would flow straight into the CDP layer. Same for text being too long, coordinate with fewer than 2 items, and so on. Fixes: - New validateToolInput function in pageToolsSupport/helpers.ts. Schema-driven so any new constraint added to a tool parameters declaration is automatically enforced at runtime. Recurses one level into nested properties / items for common shapes. - Call it in core.ts executeTool dispatch, immediately after coerceToolInput and before tool.execute(). Failures return createErrorResponse with the aggregated error list. - Tolerates missing / wrong-shape / unknown tool input — schema-driven enforcement when we have a schema, conservative passthrough otherwise. 22 new unit tests in validateToolInput.test.ts cover required-field detection, type mismatches (including the [string, null] union shape), minimum/maximum, maxLength/minItems/maxItems, enum membership, degenerate inputs, and multi-error aggregation. typecheck 9 errors (unchanged baseline), 172/172 unit tests pass.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes architectural problem E in
docs/architectural-improvements-todo.md.The
parametersfield on everyToolDefinitioncarries a full JSON schema (type, required, minimum, maximum, enum, minItems, maxItems, maxLength, minLength), but the schema was only used to generate the definition sent to the LLM. The runtime path went throughcoerceToolInputTypes(pageToolsSupport/helpers.ts:91-113), which only did string→number / string→boolean coercion. There was no range check, no enum check, no required-field check, no length check.Concretely: a model could pass
duration: 999to thecomputerwaitaction (declaredmaximum: 30) and the bad value would flow straight into the CDP layer. Same fortextbeing too long,coordinatewith fewer than 2 items, etc.What changed
validateToolInput(toolName, input, toolDefinitions)function inpageToolsSupport/helpers.ts. Schema-driven so any new constraint added to a tool'sparametersdeclaration is automatically enforced at runtime. Recurses one level into nestedproperties/itemsfor common shapes.core.ts#executeTooldispatch, immediately aftercoerceToolInputand beforetool.execute(). Failures returncreateErrorResponsewith the aggregated error list.validateToolInput.test.tsVerification
ContentBlocksRenderer.tsx— unrelated)172/172 passed(+22 new, was 150/150)🤖 Generated with Claude Code