feat: Add Google Suite & Telegram WASM tools#9
Merged
ilblackdragon merged 8 commits intomainfrom Feb 9, 2026
Merged
Conversation
Scaffold two new WASM tools that share a single Google OAuth token: - google-calendar: list/get/create/update/delete calendar events - gmail: list/search/get/send/draft/reply/trash emails Both tools use the sandboxed WIT interface with strict HTTP allowlists, credential injection, and rate limiting. OAuth config requests only the minimum scopes needed (calendar.events, gmail.modify, gmail.compose). Also adds the /add-tool skill for scaffolding future WASM or built-in tools with all boilerplate wired up. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Supports 12 actions: list/get/download/upload/update files, create folders, delete/trash, share/list/remove permissions, and list shared drives. Works with both personal and organizational drives via the corpora parameter. Uses shared google_oauth_token for auth. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Three new Google Workspace tools sharing google_oauth_token: - Sheets: create spreadsheets, read/write/append values, manage sheets, format cells - Docs: create/read/edit documents, text formatting, paragraphs, tables, lists - Slides: create/edit presentations, shapes, images, text formatting, thumbnails, templates Also adds tools-src/TOOLS.md tracking implementation status. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace TDLight Docker dependency with pure-Rust grammers crates for direct encrypted MTProto communication to Telegram's web transport endpoints. No middleware, no Docker needed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Library crates should not commit lock files. Consolidate per-tool .gitignore into a single one at wasm-tools/ level. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All tools are WASM, the extra nesting added no value. Moves all tool crates up one level, updates WIT paths and documentation references. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add OAuth 2.0 auth section to Slack capabilities with proper scopes and manual fallback instructions - URL-encode query parameters in GET requests to prevent injection - Remove dead SlackApiError struct - Pin wit-bindgen to =0.36 across all WASM tools for Rust 1.86 compat - Update add-tool template with pinned version Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Merged
6 tasks
serrrfirat
pushed a commit
to serrrfirat/ironclaw
that referenced
this pull request
Feb 16, 2026
* Add Google Calendar and Gmail WASM tools, and /add-tool skill Scaffold two new WASM tools that share a single Google OAuth token: - google-calendar: list/get/create/update/delete calendar events - gmail: list/search/get/send/draft/reply/trash emails Both tools use the sandboxed WIT interface with strict HTTP allowlists, credential injection, and rate limiting. OAuth config requests only the minimum scopes needed (calendar.events, gmail.modify, gmail.compose). Also adds the /add-tool skill for scaffolding future WASM or built-in tools with all boilerplate wired up. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Document WASM vs MCP server decision guide in CLAUDE.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add Google Drive WASM tool with full file and sharing management Supports 12 actions: list/get/download/upload/update files, create folders, delete/trash, share/list/remove permissions, and list shared drives. Works with both personal and organizational drives via the corpora parameter. Uses shared google_oauth_token for auth. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add Google Sheets, Docs, and Slides WASM tools Three new Google Workspace tools sharing google_oauth_token: - Sheets: create spreadsheets, read/write/append values, manage sheets, format cells - Docs: create/read/edit documents, text formatting, paragraphs, tables, lists - Slides: create/edit presentations, shapes, images, text formatting, thumbnails, templates Also adds tools-src/TOOLS.md tracking implementation status. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add Telegram WASM tool with direct MTProto over HTTPS Replace TDLight Docker dependency with pure-Rust grammers crates for direct encrypted MTProto communication to Telegram's web transport endpoints. No middleware, no Docker needed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Gitignore Cargo.lock files in WASM tools Library crates should not commit lock files. Consolidate per-tool .gitignore into a single one at wasm-tools/ level. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Flatten tools-src/wasm-tools/ into tools-src/ All tools are WASM, the extra nesting added no value. Moves all tool crates up one level, updates WIT paths and documentation references. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix Slack tool: add OAuth auth, URL encoding, pin wit-bindgen - Add OAuth 2.0 auth section to Slack capabilities with proper scopes and manual fallback instructions - URL-encode query parameters in GET requests to prevent injection - Remove dead SlackApiError struct - Pin wit-bindgen to =0.36 across all WASM tools for Rust 1.86 compat - Update add-tool template with pinned version Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
ilblackdragon
added a commit
that referenced
this pull request
Feb 19, 2026
- Use manifest.name (not crate_name) for installed filenames so discovery, auth, and CLI commands all agree on the stem (#1) - Add AlreadyInstalled error variant instead of misleading ExtensionNotFound (#2) - Add DownloadFailed error variant with URL context instead of stuffing URLs into PathBuf (#3) - Validate HTTP status with error_for_status() before reading response bytes in artifact downloads (#4) - Switch build_wasm_component to tokio::process::Command with status() so build output streams to the terminal (#6) - Find WASM artifact by crate_name specifically instead of picking the first .wasm file in the release directory (#7) - Add is_file() guard in catalog loader to skip directories (#8) - Detect ambiguous bare-name lookups when both tools/<name> and channels/<name> exist, with get_strict() returning an error (#9) - Fix wizard step_extensions to check tool.name for installed detection, consistent with the new naming (#11, #12) - Fix redundant closures and map_or clippy warnings in changed files Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ilblackdragon
added a commit
that referenced
this pull request
Feb 20, 2026
- Use manifest.name (not crate_name) for installed filenames so discovery, auth, and CLI commands all agree on the stem (#1) - Add AlreadyInstalled error variant instead of misleading ExtensionNotFound (#2) - Add DownloadFailed error variant with URL context instead of stuffing URLs into PathBuf (#3) - Validate HTTP status with error_for_status() before reading response bytes in artifact downloads (#4) - Switch build_wasm_component to tokio::process::Command with status() so build output streams to the terminal (#6) - Find WASM artifact by crate_name specifically instead of picking the first .wasm file in the release directory (#7) - Add is_file() guard in catalog loader to skip directories (#8) - Detect ambiguous bare-name lookups when both tools/<name> and channels/<name> exist, with get_strict() returning an error (#9) - Fix wizard step_extensions to check tool.name for installed detection, consistent with the new naming (#11, #12) - Fix redundant closures and map_or clippy warnings in changed files Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ilblackdragon
added a commit
that referenced
this pull request
Feb 20, 2026
…tion (#238) * feat: add extension registry with metadata catalog, CLI, and onboarding integration Adds a central registry that catalogs all 14 available extensions (10 tools, 4 channels) with their capabilities, auth requirements, and artifact references. The onboarding wizard now shows installable channels from the registry and offers tool installation as a new Step 7. - registry/ folder with per-extension JSON manifests and bundle definitions - src/registry/ module: manifest structs, catalog loader, installer - `ironclaw registry list|info|install|install-defaults` CLI commands - Setup wizard enhanced: channels from registry, new extensions step (8 steps) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(setup): resolve workspace errors for tool crates and channels-only onboarding Tool crates in tools-src/ and channels-src/ failed `cargo metadata` during onboard install because Cargo resolved them as part of the root workspace. Add `[workspace]` table to each standalone crate and extend the root `workspace.exclude` list so they build independently. Channels-only mode (`onboard --channels-only`) failed with "Secrets not configured" and "No database connection" because it skipped database and security setup. Add `reconnect_existing_db()` to establish the DB connection and load saved settings before running channel configuration. Also improve the tunnel "already configured" display to show full provider details (domain, mode, command) instead of just the provider name. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(registry): address PR review feedback on installer and catalog - Use manifest.name (not crate_name) for installed filenames so discovery, auth, and CLI commands all agree on the stem (#1) - Add AlreadyInstalled error variant instead of misleading ExtensionNotFound (#2) - Add DownloadFailed error variant with URL context instead of stuffing URLs into PathBuf (#3) - Validate HTTP status with error_for_status() before reading response bytes in artifact downloads (#4) - Switch build_wasm_component to tokio::process::Command with status() so build output streams to the terminal (#6) - Find WASM artifact by crate_name specifically instead of picking the first .wasm file in the release directory (#7) - Add is_file() guard in catalog loader to skip directories (#8) - Detect ambiguous bare-name lookups when both tools/<name> and channels/<name> exist, with get_strict() returning an error (#9) - Fix wizard step_extensions to check tool.name for installed detection, consistent with the new naming (#11, #12) - Fix redundant closures and map_or clippy warnings in changed files Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(setup): restore DB connection fields after settings reload reconnect_postgres() and reconnect_libsql() called Settings::from_db_map() which overwrote database_url / libsql_path / libsql_url set from env vars. Also use get_strict() in cmd_info to surface ambiguous bare-name errors. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style: fix clippy collapsible_if and print_literal warnings Collapse nested if-let chains and inline string literals in format macros to satisfy CI clippy lint checks (deny warnings). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(registry): prefer artifacts for install-defaults and improve dir lookup - InstallDefaults now defaults to downloading pre-built artifacts (matching `registry install` behavior), with --build flag for source builds. - find_registry_dir() walks up 3 ancestor levels from the exe and adds a CARGO_MANIFEST_DIR fallback, matching load_registry_catalog() logic. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
jaswinder6991
pushed a commit
to jaswinder6991/ironclaw
that referenced
this pull request
Feb 26, 2026
…tion (nearai#238) * feat: add extension registry with metadata catalog, CLI, and onboarding integration Adds a central registry that catalogs all 14 available extensions (10 tools, 4 channels) with their capabilities, auth requirements, and artifact references. The onboarding wizard now shows installable channels from the registry and offers tool installation as a new Step 7. - registry/ folder with per-extension JSON manifests and bundle definitions - src/registry/ module: manifest structs, catalog loader, installer - `ironclaw registry list|info|install|install-defaults` CLI commands - Setup wizard enhanced: channels from registry, new extensions step (8 steps) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(setup): resolve workspace errors for tool crates and channels-only onboarding Tool crates in tools-src/ and channels-src/ failed `cargo metadata` during onboard install because Cargo resolved them as part of the root workspace. Add `[workspace]` table to each standalone crate and extend the root `workspace.exclude` list so they build independently. Channels-only mode (`onboard --channels-only`) failed with "Secrets not configured" and "No database connection" because it skipped database and security setup. Add `reconnect_existing_db()` to establish the DB connection and load saved settings before running channel configuration. Also improve the tunnel "already configured" display to show full provider details (domain, mode, command) instead of just the provider name. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(registry): address PR review feedback on installer and catalog - Use manifest.name (not crate_name) for installed filenames so discovery, auth, and CLI commands all agree on the stem (nearai#1) - Add AlreadyInstalled error variant instead of misleading ExtensionNotFound (nearai#2) - Add DownloadFailed error variant with URL context instead of stuffing URLs into PathBuf (nearai#3) - Validate HTTP status with error_for_status() before reading response bytes in artifact downloads (nearai#4) - Switch build_wasm_component to tokio::process::Command with status() so build output streams to the terminal (nearai#6) - Find WASM artifact by crate_name specifically instead of picking the first .wasm file in the release directory (nearai#7) - Add is_file() guard in catalog loader to skip directories (nearai#8) - Detect ambiguous bare-name lookups when both tools/<name> and channels/<name> exist, with get_strict() returning an error (nearai#9) - Fix wizard step_extensions to check tool.name for installed detection, consistent with the new naming (nearai#11, nearai#12) - Fix redundant closures and map_or clippy warnings in changed files Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(setup): restore DB connection fields after settings reload reconnect_postgres() and reconnect_libsql() called Settings::from_db_map() which overwrote database_url / libsql_path / libsql_url set from env vars. Also use get_strict() in cmd_info to surface ambiguous bare-name errors. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style: fix clippy collapsible_if and print_literal warnings Collapse nested if-let chains and inline string literals in format macros to satisfy CI clippy lint checks (deny warnings). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(registry): prefer artifacts for install-defaults and improve dir lookup - InstallDefaults now defaults to downloading pre-built artifacts (matching `registry install` behavior), with --build flag for source builds. - find_registry_dir() walks up 3 ancestor levels from the exe and adds a CARGO_MANIFEST_DIR fallback, matching load_registry_catalog() logic. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
5 tasks
ilblackdragon
added a commit
that referenced
this pull request
Mar 7, 2026
…lity Security fixes: - Remove SSRF-prone download() from DocumentExtractionMiddleware (#13) - Sanitize filenames in workspace path to prevent directory traversal (#11) - Pre-check file size before reading in WASM wrapper to prevent OOM (#2) - Percent-encode file_id in Telegram source URLs (#7) Correctness fixes: - Clear image_content_parts on turn end to prevent memory leak (#1) - Find first *successful* transcription instead of first overall (#3) - Enforce data.len() size limit in document extraction (#10) - Use UTF-8 safe truncation with char_indices() (#12) Robustness & code quality: - Add 120s timeout to OpenAI Whisper HTTP client (#5) - Trim trailing slash from Whisper base_url (#6) - Allow ~/.ironclaw/ paths in WASM wrapper (#8) - Return error from on_broadcast in Slack/Discord/WhatsApp (#9) - Fix doc comment in HTTP tool (#4) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ilblackdragon
added a commit
that referenced
this pull request
Mar 7, 2026
* feat: add inbound attachment support to WASM channel system Add attachment record to WIT interface and implement inbound media parsing across all four channel implementations (Telegram, Slack, WhatsApp, Discord). Attachments flow from WASM channels through EmittedMessage to IncomingMessage with validation (size limits, MIME allowlist, count caps) at the host boundary. - Add `attachment` record to `emitted-message` in wit/channel.wit - Add `IncomingAttachment` struct to channel.rs and re-export - Add host-side validation (20MB total, 10 max, MIME allowlist) - Telegram: parse photo, document, audio, video, voice, sticker - Slack: parse file attachments with url_private - WhatsApp: parse image, audio, video, document with captions - Discord: backward-compatible empty attachments - Update FEATURE_PARITY.md section 7 - Add fixture-based tests per channel and host integration tests [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: integrate outbound attachment support and reconcile WIT types (#409) Reconcile PR #409's outbound attachment work with our inbound attachment support into a unified design: WIT type split: - `inbound-attachment` in channel-host: metadata-only (id, mime_type, filename, size_bytes, source_url, storage_key, extracted_text) - `attachment` in channel: raw bytes (filename, mime_type, data) on agent-response for outbound sending Outbound features (from PR #409): - `on-broadcast` WIT export for proactive messages without prior inbound - Telegram: multipart sendPhoto/sendDocument with auto photo→document fallback for files >10MB - wrapper.rs: `call_on_broadcast`, `read_attachments` from disk, attachment params threaded through `call_on_respond` - HTTP tool: `save_to` param for binary downloads to /tmp/ (50MB limit, path traversal protection, SSRF-safe redirect following) - Message tool: allow /tmp/ paths for attachments alongside base_dir - Credential env var fallback in inject_channel_credentials Channel updates: - All 4 channels implement on_broadcast (Telegram full, others stub) - Telegram: polling_enabled config, adjusted poll timeout - Inbound attachment types renamed to InboundAttachment in all channels Tests: 1965 passing (9 new), 0 clippy warnings [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add audio transcription pipeline and extensible WIT attachment design Add host-side transcription middleware (OpenAI Whisper) that detects audio attachments with inline data on incoming messages and transcribes them automatically. Refactor WIT inbound-attachment to use extras-json and a store-attachment-data host function instead of typed fields, so future attachment properties (dimensions, codec, etc.) don't require WIT changes that invalidate all channel plugins. - Add src/transcription/ module: TranscriptionProvider trait, TranscriptionMiddleware, AudioFormat enum, OpenAI Whisper provider - Add src/config/transcription.rs: TRANSCRIPTION_ENABLED/MODEL/BASE_URL - Wire middleware into agent message loop via AgentDeps - WIT: replace data + duration-secs with extras-json + store-attachment-data - Host: parse extras-json for well-known keys, merge stored binary data - Telegram: download voice files via store-attachment-data, add duration to extras-json, add /file/bot to HTTP allowlist, voice-only placeholder - Add reqwest multipart feature for Whisper API uploads - 5 regression tests for transcription middleware Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: wire attachment processing into LLM pipeline with multimodal image support Attachments on incoming messages are now augmented into user text via XML tags before entering the turn system, and images with data are passed as multimodal content parts (base64 data URIs) to LLM providers. This enables audio transcripts, document text, and image content to reach the LLM without changes to ChatMessage serialization or provider interfaces. - Add src/agent/attachments.rs with augment_with_attachments() and 9 unit tests - Add ContentPart/ImageUrl types to llm::provider with OpenAI-compatible serde - Carry image_content_parts transiently on Turn (skipped in serialization) - Update nearai_chat and rig_adapter to serialize multimodal content - Add 3 e2e tests verifying attachments flow through the full agent loop Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: CI failures — formatting, version bumps, and Telegram voice test - Fix cargo fmt formatting in attachments.rs, nearai_chat.rs, rig_adapter.rs, e2e_attachments.rs - Bump channel registry versions 0.1.0 → 0.2.0 (discord, slack, telegram, whatsapp) to satisfy version-bump CI check - Fix Telegram test_extract_attachments_voice: add missing required `duration` field to voice fixture JSON Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: bump WIT channel version to 0.3.0, fix Telegram voice test, add pre-commit hook - Bump wit/channel.wit package version 0.2.0 → 0.3.0 (interface changed with store-attachment-data) - Update WIT_CHANNEL_VERSION constant and registry wit_version fields to match - Fix Telegram test_extract_attachments_voice: gate voice download behind #[cfg(target_arch = "wasm32")] so host functions aren't called in native tests, update assertions for generated filename and extras_json duration - Add @0.3.0 linker stubs in wit_compat.rs - Add .githooks/pre-commit hook that runs scripts/check-version-bumps.sh when WIT or extension sources are staged - Symlink commit-msg regression hook into .githooks/ [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: extract voice download from extract_attachments into handle_message Move download_voice_file + store_attachment_data calls out of extract_attachments into a separate download_and_store_voice function called from handle_message. This keeps extract_attachments as a pure data-mapping function with no host calls, making it fully testable in native unit tests without #[cfg(target_arch)] gates. [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address PR review comments — security, correctness, and code quality Security fixes: - Add path validation to read_attachments (restrict to /tmp/) preventing arbitrary file reads from compromised tools - Escape XML special characters in attachment filenames, MIME types, and extracted text to prevent prompt injection via tag spoofing - Percent-encode file_id in Telegram getFile URL to prevent query injection - Clone SecretString directly instead of expose_secret().to_string() Correctness fixes: - Fix store_attachment_data overwrite accounting: subtract old entry size before adding new to prevent inflated totals and false rejections - Use max(reported, stored_size) for attachment size accounting to prevent WASM channels from under-reporting size_bytes to bypass limits - Add application/octet-stream to MIME allowlist (channels default unknown types to this) Code quality: - Extract send_response helper in Telegram, deduplicating on_respond and on_broadcast - Rename misleading Discord test to test_parse_slash_command_interaction - Fix .githooks/commit-msg to use relative symlink (portable across machines) [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add tool_upgrade command + fix TOCTOU in save_to path validation Add `tool_upgrade` — a new extension management tool that automatically detects and reinstalls WASM extensions with outdated WIT versions. Preserves authentication secrets during upgrade. Supports upgrading a single extension by name or all installed WASM tools/channels at once. Fix TOCTOU in `validate_save_to_path`: validate the path *before* creating parent directories, so traversal paths like `/tmp/../../etc/` cannot cause filesystem mutations outside /tmp before being rejected. [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: unify WIT package version to 0.3.0 across tool.wit and all capabilities tool.wit and channel.wit share the `near:agent` package namespace, so they must declare the same version. Bumps tool.wit from 0.2.0 to 0.3.0 and updates all capabilities files and registry entries to match. Fixes `cargo component build` failure: "package identifier near:agent@0.2.0 does not match previous package name of near:agent@0.3.0" [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: move WIT file comments after package declaration WIT treats `//` comments before `package` as doc comments. When both tool.wit and channel.wit had header comments, the parser rejected them as "doc comments on multiple 'package' items". Move comments after the package declaration in both files. Also bumps tool registry versions to 0.2.0 to match the WIT 0.3.0 bump. [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: display extension versions in gateway Extensions tab Add version field to InstalledExtension and RegistryEntry types, pipe through the web API (ExtensionInfo, RegistryEntryInfo), and render as a badge in the gateway UI for both installed and available extensions. For installed WASM extensions, version is read from the capabilities file with a fallback to the registry entry when the local file has no version (old installations). Bump all extension Cargo.toml and registry JSON versions from 0.1.0 to 0.2.0 to keep them in sync. [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add document text extraction middleware for PDF, Office, and text files Extract text from document attachments (PDF, DOCX, PPTX, XLSX, RTF, plain text, code files) so the LLM can reason about uploaded documents. Uses pdf-extract for PDFs, zip+XML parsing for Office XML formats, and UTF-8 decode for text files. Wired into the agent loop after transcription middleware. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: download document files in Telegram channel for text extraction The DocumentExtractionMiddleware needs file bytes in the attachment `data` field, but only voice files were being downloaded. Document attachments (PDFs, DOCX, etc.) had empty `data` and a source_url with a credential placeholder that only works inside the WASM host's http_request. Add `download_and_store_documents()` that downloads non-voice, non-image, non-audio attachments via the existing two-step getFile→download flow and stores bytes via `store_attachment_data` for host-side extraction. Also rename `download_voice_file` → `download_telegram_file` since it's generic for any file_id. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: allow Office MIME types and increase file download limit for Telegram Two issues preventing document extraction from Telegram: 1. PPTX/DOCX/XLSX MIME types (application/vnd.*) were dropped by the WASM host attachment allowlist — add application/vnd., application/msword, and application/rtf prefixes. 2. Telegram file downloads over 10 MB failed with "Response body too large" — set max_response_bytes to 20 MB in Telegram capabilities. [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: report document extraction errors back to user instead of silently skipping - Bump max_response_bytes to 50 MB for Telegram file downloads - When document extraction fails (too large, download error, parse error), set extracted_text to a user-friendly error message instead of leaving it None. This ensures the LLM tells the user what went wrong. - On Telegram download failure, set extracted_text with the error so the user sees feedback even when the file never reaches the extraction middleware. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: store extracted document text in workspace memory for search/recall After document extraction succeeds, write the extracted text to workspace memory at `documents/{date}/{filename}`. This enables: - Full-text and semantic search over past uploaded documents - Cross-conversation recall ("what did that PDF say?") - Automatic chunking and embedding via the workspace pipeline Documents are stored with metadata header (uploader, channel, date, MIME type). Error messages (extraction failures) are not stored — only successful extractions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: CI failures — formatting, unused assignment warning - Run cargo fmt on document_extraction and agent_loop modules - Suppress unused_assignments warning on trace_llm_ref (used only behind #[cfg(feature = "libsql")]) [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address PR review comments — security, correctness, and code quality Security fixes: - Remove SSRF-prone download() from DocumentExtractionMiddleware (#13) - Sanitize filenames in workspace path to prevent directory traversal (#11) - Pre-check file size before reading in WASM wrapper to prevent OOM (#2) - Percent-encode file_id in Telegram source URLs (#7) Correctness fixes: - Clear image_content_parts on turn end to prevent memory leak (#1) - Find first *successful* transcription instead of first overall (#3) - Enforce data.len() size limit in document extraction (#10) - Use UTF-8 safe truncation with char_indices() (#12) Robustness & code quality: - Add 120s timeout to OpenAI Whisper HTTP client (#5) - Trim trailing slash from Whisper base_url (#6) - Allow ~/.ironclaw/ paths in WASM wrapper (#8) - Return error from on_broadcast in Slack/Discord/WhatsApp (#9) - Fix doc comment in HTTP tool (#4) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: formatting — cargo fmt Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address latest PR review — doc comments, error messages, version bumps - Fix DocumentExtractionMiddleware doc comment (no longer downloads from source_url) - Fix error message: "no inline data" instead of "no download URL" - Log error + fallback instead of silent unwrap_or_default on Whisper HTTP client - Bump all capabilities.json versions from 0.1.0 to 0.2.0 to match Cargo.toml Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: remove unsupported profile: minimal from CI workflows [skip-regression-check] dtolnay/rust-toolchain@stable does not accept the 'profile' input (it was a parameter for the deprecated actions-rs/toolchain action). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: merge with latest main — resolve compilation errors and PR review nits - Add version: None to RegistryEntry/InstalledExtension test constructors - Fix MessageContent type mismatches in nearai_chat tests (String → MessageContent::Text) - Fix .contains() calls on MessageContent — use .as_text().unwrap() - Remove redundant trace_llm_ref = None assignment in test_rig - Check data size before clone in document extraction to avoid unnecessary allocation [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
7 tasks
23 tasks
6 tasks
bkutasi
pushed a commit
to bkutasi/ironclaw
that referenced
this pull request
Mar 28, 2026
* Add Google Calendar and Gmail WASM tools, and /add-tool skill Scaffold two new WASM tools that share a single Google OAuth token: - google-calendar: list/get/create/update/delete calendar events - gmail: list/search/get/send/draft/reply/trash emails Both tools use the sandboxed WIT interface with strict HTTP allowlists, credential injection, and rate limiting. OAuth config requests only the minimum scopes needed (calendar.events, gmail.modify, gmail.compose). Also adds the /add-tool skill for scaffolding future WASM or built-in tools with all boilerplate wired up. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Document WASM vs MCP server decision guide in CLAUDE.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add Google Drive WASM tool with full file and sharing management Supports 12 actions: list/get/download/upload/update files, create folders, delete/trash, share/list/remove permissions, and list shared drives. Works with both personal and organizational drives via the corpora parameter. Uses shared google_oauth_token for auth. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add Google Sheets, Docs, and Slides WASM tools Three new Google Workspace tools sharing google_oauth_token: - Sheets: create spreadsheets, read/write/append values, manage sheets, format cells - Docs: create/read/edit documents, text formatting, paragraphs, tables, lists - Slides: create/edit presentations, shapes, images, text formatting, thumbnails, templates Also adds tools-src/TOOLS.md tracking implementation status. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add Telegram WASM tool with direct MTProto over HTTPS Replace TDLight Docker dependency with pure-Rust grammers crates for direct encrypted MTProto communication to Telegram's web transport endpoints. No middleware, no Docker needed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Gitignore Cargo.lock files in WASM tools Library crates should not commit lock files. Consolidate per-tool .gitignore into a single one at wasm-tools/ level. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Flatten tools-src/wasm-tools/ into tools-src/ All tools are WASM, the extra nesting added no value. Moves all tool crates up one level, updates WIT paths and documentation references. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix Slack tool: add OAuth auth, URL encoding, pin wit-bindgen - Add OAuth 2.0 auth section to Slack capabilities with proper scopes and manual fallback instructions - URL-encode query parameters in GET requests to prevent injection - Remove dead SlackApiError struct - Pin wit-bindgen to =0.36 across all WASM tools for Rust 1.86 compat - Update add-tool template with pinned version Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
bkutasi
pushed a commit
to bkutasi/ironclaw
that referenced
this pull request
Mar 28, 2026
…tion (nearai#238) * feat: add extension registry with metadata catalog, CLI, and onboarding integration Adds a central registry that catalogs all 14 available extensions (10 tools, 4 channels) with their capabilities, auth requirements, and artifact references. The onboarding wizard now shows installable channels from the registry and offers tool installation as a new Step 7. - registry/ folder with per-extension JSON manifests and bundle definitions - src/registry/ module: manifest structs, catalog loader, installer - `ironclaw registry list|info|install|install-defaults` CLI commands - Setup wizard enhanced: channels from registry, new extensions step (8 steps) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(setup): resolve workspace errors for tool crates and channels-only onboarding Tool crates in tools-src/ and channels-src/ failed `cargo metadata` during onboard install because Cargo resolved them as part of the root workspace. Add `[workspace]` table to each standalone crate and extend the root `workspace.exclude` list so they build independently. Channels-only mode (`onboard --channels-only`) failed with "Secrets not configured" and "No database connection" because it skipped database and security setup. Add `reconnect_existing_db()` to establish the DB connection and load saved settings before running channel configuration. Also improve the tunnel "already configured" display to show full provider details (domain, mode, command) instead of just the provider name. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(registry): address PR review feedback on installer and catalog - Use manifest.name (not crate_name) for installed filenames so discovery, auth, and CLI commands all agree on the stem (nearai#1) - Add AlreadyInstalled error variant instead of misleading ExtensionNotFound (nearai#2) - Add DownloadFailed error variant with URL context instead of stuffing URLs into PathBuf (nearai#3) - Validate HTTP status with error_for_status() before reading response bytes in artifact downloads (nearai#4) - Switch build_wasm_component to tokio::process::Command with status() so build output streams to the terminal (nearai#6) - Find WASM artifact by crate_name specifically instead of picking the first .wasm file in the release directory (nearai#7) - Add is_file() guard in catalog loader to skip directories (nearai#8) - Detect ambiguous bare-name lookups when both tools/<name> and channels/<name> exist, with get_strict() returning an error (nearai#9) - Fix wizard step_extensions to check tool.name for installed detection, consistent with the new naming (nearai#11, nearai#12) - Fix redundant closures and map_or clippy warnings in changed files Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(setup): restore DB connection fields after settings reload reconnect_postgres() and reconnect_libsql() called Settings::from_db_map() which overwrote database_url / libsql_path / libsql_url set from env vars. Also use get_strict() in cmd_info to surface ambiguous bare-name errors. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style: fix clippy collapsible_if and print_literal warnings Collapse nested if-let chains and inline string literals in format macros to satisfy CI clippy lint checks (deny warnings). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(registry): prefer artifacts for install-defaults and improve dir lookup - InstallDefaults now defaults to downloading pre-built artifacts (matching `registry install` behavior), with --build flag for source builds. - find_registry_dir() walks up 3 ancestor levels from the exe and adds a CARGO_MANIFEST_DIR fallback, matching load_registry_catalog() logic. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
bkutasi
pushed a commit
to bkutasi/ironclaw
that referenced
this pull request
Mar 28, 2026
) * feat: add inbound attachment support to WASM channel system Add attachment record to WIT interface and implement inbound media parsing across all four channel implementations (Telegram, Slack, WhatsApp, Discord). Attachments flow from WASM channels through EmittedMessage to IncomingMessage with validation (size limits, MIME allowlist, count caps) at the host boundary. - Add `attachment` record to `emitted-message` in wit/channel.wit - Add `IncomingAttachment` struct to channel.rs and re-export - Add host-side validation (20MB total, 10 max, MIME allowlist) - Telegram: parse photo, document, audio, video, voice, sticker - Slack: parse file attachments with url_private - WhatsApp: parse image, audio, video, document with captions - Discord: backward-compatible empty attachments - Update FEATURE_PARITY.md section 7 - Add fixture-based tests per channel and host integration tests [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: integrate outbound attachment support and reconcile WIT types (nearai#409) Reconcile PR nearai#409's outbound attachment work with our inbound attachment support into a unified design: WIT type split: - `inbound-attachment` in channel-host: metadata-only (id, mime_type, filename, size_bytes, source_url, storage_key, extracted_text) - `attachment` in channel: raw bytes (filename, mime_type, data) on agent-response for outbound sending Outbound features (from PR nearai#409): - `on-broadcast` WIT export for proactive messages without prior inbound - Telegram: multipart sendPhoto/sendDocument with auto photo→document fallback for files >10MB - wrapper.rs: `call_on_broadcast`, `read_attachments` from disk, attachment params threaded through `call_on_respond` - HTTP tool: `save_to` param for binary downloads to /tmp/ (50MB limit, path traversal protection, SSRF-safe redirect following) - Message tool: allow /tmp/ paths for attachments alongside base_dir - Credential env var fallback in inject_channel_credentials Channel updates: - All 4 channels implement on_broadcast (Telegram full, others stub) - Telegram: polling_enabled config, adjusted poll timeout - Inbound attachment types renamed to InboundAttachment in all channels Tests: 1965 passing (9 new), 0 clippy warnings [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add audio transcription pipeline and extensible WIT attachment design Add host-side transcription middleware (OpenAI Whisper) that detects audio attachments with inline data on incoming messages and transcribes them automatically. Refactor WIT inbound-attachment to use extras-json and a store-attachment-data host function instead of typed fields, so future attachment properties (dimensions, codec, etc.) don't require WIT changes that invalidate all channel plugins. - Add src/transcription/ module: TranscriptionProvider trait, TranscriptionMiddleware, AudioFormat enum, OpenAI Whisper provider - Add src/config/transcription.rs: TRANSCRIPTION_ENABLED/MODEL/BASE_URL - Wire middleware into agent message loop via AgentDeps - WIT: replace data + duration-secs with extras-json + store-attachment-data - Host: parse extras-json for well-known keys, merge stored binary data - Telegram: download voice files via store-attachment-data, add duration to extras-json, add /file/bot to HTTP allowlist, voice-only placeholder - Add reqwest multipart feature for Whisper API uploads - 5 regression tests for transcription middleware Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: wire attachment processing into LLM pipeline with multimodal image support Attachments on incoming messages are now augmented into user text via XML tags before entering the turn system, and images with data are passed as multimodal content parts (base64 data URIs) to LLM providers. This enables audio transcripts, document text, and image content to reach the LLM without changes to ChatMessage serialization or provider interfaces. - Add src/agent/attachments.rs with augment_with_attachments() and 9 unit tests - Add ContentPart/ImageUrl types to llm::provider with OpenAI-compatible serde - Carry image_content_parts transiently on Turn (skipped in serialization) - Update nearai_chat and rig_adapter to serialize multimodal content - Add 3 e2e tests verifying attachments flow through the full agent loop Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: CI failures — formatting, version bumps, and Telegram voice test - Fix cargo fmt formatting in attachments.rs, nearai_chat.rs, rig_adapter.rs, e2e_attachments.rs - Bump channel registry versions 0.1.0 → 0.2.0 (discord, slack, telegram, whatsapp) to satisfy version-bump CI check - Fix Telegram test_extract_attachments_voice: add missing required `duration` field to voice fixture JSON Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: bump WIT channel version to 0.3.0, fix Telegram voice test, add pre-commit hook - Bump wit/channel.wit package version 0.2.0 → 0.3.0 (interface changed with store-attachment-data) - Update WIT_CHANNEL_VERSION constant and registry wit_version fields to match - Fix Telegram test_extract_attachments_voice: gate voice download behind #[cfg(target_arch = "wasm32")] so host functions aren't called in native tests, update assertions for generated filename and extras_json duration - Add @0.3.0 linker stubs in wit_compat.rs - Add .githooks/pre-commit hook that runs scripts/check-version-bumps.sh when WIT or extension sources are staged - Symlink commit-msg regression hook into .githooks/ [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: extract voice download from extract_attachments into handle_message Move download_voice_file + store_attachment_data calls out of extract_attachments into a separate download_and_store_voice function called from handle_message. This keeps extract_attachments as a pure data-mapping function with no host calls, making it fully testable in native unit tests without #[cfg(target_arch)] gates. [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address PR review comments — security, correctness, and code quality Security fixes: - Add path validation to read_attachments (restrict to /tmp/) preventing arbitrary file reads from compromised tools - Escape XML special characters in attachment filenames, MIME types, and extracted text to prevent prompt injection via tag spoofing - Percent-encode file_id in Telegram getFile URL to prevent query injection - Clone SecretString directly instead of expose_secret().to_string() Correctness fixes: - Fix store_attachment_data overwrite accounting: subtract old entry size before adding new to prevent inflated totals and false rejections - Use max(reported, stored_size) for attachment size accounting to prevent WASM channels from under-reporting size_bytes to bypass limits - Add application/octet-stream to MIME allowlist (channels default unknown types to this) Code quality: - Extract send_response helper in Telegram, deduplicating on_respond and on_broadcast - Rename misleading Discord test to test_parse_slash_command_interaction - Fix .githooks/commit-msg to use relative symlink (portable across machines) [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add tool_upgrade command + fix TOCTOU in save_to path validation Add `tool_upgrade` — a new extension management tool that automatically detects and reinstalls WASM extensions with outdated WIT versions. Preserves authentication secrets during upgrade. Supports upgrading a single extension by name or all installed WASM tools/channels at once. Fix TOCTOU in `validate_save_to_path`: validate the path *before* creating parent directories, so traversal paths like `/tmp/../../etc/` cannot cause filesystem mutations outside /tmp before being rejected. [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: unify WIT package version to 0.3.0 across tool.wit and all capabilities tool.wit and channel.wit share the `near:agent` package namespace, so they must declare the same version. Bumps tool.wit from 0.2.0 to 0.3.0 and updates all capabilities files and registry entries to match. Fixes `cargo component build` failure: "package identifier near:agent@0.2.0 does not match previous package name of near:agent@0.3.0" [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: move WIT file comments after package declaration WIT treats `//` comments before `package` as doc comments. When both tool.wit and channel.wit had header comments, the parser rejected them as "doc comments on multiple 'package' items". Move comments after the package declaration in both files. Also bumps tool registry versions to 0.2.0 to match the WIT 0.3.0 bump. [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: display extension versions in gateway Extensions tab Add version field to InstalledExtension and RegistryEntry types, pipe through the web API (ExtensionInfo, RegistryEntryInfo), and render as a badge in the gateway UI for both installed and available extensions. For installed WASM extensions, version is read from the capabilities file with a fallback to the registry entry when the local file has no version (old installations). Bump all extension Cargo.toml and registry JSON versions from 0.1.0 to 0.2.0 to keep them in sync. [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add document text extraction middleware for PDF, Office, and text files Extract text from document attachments (PDF, DOCX, PPTX, XLSX, RTF, plain text, code files) so the LLM can reason about uploaded documents. Uses pdf-extract for PDFs, zip+XML parsing for Office XML formats, and UTF-8 decode for text files. Wired into the agent loop after transcription middleware. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: download document files in Telegram channel for text extraction The DocumentExtractionMiddleware needs file bytes in the attachment `data` field, but only voice files were being downloaded. Document attachments (PDFs, DOCX, etc.) had empty `data` and a source_url with a credential placeholder that only works inside the WASM host's http_request. Add `download_and_store_documents()` that downloads non-voice, non-image, non-audio attachments via the existing two-step getFile→download flow and stores bytes via `store_attachment_data` for host-side extraction. Also rename `download_voice_file` → `download_telegram_file` since it's generic for any file_id. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: allow Office MIME types and increase file download limit for Telegram Two issues preventing document extraction from Telegram: 1. PPTX/DOCX/XLSX MIME types (application/vnd.*) were dropped by the WASM host attachment allowlist — add application/vnd., application/msword, and application/rtf prefixes. 2. Telegram file downloads over 10 MB failed with "Response body too large" — set max_response_bytes to 20 MB in Telegram capabilities. [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: report document extraction errors back to user instead of silently skipping - Bump max_response_bytes to 50 MB for Telegram file downloads - When document extraction fails (too large, download error, parse error), set extracted_text to a user-friendly error message instead of leaving it None. This ensures the LLM tells the user what went wrong. - On Telegram download failure, set extracted_text with the error so the user sees feedback even when the file never reaches the extraction middleware. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: store extracted document text in workspace memory for search/recall After document extraction succeeds, write the extracted text to workspace memory at `documents/{date}/{filename}`. This enables: - Full-text and semantic search over past uploaded documents - Cross-conversation recall ("what did that PDF say?") - Automatic chunking and embedding via the workspace pipeline Documents are stored with metadata header (uploader, channel, date, MIME type). Error messages (extraction failures) are not stored — only successful extractions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: CI failures — formatting, unused assignment warning - Run cargo fmt on document_extraction and agent_loop modules - Suppress unused_assignments warning on trace_llm_ref (used only behind #[cfg(feature = "libsql")]) [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address PR review comments — security, correctness, and code quality Security fixes: - Remove SSRF-prone download() from DocumentExtractionMiddleware (nearai#13) - Sanitize filenames in workspace path to prevent directory traversal (nearai#11) - Pre-check file size before reading in WASM wrapper to prevent OOM (nearai#2) - Percent-encode file_id in Telegram source URLs (nearai#7) Correctness fixes: - Clear image_content_parts on turn end to prevent memory leak (nearai#1) - Find first *successful* transcription instead of first overall (nearai#3) - Enforce data.len() size limit in document extraction (nearai#10) - Use UTF-8 safe truncation with char_indices() (nearai#12) Robustness & code quality: - Add 120s timeout to OpenAI Whisper HTTP client (nearai#5) - Trim trailing slash from Whisper base_url (nearai#6) - Allow ~/.ironclaw/ paths in WASM wrapper (nearai#8) - Return error from on_broadcast in Slack/Discord/WhatsApp (nearai#9) - Fix doc comment in HTTP tool (nearai#4) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: formatting — cargo fmt Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address latest PR review — doc comments, error messages, version bumps - Fix DocumentExtractionMiddleware doc comment (no longer downloads from source_url) - Fix error message: "no inline data" instead of "no download URL" - Log error + fallback instead of silent unwrap_or_default on Whisper HTTP client - Bump all capabilities.json versions from 0.1.0 to 0.2.0 to match Cargo.toml Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: remove unsupported profile: minimal from CI workflows [skip-regression-check] dtolnay/rust-toolchain@stable does not accept the 'profile' input (it was a parameter for the deprecated actions-rs/toolchain action). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: merge with latest main — resolve compilation errors and PR review nits - Add version: None to RegistryEntry/InstalledExtension test constructors - Fix MessageContent type mismatches in nearai_chat tests (String → MessageContent::Text) - Fix .contains() calls on MessageContent — use .as_text().unwrap() - Remove redundant trace_llm_ref = None assignment in test_rig - Check data size before clone in document extraction to avoid unnecessary allocation [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This was referenced Mar 31, 2026
7 tasks
henrypark133
added a commit
that referenced
this pull request
Apr 2, 2026
…, runtime assert in Signal, remove default fallback, warn on noop pairing codes Addresses zmanian's review: - #1: pairing_list_handler requires AuthenticatedUser - #2: OwnershipCache.evict_user() evicts all entries for a user on suspension - #3: debug_assert! for multi-thread runtime in Signal block_in_place - #9: Noop PairingStore warns when generating unredeemable codes - #10: cli/mcp.rs default fallback replaced with <unset>
henrypark133
added a commit
that referenced
this pull request
Apr 4, 2026
…B-backed pairing, and OwnershipCache (#1898) * feat(ownership): add OwnerId, Identity, UserRole, can_act_on types Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(ownership): private OwnerId field, ResourceScope serde derives, fix doc comment Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * refactor(tenant): replace SystemScope::db() escape hatch with typed workspace_for_user(), fix stale variable names - Add SystemScope::workspace_for_user() that wraps Workspace::new_with_db - Remove SystemScope::db() which exposed the raw Arc<dyn Database> - Update 3 callers (routine_engine.rs x2, heartbeat.rs x1) to use the new method - Fix stale comment: "admin context" -> "system context" in SystemScope - Rename `admin` bindings to `system` in agent_loop.rs for clarity Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(tenant): rename stale admin binding to system_store in heartbeat.rs * refactor(tenant): TenantScope/TenantCtx carry Identity, add with_identity() constructor and bridge new() - TenantScope: replace `user_id: String` field with `identity: Identity`; add `with_identity()` preferred constructor; keep `new(user_id, db)` as Member-role bridge; add `identity()` accessor; all internal method bodies use `identity.owner_id.as_str()` in place of `&self.user_id` - TenantCtx: replace `user_id: String` field with `identity: Identity`; update constructor signature; add `identity()` accessor; `user_id()` delegates to `identity.owner_id.as_str()`; cost/rate methods updated accordingly - agent_loop: split `tenant_ctx(&str)` into bridge + new `tenant_ctx_with_identity(Identity)` which holds the full body; bridge delegates to avoid duplication Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * feat(db): add V16 tool scope, V17 channel_identities, V18 pairing_requests migrations - PostgreSQL: V16__tool_scope.sql adds scope column to wasm_tools/dynamic_tools - PostgreSQL: V17__channel_identities.sql creates channel identity resolution table - PostgreSQL: V18__pairing_requests.sql creates pairing request table replacing file-based store - libSQL SCHEMA: adds scope column to wasm_tools/dynamic_tools, channel_identities, pairing_requests tables - libSQL INCREMENTAL_MIGRATIONS: versions 17-19 for existing databases - IDEMPOTENT_ADD_COLUMN_MIGRATIONS: handles fresh-install/upgrade dual path for scope columns - Runner updated to check ALL idempotent columns per version before skipping SQL - Test: test_ownership_model_tables_created verifies all new tables/columns exist after migrations Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(db): use correct RFC3339 timestamp default in libSQL, document version sequence offset Replace datetime('now') with strftime('%Y-%m-%dT%H:%M:%fZ', 'now') in the channel_identities and pairing_requests table definitions (both in SCHEMA and INCREMENTAL_MIGRATIONS) to match the project-standard RFC 3339 timestamp format with millisecond precision. Also add a comment clarifying that libSQL incremental migration version numbers are independent from PostgreSQL VN migration numbers. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * feat(ownership): bootstrap_ownership(), migrate_default_owner, V19 FK migration, replace hardcoded 'default' user IDs - Add V19__ownership_fk.sql (programmatic-only, not in auto-migration sweep) - Add `migrate_default_owner` to Database trait + both PgBackend and LibSqlBackend - Add `get_or_create_user` default method to UserStore trait - Add `bootstrap_ownership()` to app.rs, called in init_database() after connect_with_handles - Replace hardcoded "default" owner_id in cli/config.rs, cli/mcp.rs, cli/mod.rs, orchestrator/mod.rs - Add TODO(ownership) comments in llm/session.rs and tools/mcp/client.rs for deferred constructors Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(ownership): atomic get_or_create_user, transactional migrate_default_owner, V19 FK inline constant, fix remaining 'default' user IDs - Delete migrations/V19__ownership_fk.sql so refinery no longer auto-applies FK constraints before bootstrap_ownership runs; add OWNERSHIP_FK_SQL constant with TODO for future programmatic application - Remove racy SELECT+INSERT default in UserStore::get_or_create_user; both PostgreSQL (ON CONFLICT DO NOTHING) and libSQL (INSERT OR IGNORE) now use atomic upserts - Wrap migrate_default_owner in explicit transactions on both backends for atomicity - Make bootstrap_ownership failure fatal (propagate error instead of warn-and-continue) - Fix mcp auth/test --user: change from default_value="default" to Option<String> resolved from configured owner_id - Replace hardcoded "default" user IDs in channels/wasm/setup.rs with config.owner_id - Replace "default" sentinel in OrchestratorState test helper with "<unset>" to make the test-only nature explicit Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(ownership): remove default user_id from create_job(), change sentinel strings to <unset> - Gate ContextManager::create_job() behind #[cfg(test)]; production code must use create_job_for_user() with an explicit user_id to prevent DB rows with user_id = 'default' being silently created on the production write path. - Change the placeholder user_id in McpClient::new(), new_with_name(), and new_with_config() from "default" to "<unset>" so accidental secrets/settings lookups surface immediately rather than silently touching the wrong DB partition. - Same sentinel change for SessionManager::new() and new_async() in session.rs; these are overwritten by attach_store() at startup with the real owner_id. - Update tests that asserted the old "default" sentinel to expect "<unset>", and switch test_list_jobs_tool / test_job_status_tool to create_job_for_user("default") to keep ownership alignment with JobContext::default(). Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * feat(db): add ChannelPairingStore sub-trait with resolve_channel_identity, upsert/approve pairing, PostgreSQL + libSQL implementations Adds PairingRequestRecord, ChannelPairingStore trait (5 methods), and generate_pairing_code() to src/db/mod.rs; implements for PgBackend in postgres.rs and LibSqlBackend in libsql/pairing.rs; wires ChannelPairingStore into the Database supertrait bound; all 6 libSQL unit tests pass. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(db): atomic libSQL approve_pairing with BEGIN IMMEDIATE, add case-insensitive/expired/double-approve tests Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * feat(ownership): add OwnershipCache for zero-DB-read identity resolution on warm path Converts src/ownership.rs to src/ownership/ module directory and adds src/ownership/cache.rs with a write-through in-process cache mapping (channel, external_id) -> Identity. Wired as Arc<OwnershipCache> on AppComponents for Task 8 pairing integration. All 7 cache unit tests pass. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * test(e2e): add ownership model E2E tests and extend pairing tests for DB-backed store Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(e2e): remove unused asyncio import, add fallback assertion in test_pairing_response_structure Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * test(tenant): unit tests for TenantScope::with_identity and AdminScope construction Adds 5 focused unit tests verifying TenantScope::with_identity stores the full Identity (owner_id + role), TenantScope::new creates a Member-role identity, and AdminScope::new returns Some for Admin and None for Member. Uses LibSqlBackend::new_memory() as the test DB stub. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(ownership): recover from RwLock poison instead of expect() in OwnershipCache Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * test(ownership): integration tests for bootstrap, tenant isolation, and ChannelPairingStore Adds tests/ownership_integration.rs covering migrate_default_owner idempotency, TenantScope per-user setting isolation (including Admin role bypass check), and the full ChannelPairingStore lifecycle (upsert, approve, remove, multi-channel isolation). Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(test): remove duplicate pairing tests and flaky random-code assertion from integration suite Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * feat(pairing): rewrite PairingStore to DB-backed async with OwnershipCache Replaces the file-based pairing store (~/.ironclaw/*-pairing.json, *-allowFrom.json) with a DB-backed async implementation that delegates to ChannelPairingStore and writes through to OwnershipCache on reads. - PairingStore::new(db, cache) uses the DB; new_noop() for test/no-DB - resolve_identity() cache-first lookup via OwnershipCache - approve(code, owner_id) removes channel arg (DB looks up by code) - All WASM host functions updated: pairing_upsert_request uses block_in_place, pairing-is-allowed renamed to pairing-resolve-identity returning Option<String>, pairing-read-allow-from deprecated (returns empty list) - Signal channel receives PairingStore via new(config, db) constructor - Web gateway pairing handlers read from state.store (DB) directly - extensions.rs derive_activation_status drops PairingStore dependency; derives status from extension.active and owner_binding flag instead - All test call sites updated to use new_noop() Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(pairing): add missing pairing_store field to all GatewayState initializers, fix disk-full post-edit compile Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * feat(channels): remove owner_id from IncomingMessage, user_id is the canonical resolved OwnerId `owner_id` on `IncomingMessage` was always a duplicate of `user_id` — both fields held the same value at every call site. Remove the field and `with_owner_id()` builder, update the four WASM-wrapper and HTTP test assertions to use `user_id`, and drop the redundant struct literal field in the routine_engine test helper. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(channels): remove stale owner_id param from make_message test helper Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * test(e2e): add browser/Playwright tests for ownership model — auth screen, chat UI, owner login Adds five Playwright-based browser tests to the ownership model E2E suite verifying the web UI experience: authenticated owner sees chat input, unauthenticated browser sees auth screen, owner can send a message and receive a response, settings tab renders without errors, and basic page structure is correct after login. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * feat(settings): migrate channel credentials from plaintext settings to encrypted secrets store Moves nearai.session_token from the plaintext DB settings table to the AES-256-GCM encrypted secrets store (key: nearai_session_token). - SessionManager gains an `attach_secrets()` method that wires in the secrets store; `save_session` writes to it when available and `load_session_from_secrets` is called preferentially over settings - `migrate_session_credential()` runs idempotently on each startup in `init_secrets()`, reading the JSON session from settings, writing it to secrets, then deleting the plaintext copy - Wizard's `persist_session_to_db` now writes to secrets first, falling back to plaintext settings only when secrets store is unavailable - Plaintext settings path is preserved as fallback for installs without a secrets store (no master key configured) Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(settings): settings fallback only when no secrets store, verify decryption before deleting plaintext Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(ownership): ROLLBACK in libSQL migrate_default_owner, shared OwnershipCache across channels, add dynamic_tools to migration, fix doc comment - libSQL migrate_default_owner: wrap UPDATE loop in async closure + match to emit ROLLBACK on any mid-transaction failure (mirroring approve_pairing pattern) - Both backends: add dynamic_tools to the migrate_default_owner table list so agent-built tools are migrated on first pairing - setup_wasm_channels: accept Arc<OwnershipCache> parameter instead of allocating a fresh cache, share the AppComponents cache - SignalChannel::new: accept Arc<OwnershipCache> parameter and pass it to PairingStore instead of allocating a new cache - PairingStore: fix module-level and struct-level doc comments to accurately describe lazy cache population after approve() Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(web): use can_act_on for authorization in job/routine handlers instead of raw string comparisons Replace 12 raw `user_id != user.user_id` / `user_id == user.user_id` string comparisons in jobs.rs and 4 in routines.rs with calls through the canonical `can_act_on` function from `crate::ownership`, which is the spec-mandated authorization mechanism. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * chore: include remaining modified files in ownership model branch * fix: add pairing_store field to test GatewayState initializers, update PairingStore API calls in integration tests Add missing `pairing_store: None` to all GatewayState struct initializers in test files. Migrate old file-based PairingStore API calls (PairingStore::new(), PairingStore::with_base_dir()) to the new DB-backed API (PairingStore::new_noop()). Rewrite pairing_integration.rs to use LibSqlBackend with the new async DB-backed PairingStore API. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * chore: cargo fmt * fix(pairing): truly no-op PairingStore noop mode, ensure owner user in CLI, fix signal safety comments - PairingStore::upsert_request now returns a dummy record in noop mode instead of erroring, and approve silently succeeds (matching the doc promise of "writes are silently discarded"). - PairingStore::approve now accepts a channel parameter, matching the updated DB trait signature and propagated to all call sites (CLI, web server, tests). - CLI run_pairing_command ensures the owner user row exists before approval to satisfy the FK constraint on channel_identities.owner_id. - Signal channel block_in_place safety comments corrected from "WASM channel callbacks" to "Signal channel message processing". Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(pairing): thread channel through approve_pairing, add created flag, retry on code collision, remove redundant indexes Addresses PR review comments: - approve_pairing validates code belongs to the given channel - PairingRequestRecord.created replaces timing heuristic - upsert retries on UNIQUE violation (up to 3 attempts) - redundant indexes removed (UNIQUE creates implicit index) Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(ownership): migrate api_tokens, serialize PG approvals, propagate resolved owner_id Addresses PR review P1/P2 regressions: - api_tokens included in migrate_default_owner (both backends) - PostgreSQL approve_pairing uses FOR UPDATE to prevent concurrent approvals - Signal resolve_sender_identity returns owner_id, set as IncomingMessage.user_id with raw phone number preserved as sender_id for reply routing - Feishu uses resolved owner_id from pairing_resolve_identity in emitted message - PairingStore noop mode logs warning when pairing admission is impossible [skip-regression-check] Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(pr-review): sanitize DB errors in pairing handlers, fix doc comments, add TODO for derive_activation_status - Pairing list/approve handlers no longer leak DB error details to clients - NotFound errors return user-friendly 'Invalid or expired pairing code' message - Module doc in pairing/store.rs corrected (remove -> evict, no insert method) - wit_compat.rs stub comment corrected to match actual Val shape - TODO added for derive_activation_status has_paired approximation * fix(pr-review): propagate libSQL query errors in approve_pairing, round-trip validate session credential migration, fix test doc comment - libSQL approve_pairing: .ok().flatten() replaced with .map_err() to propagate DB errors - migrate_session_credential: round-trip compares decrypted secret against plaintext before deleting - ownership_integration.rs: doc comment corrected to match actual test coverage * fix(pairing): store meta, wrap upserts in transactions, case-insensitive role/channel, log Signal DB errors, use auth role in handlers - Store meta JSONB/TEXT column in pairing_requests (PG migration V18, libSQL schema + incremental migration 19) - Wrap upsert_pairing_request in transactions (PG: client.transaction(), libSQL: BEGIN IMMEDIATE/COMMIT/ROLLBACK) - Case-insensitive role parsing: eq_ignore_ascii_case("admin") in both backends - Case-insensitive channel matching in approve_pairing: LOWER(channel) = LOWER($2) - Log DB errors in Signal resolve_sender_identity instead of silently discarding - Use auth role from UserIdentity in web handlers (jobs.rs, routines.rs) via identity_from_auth helper - Fix variable shadowing: rename `let channel` to `let req_channel` in libsql approve_pairing Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(security): add auth to pairing list, cache eviction on deactivate, runtime assert in Signal, remove default fallback, warn on noop pairing codes Addresses zmanian's review: - #1: pairing_list_handler requires AuthenticatedUser - #2: OwnershipCache.evict_user() evicts all entries for a user on suspension - #3: debug_assert! for multi-thread runtime in Signal block_in_place - #9: Noop PairingStore warns when generating unredeemable codes - #10: cli/mcp.rs default fallback replaced with <unset> * fix(pairing): consistent LOWER() channel matching in resolve_channel_identity, fix wizard doc comment, fix E2E test assertion for ActionResponse convention * fix(pairing): apply LOWER() consistently across all ChannelPairingStore queries (upsert, list_pending, remove) All channel matching now uses LOWER() in both PostgreSQL and libSQL backends: - upsert_pairing_request: WHERE LOWER(channel) = LOWER($1) - list_pending_pairings: WHERE LOWER(channel) = LOWER($1) - remove_channel_identity: WHERE LOWER(channel) = LOWER($1) Previously only resolve_channel_identity and approve_pairing used LOWER(), causing inconsistent matching when channel names differed by case. * fix(pairing): unify code challenge flow and harden web pairing * test: harden pairing review follow-ups * fix: guard wasm pairing callbacks by runtime flavor * fix(pairing): normalize channel keys and serialize pg upserts * chore(web): clean up ownership review follow-ups * Preserve WASM pairing allowlist compatibility --------- Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
serrrfirat
pushed a commit
that referenced
this pull request
Apr 5, 2026
…B-backed pairing, and OwnershipCache (#1898) * feat(ownership): add OwnerId, Identity, UserRole, can_act_on types Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(ownership): private OwnerId field, ResourceScope serde derives, fix doc comment Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * refactor(tenant): replace SystemScope::db() escape hatch with typed workspace_for_user(), fix stale variable names - Add SystemScope::workspace_for_user() that wraps Workspace::new_with_db - Remove SystemScope::db() which exposed the raw Arc<dyn Database> - Update 3 callers (routine_engine.rs x2, heartbeat.rs x1) to use the new method - Fix stale comment: "admin context" -> "system context" in SystemScope - Rename `admin` bindings to `system` in agent_loop.rs for clarity Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(tenant): rename stale admin binding to system_store in heartbeat.rs * refactor(tenant): TenantScope/TenantCtx carry Identity, add with_identity() constructor and bridge new() - TenantScope: replace `user_id: String` field with `identity: Identity`; add `with_identity()` preferred constructor; keep `new(user_id, db)` as Member-role bridge; add `identity()` accessor; all internal method bodies use `identity.owner_id.as_str()` in place of `&self.user_id` - TenantCtx: replace `user_id: String` field with `identity: Identity`; update constructor signature; add `identity()` accessor; `user_id()` delegates to `identity.owner_id.as_str()`; cost/rate methods updated accordingly - agent_loop: split `tenant_ctx(&str)` into bridge + new `tenant_ctx_with_identity(Identity)` which holds the full body; bridge delegates to avoid duplication Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * feat(db): add V16 tool scope, V17 channel_identities, V18 pairing_requests migrations - PostgreSQL: V16__tool_scope.sql adds scope column to wasm_tools/dynamic_tools - PostgreSQL: V17__channel_identities.sql creates channel identity resolution table - PostgreSQL: V18__pairing_requests.sql creates pairing request table replacing file-based store - libSQL SCHEMA: adds scope column to wasm_tools/dynamic_tools, channel_identities, pairing_requests tables - libSQL INCREMENTAL_MIGRATIONS: versions 17-19 for existing databases - IDEMPOTENT_ADD_COLUMN_MIGRATIONS: handles fresh-install/upgrade dual path for scope columns - Runner updated to check ALL idempotent columns per version before skipping SQL - Test: test_ownership_model_tables_created verifies all new tables/columns exist after migrations Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(db): use correct RFC3339 timestamp default in libSQL, document version sequence offset Replace datetime('now') with strftime('%Y-%m-%dT%H:%M:%fZ', 'now') in the channel_identities and pairing_requests table definitions (both in SCHEMA and INCREMENTAL_MIGRATIONS) to match the project-standard RFC 3339 timestamp format with millisecond precision. Also add a comment clarifying that libSQL incremental migration version numbers are independent from PostgreSQL VN migration numbers. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * feat(ownership): bootstrap_ownership(), migrate_default_owner, V19 FK migration, replace hardcoded 'default' user IDs - Add V19__ownership_fk.sql (programmatic-only, not in auto-migration sweep) - Add `migrate_default_owner` to Database trait + both PgBackend and LibSqlBackend - Add `get_or_create_user` default method to UserStore trait - Add `bootstrap_ownership()` to app.rs, called in init_database() after connect_with_handles - Replace hardcoded "default" owner_id in cli/config.rs, cli/mcp.rs, cli/mod.rs, orchestrator/mod.rs - Add TODO(ownership) comments in llm/session.rs and tools/mcp/client.rs for deferred constructors Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(ownership): atomic get_or_create_user, transactional migrate_default_owner, V19 FK inline constant, fix remaining 'default' user IDs - Delete migrations/V19__ownership_fk.sql so refinery no longer auto-applies FK constraints before bootstrap_ownership runs; add OWNERSHIP_FK_SQL constant with TODO for future programmatic application - Remove racy SELECT+INSERT default in UserStore::get_or_create_user; both PostgreSQL (ON CONFLICT DO NOTHING) and libSQL (INSERT OR IGNORE) now use atomic upserts - Wrap migrate_default_owner in explicit transactions on both backends for atomicity - Make bootstrap_ownership failure fatal (propagate error instead of warn-and-continue) - Fix mcp auth/test --user: change from default_value="default" to Option<String> resolved from configured owner_id - Replace hardcoded "default" user IDs in channels/wasm/setup.rs with config.owner_id - Replace "default" sentinel in OrchestratorState test helper with "<unset>" to make the test-only nature explicit Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(ownership): remove default user_id from create_job(), change sentinel strings to <unset> - Gate ContextManager::create_job() behind #[cfg(test)]; production code must use create_job_for_user() with an explicit user_id to prevent DB rows with user_id = 'default' being silently created on the production write path. - Change the placeholder user_id in McpClient::new(), new_with_name(), and new_with_config() from "default" to "<unset>" so accidental secrets/settings lookups surface immediately rather than silently touching the wrong DB partition. - Same sentinel change for SessionManager::new() and new_async() in session.rs; these are overwritten by attach_store() at startup with the real owner_id. - Update tests that asserted the old "default" sentinel to expect "<unset>", and switch test_list_jobs_tool / test_job_status_tool to create_job_for_user("default") to keep ownership alignment with JobContext::default(). Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * feat(db): add ChannelPairingStore sub-trait with resolve_channel_identity, upsert/approve pairing, PostgreSQL + libSQL implementations Adds PairingRequestRecord, ChannelPairingStore trait (5 methods), and generate_pairing_code() to src/db/mod.rs; implements for PgBackend in postgres.rs and LibSqlBackend in libsql/pairing.rs; wires ChannelPairingStore into the Database supertrait bound; all 6 libSQL unit tests pass. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(db): atomic libSQL approve_pairing with BEGIN IMMEDIATE, add case-insensitive/expired/double-approve tests Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * feat(ownership): add OwnershipCache for zero-DB-read identity resolution on warm path Converts src/ownership.rs to src/ownership/ module directory and adds src/ownership/cache.rs with a write-through in-process cache mapping (channel, external_id) -> Identity. Wired as Arc<OwnershipCache> on AppComponents for Task 8 pairing integration. All 7 cache unit tests pass. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * test(e2e): add ownership model E2E tests and extend pairing tests for DB-backed store Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(e2e): remove unused asyncio import, add fallback assertion in test_pairing_response_structure Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * test(tenant): unit tests for TenantScope::with_identity and AdminScope construction Adds 5 focused unit tests verifying TenantScope::with_identity stores the full Identity (owner_id + role), TenantScope::new creates a Member-role identity, and AdminScope::new returns Some for Admin and None for Member. Uses LibSqlBackend::new_memory() as the test DB stub. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(ownership): recover from RwLock poison instead of expect() in OwnershipCache Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * test(ownership): integration tests for bootstrap, tenant isolation, and ChannelPairingStore Adds tests/ownership_integration.rs covering migrate_default_owner idempotency, TenantScope per-user setting isolation (including Admin role bypass check), and the full ChannelPairingStore lifecycle (upsert, approve, remove, multi-channel isolation). Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(test): remove duplicate pairing tests and flaky random-code assertion from integration suite Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * feat(pairing): rewrite PairingStore to DB-backed async with OwnershipCache Replaces the file-based pairing store (~/.ironclaw/*-pairing.json, *-allowFrom.json) with a DB-backed async implementation that delegates to ChannelPairingStore and writes through to OwnershipCache on reads. - PairingStore::new(db, cache) uses the DB; new_noop() for test/no-DB - resolve_identity() cache-first lookup via OwnershipCache - approve(code, owner_id) removes channel arg (DB looks up by code) - All WASM host functions updated: pairing_upsert_request uses block_in_place, pairing-is-allowed renamed to pairing-resolve-identity returning Option<String>, pairing-read-allow-from deprecated (returns empty list) - Signal channel receives PairingStore via new(config, db) constructor - Web gateway pairing handlers read from state.store (DB) directly - extensions.rs derive_activation_status drops PairingStore dependency; derives status from extension.active and owner_binding flag instead - All test call sites updated to use new_noop() Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(pairing): add missing pairing_store field to all GatewayState initializers, fix disk-full post-edit compile Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * feat(channels): remove owner_id from IncomingMessage, user_id is the canonical resolved OwnerId `owner_id` on `IncomingMessage` was always a duplicate of `user_id` — both fields held the same value at every call site. Remove the field and `with_owner_id()` builder, update the four WASM-wrapper and HTTP test assertions to use `user_id`, and drop the redundant struct literal field in the routine_engine test helper. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(channels): remove stale owner_id param from make_message test helper Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * test(e2e): add browser/Playwright tests for ownership model — auth screen, chat UI, owner login Adds five Playwright-based browser tests to the ownership model E2E suite verifying the web UI experience: authenticated owner sees chat input, unauthenticated browser sees auth screen, owner can send a message and receive a response, settings tab renders without errors, and basic page structure is correct after login. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * feat(settings): migrate channel credentials from plaintext settings to encrypted secrets store Moves nearai.session_token from the plaintext DB settings table to the AES-256-GCM encrypted secrets store (key: nearai_session_token). - SessionManager gains an `attach_secrets()` method that wires in the secrets store; `save_session` writes to it when available and `load_session_from_secrets` is called preferentially over settings - `migrate_session_credential()` runs idempotently on each startup in `init_secrets()`, reading the JSON session from settings, writing it to secrets, then deleting the plaintext copy - Wizard's `persist_session_to_db` now writes to secrets first, falling back to plaintext settings only when secrets store is unavailable - Plaintext settings path is preserved as fallback for installs without a secrets store (no master key configured) Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(settings): settings fallback only when no secrets store, verify decryption before deleting plaintext Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(ownership): ROLLBACK in libSQL migrate_default_owner, shared OwnershipCache across channels, add dynamic_tools to migration, fix doc comment - libSQL migrate_default_owner: wrap UPDATE loop in async closure + match to emit ROLLBACK on any mid-transaction failure (mirroring approve_pairing pattern) - Both backends: add dynamic_tools to the migrate_default_owner table list so agent-built tools are migrated on first pairing - setup_wasm_channels: accept Arc<OwnershipCache> parameter instead of allocating a fresh cache, share the AppComponents cache - SignalChannel::new: accept Arc<OwnershipCache> parameter and pass it to PairingStore instead of allocating a new cache - PairingStore: fix module-level and struct-level doc comments to accurately describe lazy cache population after approve() Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(web): use can_act_on for authorization in job/routine handlers instead of raw string comparisons Replace 12 raw `user_id != user.user_id` / `user_id == user.user_id` string comparisons in jobs.rs and 4 in routines.rs with calls through the canonical `can_act_on` function from `crate::ownership`, which is the spec-mandated authorization mechanism. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * chore: include remaining modified files in ownership model branch * fix: add pairing_store field to test GatewayState initializers, update PairingStore API calls in integration tests Add missing `pairing_store: None` to all GatewayState struct initializers in test files. Migrate old file-based PairingStore API calls (PairingStore::new(), PairingStore::with_base_dir()) to the new DB-backed API (PairingStore::new_noop()). Rewrite pairing_integration.rs to use LibSqlBackend with the new async DB-backed PairingStore API. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * chore: cargo fmt * fix(pairing): truly no-op PairingStore noop mode, ensure owner user in CLI, fix signal safety comments - PairingStore::upsert_request now returns a dummy record in noop mode instead of erroring, and approve silently succeeds (matching the doc promise of "writes are silently discarded"). - PairingStore::approve now accepts a channel parameter, matching the updated DB trait signature and propagated to all call sites (CLI, web server, tests). - CLI run_pairing_command ensures the owner user row exists before approval to satisfy the FK constraint on channel_identities.owner_id. - Signal channel block_in_place safety comments corrected from "WASM channel callbacks" to "Signal channel message processing". Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(pairing): thread channel through approve_pairing, add created flag, retry on code collision, remove redundant indexes Addresses PR review comments: - approve_pairing validates code belongs to the given channel - PairingRequestRecord.created replaces timing heuristic - upsert retries on UNIQUE violation (up to 3 attempts) - redundant indexes removed (UNIQUE creates implicit index) Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(ownership): migrate api_tokens, serialize PG approvals, propagate resolved owner_id Addresses PR review P1/P2 regressions: - api_tokens included in migrate_default_owner (both backends) - PostgreSQL approve_pairing uses FOR UPDATE to prevent concurrent approvals - Signal resolve_sender_identity returns owner_id, set as IncomingMessage.user_id with raw phone number preserved as sender_id for reply routing - Feishu uses resolved owner_id from pairing_resolve_identity in emitted message - PairingStore noop mode logs warning when pairing admission is impossible [skip-regression-check] Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(pr-review): sanitize DB errors in pairing handlers, fix doc comments, add TODO for derive_activation_status - Pairing list/approve handlers no longer leak DB error details to clients - NotFound errors return user-friendly 'Invalid or expired pairing code' message - Module doc in pairing/store.rs corrected (remove -> evict, no insert method) - wit_compat.rs stub comment corrected to match actual Val shape - TODO added for derive_activation_status has_paired approximation * fix(pr-review): propagate libSQL query errors in approve_pairing, round-trip validate session credential migration, fix test doc comment - libSQL approve_pairing: .ok().flatten() replaced with .map_err() to propagate DB errors - migrate_session_credential: round-trip compares decrypted secret against plaintext before deleting - ownership_integration.rs: doc comment corrected to match actual test coverage * fix(pairing): store meta, wrap upserts in transactions, case-insensitive role/channel, log Signal DB errors, use auth role in handlers - Store meta JSONB/TEXT column in pairing_requests (PG migration V18, libSQL schema + incremental migration 19) - Wrap upsert_pairing_request in transactions (PG: client.transaction(), libSQL: BEGIN IMMEDIATE/COMMIT/ROLLBACK) - Case-insensitive role parsing: eq_ignore_ascii_case("admin") in both backends - Case-insensitive channel matching in approve_pairing: LOWER(channel) = LOWER($2) - Log DB errors in Signal resolve_sender_identity instead of silently discarding - Use auth role from UserIdentity in web handlers (jobs.rs, routines.rs) via identity_from_auth helper - Fix variable shadowing: rename `let channel` to `let req_channel` in libsql approve_pairing Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(security): add auth to pairing list, cache eviction on deactivate, runtime assert in Signal, remove default fallback, warn on noop pairing codes Addresses zmanian's review: - #1: pairing_list_handler requires AuthenticatedUser - #2: OwnershipCache.evict_user() evicts all entries for a user on suspension - #3: debug_assert! for multi-thread runtime in Signal block_in_place - #9: Noop PairingStore warns when generating unredeemable codes - #10: cli/mcp.rs default fallback replaced with <unset> * fix(pairing): consistent LOWER() channel matching in resolve_channel_identity, fix wizard doc comment, fix E2E test assertion for ActionResponse convention * fix(pairing): apply LOWER() consistently across all ChannelPairingStore queries (upsert, list_pending, remove) All channel matching now uses LOWER() in both PostgreSQL and libSQL backends: - upsert_pairing_request: WHERE LOWER(channel) = LOWER($1) - list_pending_pairings: WHERE LOWER(channel) = LOWER($1) - remove_channel_identity: WHERE LOWER(channel) = LOWER($1) Previously only resolve_channel_identity and approve_pairing used LOWER(), causing inconsistent matching when channel names differed by case. * fix(pairing): unify code challenge flow and harden web pairing * test: harden pairing review follow-ups * fix: guard wasm pairing callbacks by runtime flavor * fix(pairing): normalize channel keys and serialize pg upserts * chore(web): clean up ownership review follow-ups * Preserve WASM pairing allowlist compatibility --------- Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
drchirag1991
pushed a commit
to drchirag1991/ironclaw
that referenced
this pull request
Apr 8, 2026
) * feat: add inbound attachment support to WASM channel system Add attachment record to WIT interface and implement inbound media parsing across all four channel implementations (Telegram, Slack, WhatsApp, Discord). Attachments flow from WASM channels through EmittedMessage to IncomingMessage with validation (size limits, MIME allowlist, count caps) at the host boundary. - Add `attachment` record to `emitted-message` in wit/channel.wit - Add `IncomingAttachment` struct to channel.rs and re-export - Add host-side validation (20MB total, 10 max, MIME allowlist) - Telegram: parse photo, document, audio, video, voice, sticker - Slack: parse file attachments with url_private - WhatsApp: parse image, audio, video, document with captions - Discord: backward-compatible empty attachments - Update FEATURE_PARITY.md section 7 - Add fixture-based tests per channel and host integration tests [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: integrate outbound attachment support and reconcile WIT types (nearai#409) Reconcile PR nearai#409's outbound attachment work with our inbound attachment support into a unified design: WIT type split: - `inbound-attachment` in channel-host: metadata-only (id, mime_type, filename, size_bytes, source_url, storage_key, extracted_text) - `attachment` in channel: raw bytes (filename, mime_type, data) on agent-response for outbound sending Outbound features (from PR nearai#409): - `on-broadcast` WIT export for proactive messages without prior inbound - Telegram: multipart sendPhoto/sendDocument with auto photo→document fallback for files >10MB - wrapper.rs: `call_on_broadcast`, `read_attachments` from disk, attachment params threaded through `call_on_respond` - HTTP tool: `save_to` param for binary downloads to /tmp/ (50MB limit, path traversal protection, SSRF-safe redirect following) - Message tool: allow /tmp/ paths for attachments alongside base_dir - Credential env var fallback in inject_channel_credentials Channel updates: - All 4 channels implement on_broadcast (Telegram full, others stub) - Telegram: polling_enabled config, adjusted poll timeout - Inbound attachment types renamed to InboundAttachment in all channels Tests: 1965 passing (9 new), 0 clippy warnings [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add audio transcription pipeline and extensible WIT attachment design Add host-side transcription middleware (OpenAI Whisper) that detects audio attachments with inline data on incoming messages and transcribes them automatically. Refactor WIT inbound-attachment to use extras-json and a store-attachment-data host function instead of typed fields, so future attachment properties (dimensions, codec, etc.) don't require WIT changes that invalidate all channel plugins. - Add src/transcription/ module: TranscriptionProvider trait, TranscriptionMiddleware, AudioFormat enum, OpenAI Whisper provider - Add src/config/transcription.rs: TRANSCRIPTION_ENABLED/MODEL/BASE_URL - Wire middleware into agent message loop via AgentDeps - WIT: replace data + duration-secs with extras-json + store-attachment-data - Host: parse extras-json for well-known keys, merge stored binary data - Telegram: download voice files via store-attachment-data, add duration to extras-json, add /file/bot to HTTP allowlist, voice-only placeholder - Add reqwest multipart feature for Whisper API uploads - 5 regression tests for transcription middleware Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: wire attachment processing into LLM pipeline with multimodal image support Attachments on incoming messages are now augmented into user text via XML tags before entering the turn system, and images with data are passed as multimodal content parts (base64 data URIs) to LLM providers. This enables audio transcripts, document text, and image content to reach the LLM without changes to ChatMessage serialization or provider interfaces. - Add src/agent/attachments.rs with augment_with_attachments() and 9 unit tests - Add ContentPart/ImageUrl types to llm::provider with OpenAI-compatible serde - Carry image_content_parts transiently on Turn (skipped in serialization) - Update nearai_chat and rig_adapter to serialize multimodal content - Add 3 e2e tests verifying attachments flow through the full agent loop Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: CI failures — formatting, version bumps, and Telegram voice test - Fix cargo fmt formatting in attachments.rs, nearai_chat.rs, rig_adapter.rs, e2e_attachments.rs - Bump channel registry versions 0.1.0 → 0.2.0 (discord, slack, telegram, whatsapp) to satisfy version-bump CI check - Fix Telegram test_extract_attachments_voice: add missing required `duration` field to voice fixture JSON Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: bump WIT channel version to 0.3.0, fix Telegram voice test, add pre-commit hook - Bump wit/channel.wit package version 0.2.0 → 0.3.0 (interface changed with store-attachment-data) - Update WIT_CHANNEL_VERSION constant and registry wit_version fields to match - Fix Telegram test_extract_attachments_voice: gate voice download behind #[cfg(target_arch = "wasm32")] so host functions aren't called in native tests, update assertions for generated filename and extras_json duration - Add @0.3.0 linker stubs in wit_compat.rs - Add .githooks/pre-commit hook that runs scripts/check-version-bumps.sh when WIT or extension sources are staged - Symlink commit-msg regression hook into .githooks/ [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: extract voice download from extract_attachments into handle_message Move download_voice_file + store_attachment_data calls out of extract_attachments into a separate download_and_store_voice function called from handle_message. This keeps extract_attachments as a pure data-mapping function with no host calls, making it fully testable in native unit tests without #[cfg(target_arch)] gates. [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address PR review comments — security, correctness, and code quality Security fixes: - Add path validation to read_attachments (restrict to /tmp/) preventing arbitrary file reads from compromised tools - Escape XML special characters in attachment filenames, MIME types, and extracted text to prevent prompt injection via tag spoofing - Percent-encode file_id in Telegram getFile URL to prevent query injection - Clone SecretString directly instead of expose_secret().to_string() Correctness fixes: - Fix store_attachment_data overwrite accounting: subtract old entry size before adding new to prevent inflated totals and false rejections - Use max(reported, stored_size) for attachment size accounting to prevent WASM channels from under-reporting size_bytes to bypass limits - Add application/octet-stream to MIME allowlist (channels default unknown types to this) Code quality: - Extract send_response helper in Telegram, deduplicating on_respond and on_broadcast - Rename misleading Discord test to test_parse_slash_command_interaction - Fix .githooks/commit-msg to use relative symlink (portable across machines) [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add tool_upgrade command + fix TOCTOU in save_to path validation Add `tool_upgrade` — a new extension management tool that automatically detects and reinstalls WASM extensions with outdated WIT versions. Preserves authentication secrets during upgrade. Supports upgrading a single extension by name or all installed WASM tools/channels at once. Fix TOCTOU in `validate_save_to_path`: validate the path *before* creating parent directories, so traversal paths like `/tmp/../../etc/` cannot cause filesystem mutations outside /tmp before being rejected. [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: unify WIT package version to 0.3.0 across tool.wit and all capabilities tool.wit and channel.wit share the `near:agent` package namespace, so they must declare the same version. Bumps tool.wit from 0.2.0 to 0.3.0 and updates all capabilities files and registry entries to match. Fixes `cargo component build` failure: "package identifier near:agent@0.2.0 does not match previous package name of near:agent@0.3.0" [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: move WIT file comments after package declaration WIT treats `//` comments before `package` as doc comments. When both tool.wit and channel.wit had header comments, the parser rejected them as "doc comments on multiple 'package' items". Move comments after the package declaration in both files. Also bumps tool registry versions to 0.2.0 to match the WIT 0.3.0 bump. [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: display extension versions in gateway Extensions tab Add version field to InstalledExtension and RegistryEntry types, pipe through the web API (ExtensionInfo, RegistryEntryInfo), and render as a badge in the gateway UI for both installed and available extensions. For installed WASM extensions, version is read from the capabilities file with a fallback to the registry entry when the local file has no version (old installations). Bump all extension Cargo.toml and registry JSON versions from 0.1.0 to 0.2.0 to keep them in sync. [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add document text extraction middleware for PDF, Office, and text files Extract text from document attachments (PDF, DOCX, PPTX, XLSX, RTF, plain text, code files) so the LLM can reason about uploaded documents. Uses pdf-extract for PDFs, zip+XML parsing for Office XML formats, and UTF-8 decode for text files. Wired into the agent loop after transcription middleware. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: download document files in Telegram channel for text extraction The DocumentExtractionMiddleware needs file bytes in the attachment `data` field, but only voice files were being downloaded. Document attachments (PDFs, DOCX, etc.) had empty `data` and a source_url with a credential placeholder that only works inside the WASM host's http_request. Add `download_and_store_documents()` that downloads non-voice, non-image, non-audio attachments via the existing two-step getFile→download flow and stores bytes via `store_attachment_data` for host-side extraction. Also rename `download_voice_file` → `download_telegram_file` since it's generic for any file_id. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: allow Office MIME types and increase file download limit for Telegram Two issues preventing document extraction from Telegram: 1. PPTX/DOCX/XLSX MIME types (application/vnd.*) were dropped by the WASM host attachment allowlist — add application/vnd., application/msword, and application/rtf prefixes. 2. Telegram file downloads over 10 MB failed with "Response body too large" — set max_response_bytes to 20 MB in Telegram capabilities. [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: report document extraction errors back to user instead of silently skipping - Bump max_response_bytes to 50 MB for Telegram file downloads - When document extraction fails (too large, download error, parse error), set extracted_text to a user-friendly error message instead of leaving it None. This ensures the LLM tells the user what went wrong. - On Telegram download failure, set extracted_text with the error so the user sees feedback even when the file never reaches the extraction middleware. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: store extracted document text in workspace memory for search/recall After document extraction succeeds, write the extracted text to workspace memory at `documents/{date}/{filename}`. This enables: - Full-text and semantic search over past uploaded documents - Cross-conversation recall ("what did that PDF say?") - Automatic chunking and embedding via the workspace pipeline Documents are stored with metadata header (uploader, channel, date, MIME type). Error messages (extraction failures) are not stored — only successful extractions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: CI failures — formatting, unused assignment warning - Run cargo fmt on document_extraction and agent_loop modules - Suppress unused_assignments warning on trace_llm_ref (used only behind #[cfg(feature = "libsql")]) [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address PR review comments — security, correctness, and code quality Security fixes: - Remove SSRF-prone download() from DocumentExtractionMiddleware (nearai#13) - Sanitize filenames in workspace path to prevent directory traversal (nearai#11) - Pre-check file size before reading in WASM wrapper to prevent OOM (nearai#2) - Percent-encode file_id in Telegram source URLs (nearai#7) Correctness fixes: - Clear image_content_parts on turn end to prevent memory leak (nearai#1) - Find first *successful* transcription instead of first overall (nearai#3) - Enforce data.len() size limit in document extraction (nearai#10) - Use UTF-8 safe truncation with char_indices() (nearai#12) Robustness & code quality: - Add 120s timeout to OpenAI Whisper HTTP client (nearai#5) - Trim trailing slash from Whisper base_url (nearai#6) - Allow ~/.ironclaw/ paths in WASM wrapper (nearai#8) - Return error from on_broadcast in Slack/Discord/WhatsApp (nearai#9) - Fix doc comment in HTTP tool (nearai#4) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: formatting — cargo fmt Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address latest PR review — doc comments, error messages, version bumps - Fix DocumentExtractionMiddleware doc comment (no longer downloads from source_url) - Fix error message: "no inline data" instead of "no download URL" - Log error + fallback instead of silent unwrap_or_default on Whisper HTTP client - Bump all capabilities.json versions from 0.1.0 to 0.2.0 to match Cargo.toml Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: remove unsupported profile: minimal from CI workflows [skip-regression-check] dtolnay/rust-toolchain@stable does not accept the 'profile' input (it was a parameter for the deprecated actions-rs/toolchain action). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: merge with latest main — resolve compilation errors and PR review nits - Add version: None to RegistryEntry/InstalledExtension test constructors - Fix MessageContent type mismatches in nearai_chat tests (String → MessageContent::Text) - Fix .contains() calls on MessageContent — use .as_text().unwrap() - Remove redundant trace_llm_ref = None assignment in test_rig - Check data size before clone in document extraction to avoid unnecessary allocation [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
drchirag1991
pushed a commit
to drchirag1991/ironclaw
that referenced
this pull request
Apr 8, 2026
…B-backed pairing, and OwnershipCache (nearai#1898) * feat(ownership): add OwnerId, Identity, UserRole, can_act_on types Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(ownership): private OwnerId field, ResourceScope serde derives, fix doc comment Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * refactor(tenant): replace SystemScope::db() escape hatch with typed workspace_for_user(), fix stale variable names - Add SystemScope::workspace_for_user() that wraps Workspace::new_with_db - Remove SystemScope::db() which exposed the raw Arc<dyn Database> - Update 3 callers (routine_engine.rs x2, heartbeat.rs x1) to use the new method - Fix stale comment: "admin context" -> "system context" in SystemScope - Rename `admin` bindings to `system` in agent_loop.rs for clarity Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(tenant): rename stale admin binding to system_store in heartbeat.rs * refactor(tenant): TenantScope/TenantCtx carry Identity, add with_identity() constructor and bridge new() - TenantScope: replace `user_id: String` field with `identity: Identity`; add `with_identity()` preferred constructor; keep `new(user_id, db)` as Member-role bridge; add `identity()` accessor; all internal method bodies use `identity.owner_id.as_str()` in place of `&self.user_id` - TenantCtx: replace `user_id: String` field with `identity: Identity`; update constructor signature; add `identity()` accessor; `user_id()` delegates to `identity.owner_id.as_str()`; cost/rate methods updated accordingly - agent_loop: split `tenant_ctx(&str)` into bridge + new `tenant_ctx_with_identity(Identity)` which holds the full body; bridge delegates to avoid duplication Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * feat(db): add V16 tool scope, V17 channel_identities, V18 pairing_requests migrations - PostgreSQL: V16__tool_scope.sql adds scope column to wasm_tools/dynamic_tools - PostgreSQL: V17__channel_identities.sql creates channel identity resolution table - PostgreSQL: V18__pairing_requests.sql creates pairing request table replacing file-based store - libSQL SCHEMA: adds scope column to wasm_tools/dynamic_tools, channel_identities, pairing_requests tables - libSQL INCREMENTAL_MIGRATIONS: versions 17-19 for existing databases - IDEMPOTENT_ADD_COLUMN_MIGRATIONS: handles fresh-install/upgrade dual path for scope columns - Runner updated to check ALL idempotent columns per version before skipping SQL - Test: test_ownership_model_tables_created verifies all new tables/columns exist after migrations Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(db): use correct RFC3339 timestamp default in libSQL, document version sequence offset Replace datetime('now') with strftime('%Y-%m-%dT%H:%M:%fZ', 'now') in the channel_identities and pairing_requests table definitions (both in SCHEMA and INCREMENTAL_MIGRATIONS) to match the project-standard RFC 3339 timestamp format with millisecond precision. Also add a comment clarifying that libSQL incremental migration version numbers are independent from PostgreSQL VN migration numbers. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * feat(ownership): bootstrap_ownership(), migrate_default_owner, V19 FK migration, replace hardcoded 'default' user IDs - Add V19__ownership_fk.sql (programmatic-only, not in auto-migration sweep) - Add `migrate_default_owner` to Database trait + both PgBackend and LibSqlBackend - Add `get_or_create_user` default method to UserStore trait - Add `bootstrap_ownership()` to app.rs, called in init_database() after connect_with_handles - Replace hardcoded "default" owner_id in cli/config.rs, cli/mcp.rs, cli/mod.rs, orchestrator/mod.rs - Add TODO(ownership) comments in llm/session.rs and tools/mcp/client.rs for deferred constructors Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(ownership): atomic get_or_create_user, transactional migrate_default_owner, V19 FK inline constant, fix remaining 'default' user IDs - Delete migrations/V19__ownership_fk.sql so refinery no longer auto-applies FK constraints before bootstrap_ownership runs; add OWNERSHIP_FK_SQL constant with TODO for future programmatic application - Remove racy SELECT+INSERT default in UserStore::get_or_create_user; both PostgreSQL (ON CONFLICT DO NOTHING) and libSQL (INSERT OR IGNORE) now use atomic upserts - Wrap migrate_default_owner in explicit transactions on both backends for atomicity - Make bootstrap_ownership failure fatal (propagate error instead of warn-and-continue) - Fix mcp auth/test --user: change from default_value="default" to Option<String> resolved from configured owner_id - Replace hardcoded "default" user IDs in channels/wasm/setup.rs with config.owner_id - Replace "default" sentinel in OrchestratorState test helper with "<unset>" to make the test-only nature explicit Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(ownership): remove default user_id from create_job(), change sentinel strings to <unset> - Gate ContextManager::create_job() behind #[cfg(test)]; production code must use create_job_for_user() with an explicit user_id to prevent DB rows with user_id = 'default' being silently created on the production write path. - Change the placeholder user_id in McpClient::new(), new_with_name(), and new_with_config() from "default" to "<unset>" so accidental secrets/settings lookups surface immediately rather than silently touching the wrong DB partition. - Same sentinel change for SessionManager::new() and new_async() in session.rs; these are overwritten by attach_store() at startup with the real owner_id. - Update tests that asserted the old "default" sentinel to expect "<unset>", and switch test_list_jobs_tool / test_job_status_tool to create_job_for_user("default") to keep ownership alignment with JobContext::default(). Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * feat(db): add ChannelPairingStore sub-trait with resolve_channel_identity, upsert/approve pairing, PostgreSQL + libSQL implementations Adds PairingRequestRecord, ChannelPairingStore trait (5 methods), and generate_pairing_code() to src/db/mod.rs; implements for PgBackend in postgres.rs and LibSqlBackend in libsql/pairing.rs; wires ChannelPairingStore into the Database supertrait bound; all 6 libSQL unit tests pass. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(db): atomic libSQL approve_pairing with BEGIN IMMEDIATE, add case-insensitive/expired/double-approve tests Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * feat(ownership): add OwnershipCache for zero-DB-read identity resolution on warm path Converts src/ownership.rs to src/ownership/ module directory and adds src/ownership/cache.rs with a write-through in-process cache mapping (channel, external_id) -> Identity. Wired as Arc<OwnershipCache> on AppComponents for Task 8 pairing integration. All 7 cache unit tests pass. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * test(e2e): add ownership model E2E tests and extend pairing tests for DB-backed store Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(e2e): remove unused asyncio import, add fallback assertion in test_pairing_response_structure Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * test(tenant): unit tests for TenantScope::with_identity and AdminScope construction Adds 5 focused unit tests verifying TenantScope::with_identity stores the full Identity (owner_id + role), TenantScope::new creates a Member-role identity, and AdminScope::new returns Some for Admin and None for Member. Uses LibSqlBackend::new_memory() as the test DB stub. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(ownership): recover from RwLock poison instead of expect() in OwnershipCache Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * test(ownership): integration tests for bootstrap, tenant isolation, and ChannelPairingStore Adds tests/ownership_integration.rs covering migrate_default_owner idempotency, TenantScope per-user setting isolation (including Admin role bypass check), and the full ChannelPairingStore lifecycle (upsert, approve, remove, multi-channel isolation). Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(test): remove duplicate pairing tests and flaky random-code assertion from integration suite Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * feat(pairing): rewrite PairingStore to DB-backed async with OwnershipCache Replaces the file-based pairing store (~/.ironclaw/*-pairing.json, *-allowFrom.json) with a DB-backed async implementation that delegates to ChannelPairingStore and writes through to OwnershipCache on reads. - PairingStore::new(db, cache) uses the DB; new_noop() for test/no-DB - resolve_identity() cache-first lookup via OwnershipCache - approve(code, owner_id) removes channel arg (DB looks up by code) - All WASM host functions updated: pairing_upsert_request uses block_in_place, pairing-is-allowed renamed to pairing-resolve-identity returning Option<String>, pairing-read-allow-from deprecated (returns empty list) - Signal channel receives PairingStore via new(config, db) constructor - Web gateway pairing handlers read from state.store (DB) directly - extensions.rs derive_activation_status drops PairingStore dependency; derives status from extension.active and owner_binding flag instead - All test call sites updated to use new_noop() Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(pairing): add missing pairing_store field to all GatewayState initializers, fix disk-full post-edit compile Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * feat(channels): remove owner_id from IncomingMessage, user_id is the canonical resolved OwnerId `owner_id` on `IncomingMessage` was always a duplicate of `user_id` — both fields held the same value at every call site. Remove the field and `with_owner_id()` builder, update the four WASM-wrapper and HTTP test assertions to use `user_id`, and drop the redundant struct literal field in the routine_engine test helper. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(channels): remove stale owner_id param from make_message test helper Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * test(e2e): add browser/Playwright tests for ownership model — auth screen, chat UI, owner login Adds five Playwright-based browser tests to the ownership model E2E suite verifying the web UI experience: authenticated owner sees chat input, unauthenticated browser sees auth screen, owner can send a message and receive a response, settings tab renders without errors, and basic page structure is correct after login. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * feat(settings): migrate channel credentials from plaintext settings to encrypted secrets store Moves nearai.session_token from the plaintext DB settings table to the AES-256-GCM encrypted secrets store (key: nearai_session_token). - SessionManager gains an `attach_secrets()` method that wires in the secrets store; `save_session` writes to it when available and `load_session_from_secrets` is called preferentially over settings - `migrate_session_credential()` runs idempotently on each startup in `init_secrets()`, reading the JSON session from settings, writing it to secrets, then deleting the plaintext copy - Wizard's `persist_session_to_db` now writes to secrets first, falling back to plaintext settings only when secrets store is unavailable - Plaintext settings path is preserved as fallback for installs without a secrets store (no master key configured) Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(settings): settings fallback only when no secrets store, verify decryption before deleting plaintext Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(ownership): ROLLBACK in libSQL migrate_default_owner, shared OwnershipCache across channels, add dynamic_tools to migration, fix doc comment - libSQL migrate_default_owner: wrap UPDATE loop in async closure + match to emit ROLLBACK on any mid-transaction failure (mirroring approve_pairing pattern) - Both backends: add dynamic_tools to the migrate_default_owner table list so agent-built tools are migrated on first pairing - setup_wasm_channels: accept Arc<OwnershipCache> parameter instead of allocating a fresh cache, share the AppComponents cache - SignalChannel::new: accept Arc<OwnershipCache> parameter and pass it to PairingStore instead of allocating a new cache - PairingStore: fix module-level and struct-level doc comments to accurately describe lazy cache population after approve() Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(web): use can_act_on for authorization in job/routine handlers instead of raw string comparisons Replace 12 raw `user_id != user.user_id` / `user_id == user.user_id` string comparisons in jobs.rs and 4 in routines.rs with calls through the canonical `can_act_on` function from `crate::ownership`, which is the spec-mandated authorization mechanism. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * chore: include remaining modified files in ownership model branch * fix: add pairing_store field to test GatewayState initializers, update PairingStore API calls in integration tests Add missing `pairing_store: None` to all GatewayState struct initializers in test files. Migrate old file-based PairingStore API calls (PairingStore::new(), PairingStore::with_base_dir()) to the new DB-backed API (PairingStore::new_noop()). Rewrite pairing_integration.rs to use LibSqlBackend with the new async DB-backed PairingStore API. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * chore: cargo fmt * fix(pairing): truly no-op PairingStore noop mode, ensure owner user in CLI, fix signal safety comments - PairingStore::upsert_request now returns a dummy record in noop mode instead of erroring, and approve silently succeeds (matching the doc promise of "writes are silently discarded"). - PairingStore::approve now accepts a channel parameter, matching the updated DB trait signature and propagated to all call sites (CLI, web server, tests). - CLI run_pairing_command ensures the owner user row exists before approval to satisfy the FK constraint on channel_identities.owner_id. - Signal channel block_in_place safety comments corrected from "WASM channel callbacks" to "Signal channel message processing". Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(pairing): thread channel through approve_pairing, add created flag, retry on code collision, remove redundant indexes Addresses PR review comments: - approve_pairing validates code belongs to the given channel - PairingRequestRecord.created replaces timing heuristic - upsert retries on UNIQUE violation (up to 3 attempts) - redundant indexes removed (UNIQUE creates implicit index) Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(ownership): migrate api_tokens, serialize PG approvals, propagate resolved owner_id Addresses PR review P1/P2 regressions: - api_tokens included in migrate_default_owner (both backends) - PostgreSQL approve_pairing uses FOR UPDATE to prevent concurrent approvals - Signal resolve_sender_identity returns owner_id, set as IncomingMessage.user_id with raw phone number preserved as sender_id for reply routing - Feishu uses resolved owner_id from pairing_resolve_identity in emitted message - PairingStore noop mode logs warning when pairing admission is impossible [skip-regression-check] Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(pr-review): sanitize DB errors in pairing handlers, fix doc comments, add TODO for derive_activation_status - Pairing list/approve handlers no longer leak DB error details to clients - NotFound errors return user-friendly 'Invalid or expired pairing code' message - Module doc in pairing/store.rs corrected (remove -> evict, no insert method) - wit_compat.rs stub comment corrected to match actual Val shape - TODO added for derive_activation_status has_paired approximation * fix(pr-review): propagate libSQL query errors in approve_pairing, round-trip validate session credential migration, fix test doc comment - libSQL approve_pairing: .ok().flatten() replaced with .map_err() to propagate DB errors - migrate_session_credential: round-trip compares decrypted secret against plaintext before deleting - ownership_integration.rs: doc comment corrected to match actual test coverage * fix(pairing): store meta, wrap upserts in transactions, case-insensitive role/channel, log Signal DB errors, use auth role in handlers - Store meta JSONB/TEXT column in pairing_requests (PG migration V18, libSQL schema + incremental migration 19) - Wrap upsert_pairing_request in transactions (PG: client.transaction(), libSQL: BEGIN IMMEDIATE/COMMIT/ROLLBACK) - Case-insensitive role parsing: eq_ignore_ascii_case("admin") in both backends - Case-insensitive channel matching in approve_pairing: LOWER(channel) = LOWER($2) - Log DB errors in Signal resolve_sender_identity instead of silently discarding - Use auth role from UserIdentity in web handlers (jobs.rs, routines.rs) via identity_from_auth helper - Fix variable shadowing: rename `let channel` to `let req_channel` in libsql approve_pairing Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(security): add auth to pairing list, cache eviction on deactivate, runtime assert in Signal, remove default fallback, warn on noop pairing codes Addresses zmanian's review: - nearai#1: pairing_list_handler requires AuthenticatedUser - nearai#2: OwnershipCache.evict_user() evicts all entries for a user on suspension - nearai#3: debug_assert! for multi-thread runtime in Signal block_in_place - nearai#9: Noop PairingStore warns when generating unredeemable codes - nearai#10: cli/mcp.rs default fallback replaced with <unset> * fix(pairing): consistent LOWER() channel matching in resolve_channel_identity, fix wizard doc comment, fix E2E test assertion for ActionResponse convention * fix(pairing): apply LOWER() consistently across all ChannelPairingStore queries (upsert, list_pending, remove) All channel matching now uses LOWER() in both PostgreSQL and libSQL backends: - upsert_pairing_request: WHERE LOWER(channel) = LOWER($1) - list_pending_pairings: WHERE LOWER(channel) = LOWER($1) - remove_channel_identity: WHERE LOWER(channel) = LOWER($1) Previously only resolve_channel_identity and approve_pairing used LOWER(), causing inconsistent matching when channel names differed by case. * fix(pairing): unify code challenge flow and harden web pairing * test: harden pairing review follow-ups * fix: guard wasm pairing callbacks by runtime flavor * fix(pairing): normalize channel keys and serialize pg upserts * chore(web): clean up ownership review follow-ups * Preserve WASM pairing allowlist compatibility --------- Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
4 tasks
ilblackdragon
added a commit
that referenced
this pull request
Apr 8, 2026
High-severity security and correctness fixes from ilblackdragon and serrrfirat reviews, bundled into one commit. 1. SSRF-validate the OAuth refresh proxy URL (`src/auth/mod.rs`). `IRONCLAW_OAUTH_EXCHANGE_URL` was previously trusted as-is, so a misconfigured proxy could send the user's refresh token to internal infrastructure. Wraps `validate_and_resolve_http_target` in a new `validate_oauth_proxy_url` helper. Loopback is gated behind `IRONCLAW_OAUTH_PROXY_ALLOW_LOOPBACK` for tests only. 2. WASM `resolve_host_credentials` now fails closed (`src/tools/wasm/wrapper.rs`). Returns a struct with `resolved` plus `missing_required`; `execute()` bails when any non-optional credential is unresolvable. `CredentialMapping` gains an `optional: bool` field (`#[serde(default)]`) — defaults to required so a tool that simply declares a credential cannot be silently downgraded to an unauthenticated request. 3. `ensure_extension_ready` no longer auto-installs registry extensions on the `UseCapability` (LLM-driven) path (`src/extensions/manager.rs`). Auto-install is now restricted to `PostInstall` and `ExplicitActivate` intents. Latent action invocations surface `NotInstalled` so the bridge can route them through the install/approval gate. 4. `is_known_credential` defaults to `false` when no credential registry is wired (`src/bridge/effect_adapter.rs`). Previously returned `true`, which made the absence of a registry indistinguishable from a permitted credential. 5. `auth_descriptor_cache` is now TTL-bounded (60s) with explicit invalidation (`src/auth/mod.rs`). The cache is no longer an unbounded process-global; deleted/suspended users fall out within the window even without an invalidation hook. 6. libSQL `create_user` / `get_or_create_user` ROLLBACK errors are now logged instead of swallowed (`src/db/libsql/users.rs`). The connection-per-operation model means a failed ROLLBACK cannot leak dirty state, but the warning gives operators visibility. 7. `activate_wasm_tool` and `activate_mcp` now invalidate the latent provider actions cache after success (`src/extensions/manager.rs`), so newly-activated providers stop appearing as latent on the next ensure cycle. 8. `restore_from_persistence` clears the `approval_already_granted` flag on rehydrated pending gates (`src/gate/store.rs`). The flag is an in-memory hint for chained gates within a single router cycle and must not survive a process restart. 9. `resolved_call_id_for_pending_action` now returns `Option<String>` (`src/bridge/router.rs`). The previous empty-string fallback corrupted engine call/result pairing on a miss; callers now synthesize a non-empty correlator and log a warning. Additional regression tests: - `ensure_extension_ready_use_capability_does_not_auto_install` — guards fix #3. - `resolved_call_id_returns_none_when_no_history_match` — guards #9. - `test_resolve_host_credentials_denies_default_fallback_when_caller_is_default` — negative test for the `DefaultFallback::AdminOnly` policy when the caller's `user_id` is literally `"default"`. Existing test `ensure_extension_ready_auto_installs_registry_wasm_tool_on_first_use` renamed to `..._on_explicit_activate` and switched to the `ExplicitActivate` intent so it still exercises the auto-install path. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
serrrfirat
added a commit
that referenced
this pull request
Apr 13, 2026
Critical fixes: - Use DB-first config system for MissionsConfig instead of raw std::env::var in router.rs (issue #1) - SessionSummaryHook now uses thread_ids from HookEvent::SessionEnd to summarize the correct conversation instead of guessing via recency; falls back to most-recent for backward compatibility (#2) - Add per-user rate limiter (10/min, 60/hr) and 15s timeout on reasoning LLM calls in MemorySearchTool to prevent unbounded usage (#3) Test coverage: - Caller-level tests for reasoning-augmented recall (LLM wiring, disabled config, and failure fallback paths) (#4) - SessionSummaryHook LLM failure path test confirming fail-open behavior (#5) - reasoning_enabled config field tests (default, env, DB override) (#6) - MissionSettings and SearchSettings round-trip assertions in comprehensive_db_map_round_trip (#11) Convention fixes: - Remove double env-var parsing in MissionsConfig::resolve (#7) - Use ChatMessage::system()/user() constructors in SessionSummaryHook (#8) - Add TODO comments for inline prompt strings (#9) - Add timeout on reasoning LLM call (#10) CI fixes: - Remove 4 stale wasmtime advisory entries from deny.toml - Add RUSTSEC-2026-0097 (rand 0.8.5) to advisory ignore list Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
10 tasks
6 tasks
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.
Scaffold two new WASM tools that share a single Google OAuth token:
Both tools use the sandboxed WIT interface with strict HTTP allowlists, credential injection, and rate limiting. OAuth config requests only the minimum scopes needed (calendar.events, gmail.modify, gmail.compose).
Also adds the /add-tool skill for scaffolding future WASM or built-in tools with all boilerplate wired up.