mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-30 23:14:37 +00:00
11c67a70b98b5b3cdc0fee2b6ec963a9120ded26
551
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
11c67a70b9 | feat(notifications): add dismiss/stats RPC, DomainEvents, and triage bus integration (#875) | ||
|
|
208c276a3e | feat(notifications): bridge CEF providers into core ingest pipeline with dedup (#874) | ||
|
|
b329e45cdb |
feat(skills): uninstall for user-scope SKILL.md skills (#781) (#833)
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
b81d04df54 | feat(redirect_links): SQLite-backed URL shortener for token-heavy links (#870) | ||
|
|
500ddc2a8a | feat(webview_apis): WebSocket bridge for live-webview APIs, Gmail first (#869) | ||
|
|
b40af6c294 |
fix(security): command injection in npm postinstall + weak RNG fallback (#837)
- Replace `execSync` with `execFileSync` in npm install script to prevent command injection. - Pass PowerShell paths via environment variables to avoid shell metacharacter interpolation. - Add `-NoProfile` and `-NonInteractive` flags to PowerShell extraction for cleaner installs. - Upgrade `makeAccountId` to prioritize `crypto.getRandomValues` over `Math.random` for suffixes. - Update internal thread title logic to import `collapse_whitespace` from the correct location. Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai> |
||
|
|
53befcd56f |
chore: drop dead crypto + stale rules, Windows doctor disk probe (#856)
Two cleanups surfaced during a codebase review: ## Changes - **Drop dead integration-token crypto** — `integrationTokensCrypto.ts` + helpers; no remaining consumers - **Trim stale `.claude/rules`** — rules that contradict current CLAUDE.md - **Windows doctor disk probe** — detect low-disk conditions on Windows - **Refresh stale binary-size claim** in docs Net: +82 / −3,596 (mostly dead-code removal). ## Test plan - [ ] `yarn typecheck` green — no dangling imports to crypto module - [ ] `cargo check` green - [ ] Doctor runs on Windows host Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai> |
||
|
|
1408666706 |
feat(notifications): native OS notifications via CEF shim + scanner fallback (#850)
Co-authored-by: oxoxDev <nikhil@tinyhumans.ai> Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai> |
||
|
|
c0f6e39b3e |
feat(memory_tree): Phase 4 retrieval tools for hierarchical memory (#710) (#831)
Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai> |
||
|
|
9d7237bb91 |
feat(skills): agentic loop wiring for SKILL.md bodies (#781) (#807)
- Add Skill::read_body to fetch SKILL.md instruction text during agent inference. - Implement a skill matcher supporting explicit @ mentions and automatic keyword/tag heuristics. - Create a rendering system for skill injection with an 8KB budget and truncation handling. - Wire skill matching and instruction body injection into the Agent::turn execution path. - Include 24 unit tests for skill matching, ranking logic, and size-cap enforcement. Closes #781 Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai> |
||
|
|
c59b4289f1 |
refactor(config): extract env lookup seam for testability (#792)
## Summary - Introduce an `EnvLookup` seam in `src/openhuman/config/schema/load.rs`. - Route env-overlay reads through `EnvLookup`/`ProcessEnv` while preserving existing precedence and runtime behavior. - Add focused parallel-safe unit coverage for config env overlay behavior without mutating process env. ## Problem - `Config::apply_env_overrides` mixed environment access, override logic, and global side effects. - That made precedence and parsing behavior harder to unit test in isolation and forced reliance on process-env mutation. ## Solution - Add `pub(crate) trait EnvLookup` with `get`, `contains`, and `get_any`, plus a production `ProcessEnv` implementation. - Delegate overlay logic through `apply_env_overlay_with(&ProcessEnv)` and keep side effects in the small wrapper. - Add focused tests covering precedence, parsing, defaults, and legacy-migration interactions with no global env mutation. ## Submission Checklist - [x] **Unit tests** — Targeted Rust config tests added and broader related module suites passed - [ ] **E2E / integration** — Not applicable; this refactor preserves existing behavior and improves unit-level testability - [x] **N/A** — E2E is not applicable because no user-visible or cross-process flow changed - [ ] **Doc comments** — Not applicable; internal refactor in existing module only - [ ] **Inline comments** — Not applicable; logic remains local and test coverage documents behavior ## Impact - No intended runtime behavior change; refactor keeps config precedence intact. - Improves maintainability and makes future config/workspace resolution seams easier to test safely. ## Related - Issue(s): - Follow-up PR(s)/TODOs: thread `EnvLookup` through runtime config directory resolution helpers Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai> |
||
|
|
629b4ccb07 |
test(memory_tree): unit tests for retrieval RPC handlers (#710) (#839)
- Add JSON-RPC handlers for source, global, and topic-based memory retrieval. - Integrate Ollama embeddings for semantic reranking of chunks and summaries. - Implement 15 unit tests for RPC handlers covering parameter parsing and PII redaction. - Redact PII from logs by removing raw source, entity, and node identifiers. - Fix BFS traversal for drill-down and deduplicate results in topic queries. - Add configuration for embedding endpoints, models, and strictness modes. Co-authored-by: sanil-23 <sanil@vezures.xyz> Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai> |
||
|
|
c78f496686 | fix(webhooks,cron): wire trigger pipeline so agents auto-respond reliably (#747) | ||
|
|
7670ae9f12 |
feat(notifications): core→shell DomainEvent bridge (Phase 1d of #395) (#782)
Co-authored-by: Jwalin Shah <jshah1331@gmail.com> Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai> |
||
|
|
5e660f2fe8 |
feat(core): memory namespaces, recall citations, provider_surfaces RPC (#803)
Co-authored-by: Jwalin Shah <jshah1331@gmail.com> Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai> |
||
|
|
29a3962b55 | Feat: add email login auth (#832) | ||
|
|
22b7a8b18f |
[codex] Harden threads schema parse contract tests (#797)
Co-authored-by: Jwalin Shah <jshah1331@gmail.com> |
||
|
|
e86406ea05 |
[codex] Refactor config runtime dir resolution for testability (#794)
Co-authored-by: Jwalin Shah <jshah1331@gmail.com> |
||
|
|
f539899a69 |
refactor(agent): move debug dump into domain and extract dump writer (#764)
Co-authored-by: Jwalin Shah <jshah1331@gmail.com> Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai> |
||
|
|
4993a410f2 |
test(rpc,core,billing,team): harden registry + RPC helpers with edge-case coverage (#786)
Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Jwalin Shah <jshah1331@gmail.com> |
||
|
|
1d62d1c0ac |
refactor(threads, tool_timeout): extract pure helpers for testability (#784)
Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Jwalin Shah <jshah1331@gmail.com> |
||
|
|
fc213572c6 |
test(threads): harden JSON-RPC validation coverage (#790)
Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Jwalin Shah <jshah1331@gmail.com> |
||
|
|
325f019853 |
refactor(core): move CLI adapters out of src/core/ into per-domain cli modules (#758)
* refactor(voice): move standalone CLI adapter into voice domain
Move the blocking `openhuman voice` / `openhuman dictate` dictation-server
subcommand out of `src/core/cli.rs` and into a new `src/openhuman/voice/cli.rs`
owned by the voice domain. The core CLI dispatcher now routes to
`voice::cli::run_standalone_subcommand` and no longer imports voice internals.
Why this shape: the standalone server blocks forever on the hotkey listener,
which doesn't fit the controller registry's request/response contract. A
domain-owned CLI adapter is the right home for long-lived operational
commands; the controller registry stays for RPC-style capabilities.
Establishes the exposure rule documented in PLAN.md for the rest of the
backend overhaul.
- src/openhuman/voice/cli.rs: new 118-line adapter (flag parsing, tokio
runtime, run_standalone call)
- src/openhuman/voice/mod.rs: declare `pub mod cli`
- src/core/cli.rs: delete 92-line run_voice_server_command, dispatch only
- PLAN.md: new scope doc + exposure rule
* refactor(text_input): move CLI adapter into domain
Move src/core/text_input_cli.rs to src/openhuman/text_input/cli.rs and
drop the stale `pub mod text_input_cli` from src/core/mod.rs. Core CLI
dispatcher now routes `text-input` to the domain-owned adapter.
The moved file has a mixed shape that validates the exposure rule from
the previous voice commit:
- `run` starts a long-lived HTTP JSON-RPC server (domain cli.rs shape)
- `read / insert / ghost / dismiss` are short-lived UX wrappers around
`text_input::rpc::*` (pretty-print, flag parsing — CLI affordance, not
transport duplication)
Both shapes legitimately belong in the domain. The controller registry
stays for transport-agnostic capabilities; CLI UX wrappers live next to
their domain RPC.
- git mv src/core/text_input_cli.rs src/openhuman/text_input/cli.rs
- src/openhuman/text_input/mod.rs: declare `pub mod cli`
- src/core/mod.rs: drop `pub mod text_input_cli`
- src/core/cli.rs: update dispatch path
* refactor(tree_summarizer): move CLI adapter into domain
git mv src/core/tree_summarizer_cli.rs src/openhuman/tree_summarizer/cli.rs
and drop the stale `pub mod tree_summarizer_cli` from src/core/mod.rs.
Core CLI dispatcher now routes `tree-summarizer` to the domain-owned
adapter.
All five subcommands (ingest / run / query / status / rebuild) are short
-lived UX wrappers over registry handlers that already exist in
tree_summarizer/schemas.rs — no long-running server, no business logic
duplicated. Straight relocation keeps transport generic.
- git mv src/core/tree_summarizer_cli.rs src/openhuman/tree_summarizer/cli.rs
- src/openhuman/tree_summarizer/mod.rs: declare `pub mod cli`
- src/core/mod.rs: drop `pub mod tree_summarizer_cli`
- src/core/cli.rs: update dispatch path
* refactor(screen_intelligence): move + split CLI adapter into domain
Move src/core/screen_intelligence_cli.rs (699 lines, 7 subcommands) into
a domain-owned cli/ submodule, split by responsibility so no single file
exceeds the project's 500-line soft limit.
Layout:
screen_intelligence/cli/mod.rs (204) dispatch + shared helpers
(CliOpts, parse_opts,
bootstrap_engine, logging,
print_help)
screen_intelligence/cli/server.rs (79) run_server (long-running
capture+vision loop)
screen_intelligence/cli/session.rs (150) status / start / stop
screen_intelligence/cli/capture.rs (173) capture / vision (inspect pair)
screen_intelligence/cli/doctor.rs (107) readiness diagnostics
Classification before moving:
- no business-logic duplication vs schemas.rs handlers
- heavy fns (doctor 103, capture 97, start 89) were 80%+ pretty-printing
and CLI-side orchestration (polling loop, flag-driven save) — legit
CLI UX, not extracted domain logic
- shared helpers stayed pub(super) inside mod.rs; no new domain APIs
introduced
Core transport changes:
- delete src/core/screen_intelligence_cli.rs
- drop `pub mod screen_intelligence_cli` from src/core/mod.rs
- src/core/cli.rs dispatch routes to domain cli::
After this commit, src/core/cli.rs contains only dispatch lines for all
four migrated domains (voice, text_input, tree_summarizer,
screen_intelligence) — no embedded domain logic.
* refactor(screen_intelligence): address external review — extract capture save + tighten cli visibility
Follow-ups from codex + gemini review of the branch:
1. Extract CaptureFrame construction + disk save from the CLI.
`capture.rs --keep` was building a CaptureFrame inline (stamping
timestamps, copying context fields) and calling
`AccessibilityEngine::save_screenshot_to_disk` directly. Added
`AccessibilityEngine::save_capture_test_result(workspace_dir,
&CaptureTestResult, reason) -> Option<Result<PathBuf, String>>`
so the CLI only passes the result through and picks a reason
label. Domain owns the frame shape, not the CLI.
2. Tighten CLI module visibility across all four migrated domains.
`pub mod cli` -> `pub(crate) mod cli` and
`pub fn run_*_command` -> `pub(crate) fn` for voice, text_input,
tree_summarizer, screen_intelligence. Only src/core/cli.rs needs
to see these; they're transport plumbing, not domain public API.
No behavior change.
* style: cargo fmt
* address review comments from coderabbitai
- screen_intelligence/cli/server: use effective keep_screenshots (opts.keep
|| config flag) for both SiServerConfig and status output, so the server
behaves consistently with what the CLI reports.
- voice/cli: surface config load errors with a warning instead of silently
falling back to Config::default(); reject unknown --mode values instead
of silently coercing them to Push.
- PLAN.md: narrow the Phase 4 enforcement grep so it forbids domain
imports/non-dispatcher references in the transport layer while still
allowing crate::openhuman::<domain>::cli::run_*(...) dispatch calls,
which this PR legitimately introduces.
* style: cargo fmt voice cli
* style: cargo fmt + bump tauri-cef vendor
* fix(screen_intelligence): narrow save_capture_test_result to pub(crate) (addresses @coderabbitai nitpick on engine.rs:486)
AccessibilityEngine::save_capture_test_result is only called from the
domain's own cli/capture.rs (crate-internal). Making it pub leaks it
into the public API surface unnecessarily — tighten to pub(crate) to
match the visibility-tightening done across the rest of this PR.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* update
* chore: update .gitignore to include scheduled_tasks.lock in app/.claude
* ran formatter
---------
Co-authored-by: Jwalin Shah <jshah1331@gmail.com>
Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
|
||
|
|
bc9e223306 |
feat(privacy): backend-backed capability privacy metadata + PrivacyPanel (#760)
* feat(about_app): add capability privacy metadata
Adds optional `CapabilityPrivacy { leaves_device, data_kind, destinations }`
to the about_app capability catalog so the in-app Privacy surface can be
backend-backed instead of hand-maintained. Twelve representative capabilities
are annotated for the first audited set (raw/local, derived/backend,
credentials, diagnostics, model download); remaining entries default to
None and are simply not surfaced. Wire format stays backward compatible
via skip_serializing_if.
* feat(settings): drive privacy panel from about_app capabilities
Replaces the hand-maintained privacy rows with data fetched from
openhuman.about_app_list. Only capabilities that ship privacy metadata
are rendered; loading and RPC failure both degrade gracefully and the
analytics toggle plus explanatory copy remain intact. Adds a small typed
client (utils/tauriCommands/aboutApp.ts) and focused vitest coverage for
render, omission of unannotated entries, and RPC failure.
---------
Co-authored-by: Jwalin Shah <jshah1331@gmail.com>
|
||
|
|
3bb714bf96 |
feat(webview): native OS notifications from embedded webview apps (#714) (#727)
* feat(webview_accounts): native OS notifications from embedded webviews (#714) Forward CEF notification intercept payloads to tauri-plugin-notification, prefixing the title with the provider label so the source of each toast is obvious at a glance. Honour `silent` (skip toast, still record route), `icon` (passed through to the native builder), and `tag` (used as the dedup key, with a monotonic timestamp fallback for untagged payloads). Record a NotificationRoute keyed by `{provider}:{account_id}:{tag_or_uuid}` so a future click hook (UNUserNotificationCenter / notify-rust on_response) can route the OS click back to the source account. Entries are cleared on webview_account_close / _purge to bound map growth. Expose webview_notification_permission_state / _request commands mapping tauri::plugin::PermissionState onto the web API triple. Non-cef stubs return "default" so the frontend can call the same invoke names on both runtimes. Wire notification:allow-* capabilities so the plugin can be invoked from the webview. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(accounts): wire notification permission + click bridge (#714) Round-trip the OS notification permission once per session on first account open via the new invoke pair. Attach a dormant notification:click listener that dispatches setActiveAccount and brings the main window to front when a platform click hook starts emitting the event — contract matches the Rust NotificationRoute shape so the emit side is a one-liner. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: sync Cargo.lock to 0.52.26 after version bump Lockfile picked up the pending 0.52.26 version bump from Cargo.toml while building the notification feature. No dependency graph change. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(notifications): add notification bypass for embedded webview apps (#679) - Add NotificationBypassPrefs (global DND, per-account mute, bypass-when-focused) to WebviewAccountsState with thread-safe AtomicBool window focus tracking - Evaluate all three bypass conditions inside forward_native_notification before showing OS toast; each suppression path logs at debug with [notify-bypass] prefix - Add four new Tauri commands: webview_notification_set_dnd, webview_notification_mute_account, webview_notification_get_bypass_prefs, webview_set_focused_account - Wire window focus tracking in setup hook via on_window_event Focused handler - Frontend: add setAccountMuted, setGlobalDnd, getBypassPrefs, setFocusedAccount helpers in webviewAccountService; sync focused account on open + click - Add NotificationsPanel settings page with Global DND toggle - Register NotificationsPanel at /settings/notifications Closes #679 * feat(notifications): integrate notifications feature into app - Added Notifications page and routing to AppRoutes. - Introduced NotificationRoutingPanel in Settings for managing notification settings. - Updated SettingsHome to include navigation for notification routing. - Integrated notifications reducer into the store for state management. - Enhanced Rust backend to support notification handling from embedded webviews. This commit lays the groundwork for a comprehensive notification system within the application. * refactor(notifications): clean up code formatting and structure - Simplified JSX structure in NotificationCard for better readability. - Consolidated fetchNotifications call in NotificationCenter for cleaner syntax. - Improved formatting in NotificationRoutingPanel and notificationsSlice for consistency. - Enhanced Rust code readability by streamlining function signatures and logic. These changes enhance code maintainability and readability across the notifications feature. * refactor(webview_accounts): simplify webview_notification_set_dnd function signature - Removed unnecessary line breaks in the webview_notification_set_dnd function for improved readability. * feat(notifications): implement provider-level notification settings management - Added `getNotificationSettings` and `setNotificationSettings` functions to manage notification settings for providers. - Enhanced `NotificationRoutingPanel` to display and update settings for Gmail, Slack, Discord, and WhatsApp. - Introduced new RPC endpoints for retrieving and updating notification settings. - Updated database schema to store notification settings persistently. This commit establishes a robust system for managing notification preferences, improving user control over notifications. * refactor(notifications): improve code formatting and readability - Enhanced formatting in NotificationRoutingPanel for better clarity. - Streamlined function signatures in notificationService and Rust backend. - Improved readability of assertions in tests by adjusting line breaks. These changes contribute to a more maintainable and comprehensible codebase for the notifications feature. * chore(vendor): bump tauri-cef to fix Slack notification permission banner Updates the tauri-cef submodule to 55db2d6 which adds a navigator.permissions.query shim in the CEF render process. Slack checks this API (not just Notification.permission) to decide whether to show its "needs your permission" banner — the shim returns "granted" for notifications queries so the banner no longer appears. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(vendor): bump tauri-cef for cargo fmt fixes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(vendor): bump tauri-cef — native V8 permissions.query shim Switches from context.eval() to a proper PermissionsQueryV8Handler so the navigator.permissions.query fix actually runs in on_context_created. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(notifications): patch navigator.permissions.query in ua_spoof.js The V8 set_value_bykey approach in cef-helper's on_context_created does not stick on CEF platform objects (Chromium's V8 binding layer silently ignores property writes on native wrappers like Permissions). The init script path via frame.execute_java_script runs in the fully-initialised JS context where navigator.permissions IS writable, matching how ua_spoof.js already overrides navigator.userAgent successfully. Slack checks navigator.permissions.query({ name: 'notifications' }) before showing its "needs permission" banner — patching it here to return "granted" removes the banner. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(notifications): use Object.defineProperty to shim navigator.permissions Two-layer fix for the Slack "needs permission to enable notifications" banner: 1. cef-helper (submodule update to 99a2686): context.eval() in on_context_created installs Object.defineProperty(navigator, 'permissions', ...) before any page JS runs. 2. ua_spoof.js: same Object.defineProperty pattern as belt-and-suspenders for frames that reload or trigger permission checks after on_load_end. Simple property assignment on Blink platform objects is silently ignored; Object.defineProperty on the navigator wrapper itself (the same mechanism already used for navigator.userAgent) works correctly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(notifications): address CodeRabbit review issues on PR #727 - Move raw_title out of log::info! (PII risk) — log title_chars at info, raw_title at debug only - Fix permissionChecked set before async invoke in ensureNotificationPermission so transient failures allow retry on next account open * fix(cef): enable webview-data-url feature for CEF placeholder URL The CEF backend uses a data: URL as the initial webview location so CDP can attach before the real provider URL loads. Tauri's add_child rejects data: URLs unless the webview-data-url feature is enabled. * fix(notifications): address remaining CodeRabbit issues on PR #727 - cdp/emulation: bump Chrome UA 124→136 to pass Slack browser check - cdp/session: inject Page.addScriptToEvaluateOnNewDocument to stub Notification.permission as "granted" and silence provider banners - notifications/mod.rs + core/all.rs: wire notifications domain into the controller registry (fixes unknown-method in json_rpc_e2e tests) - notifications/schemas: add skipped bool output to ingest schema - notifications/store: add tracing::warn on datetime parse failure - notificationService: union return type for ingestNotification - webviewAccountService: narrow union before accessing result.id - NotificationCenter: drive loading/error from fetch effect; track allProviders separately so filter pills don't collapse on selection - NotificationRoutingPanel: rollback optimistic update on save failure - useSettingsNavigation: add notifications/notification-routing routes - scripts/install.sh: remove silent dry-run exit 0 on asset failure - scripts/setup-dev-codesign.sh: remove unconditional -legacy flag - docs/SUMMARY.md: remove worktree path, fix macOS capitalisation, remove self-referential deletion note * chore: apply prettier + cargo fmt + fix useEffect dep warning Auto-apply formatting changes flagged by the pre-push hook: - prettier reformatted NotificationCenter.tsx and notificationService.ts - cargo fmt reformatted all.rs, openhuman/mod.rs, notifications/schemas.rs - NotificationRoutingPanel: move providers array to module scope so useEffect dependency array is satisfied without exhaustive-deps warning * feat(notifications): enhance notification management and permissions - Added new commands for managing notification preferences, including setting global Do Not Disturb (DND), muting specific accounts, and retrieving current bypass preferences. - Implemented a notification permission state handler to ensure consistent behavior across different environments. - Updated the JavaScript shim for notification permissions to handle both Notification and PushManager states, ensuring compatibility with various providers. - Refactored the WebviewAccountsState to include a new structure for managing notification bypass preferences, improving the overall notification handling logic. * update agents * fix(notifications): complete schema + navigation metadata for ingest/settings routes - app/src/components/settings/hooks/useSettingsNavigation.ts: resolve the new `/settings/notifications` and `/settings/notification-routing` URLs to their SettingsRoute values and feed them into breadcrumbs so the new panels don't silently fall through to `'home'`. Addresses CodeRabbit on useSettingsNavigation.ts:34. - src/openhuman/notifications/schemas.rs: add the optional `reason` output on `notification.ingest` (populated alongside `skipped=true` by the runtime) and the normalized `settings` output on `notification.settings_set` so schema-driven clients see the full response shape. Addresses CodeRabbit on schemas.rs:103 and schemas.rs:217. - src/core/all.rs: add a `notification` namespace_description so CLI help covers the new controllers, plus a test assertion. Addresses CodeRabbit on src/core/all.rs:149. * fix(notifications): trace DB entry, surface empty update matches, warn on bad scored_at - Add `tracing::trace!` checkpoints around the `with_connection` DB open and schema migration so notification-delivery issues are reconstructible from logs. - `update_triage` and `mark_read` now inspect `Connection::execute`'s affected-row count: log a `warn!` when the update matched zero rows (row deleted between ingest and scoring / client passed a stale id), `debug!` on the normal path. - `scored_at` parsing no longer silently drops malformed values — log a `warn!` with the raw value and parse error before treating the row as unscored, matching the existing behavior for `received_at`. Addresses CodeRabbit on store.rs (lines 72, 172, 294). * fix(webview): respect silent notifications, multi-host CDP fallback, shim idempotency - webview_accounts/mod.rs: honor the Web Notification `silent` flag. Previously we only logged it and still called `builder.show()`, so pages that marked a notification silent still produced an OS toast. Mirror event still fires so the in-app center updates; only the OS toast is suppressed. Also picks up a prior cargo-fmt rewrap. - cdp/target.rs: `browser_ws_url()` now continues the host loop when `resp.json()` fails instead of early-returning via `?`. A malformed response from the first host (CDP_HOST) no longer prevents the `localhost` fallback from being tried. - webview_accounts/ua_spoof.js: guard the Notification wrapper behind `window.__OH_NOTIF_SHIM` so repeated evaluations of the script (Page.addScriptToEvaluateOnNewDocument + frame-level re-injections) don't stack wrappers onto the same page globals or re-proxy `Function.prototype.toString`. Addresses CodeRabbit on webview_accounts/mod.rs:377, cdp/target.rs:33, and ua_spoof.js:176. * update agents * update --------- Co-authored-by: oxoxDev <nikhil@tinyhumans.ai> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai> |
||
|
|
1461d78509 |
feat(composio): inject connected identities into agent prompts (#774)
* chore(vendor): bump tauri-cef to feat/cef tip (073047817) * feat(composio): inject connected identities into agent prompts Standardize provider identity persistence with a shared identity_set hook and skill-scoped facet keys so connected account identity survives restarts and is merged into inference context. Add connected identity rendering in welcome, orchestrator, and integrations_agent prompts so agents can reference cross-platform user identity during conversations. Closes #691 Made-with: Cursor * fix(composio): resolve CodeRabbit identity prompt and cleanup findings Make connected-identity prompt injection deterministic via PromptContext, sanitize provider identity fields before prompt rendering, and clear persisted identity facets when Composio connections are removed to avoid stale context. Made-with: Cursor * fix(ci): satisfy format checks and dry-run installer smoke Apply rustfmt to touched Rust files and make install.sh dry-run exit successfully when no compatible release asset exists, so smoke validation remains informative without failing on missing artifacts. Made-with: Cursor |
||
|
|
581f37965c |
feat(skills): SKILL.md skills UI — browse, create, install from URL (#681) (#740)
* feat(skills/core): add read_skill_resource with size + traversal guards (#681) Introduces `read_skill_resource(skill_id, relative_path)` in the skills ops module. Used by the new `skills.read_resource` RPC (landed in a follow-up commit) to let the UI preview files bundled alongside a SKILL.md without having to shell out to the Node runtime. Guards rejecting each known attack surface have their own unit test: - empty skill_id / empty relative_path - unknown skill - absolute paths - `..` traversal escapes (checked after canonicalization against the skill root, reusing the pattern from the Node exec allowlist) - directory targets - symlinked leaves (reject via `symlink_metadata` before open) - files over the 128 KB cap - non-UTF-8 content (binary allowlist is text-only) Happy-path test covers a small text resource under the skill root. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(skills/core): wire skills.read_resource RPC + namespace (#681) Adds `skills` RPC namespace with `skills.list` and `skills.read_resource` handlers. `read_resource` delegates to `read_skill_resource` (previous commit) and surfaces path-traversal / size / encoding errors back to the caller verbatim so the UI can render the error string as-is. - `src/openhuman/skills/schemas.rs` (new): controller + schema definitions, plus unit tests for schema name stability, round-trip of the minimum `SkillSummary` fields, and controller list/schema length parity. - `src/openhuman/skills/mod.rs`: declare `pub mod schemas` and re-export `all_skills_controller_schemas`, `all_skills_registered_controllers`, and `skills_schemas`. - `src/core/all.rs`: register the controllers + schemas and add a namespace description so the RPC discovery endpoint surfaces it. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(app/api): typed skillsApi client for list + read_resource (#681) Thin typed wrapper around the `skills.list` and `skills.read_resource` RPCs added in the previous commit. The client normalises the backend response shape (bytes + UTF-8 content) and rethrows backend error strings verbatim so the preview pane can render them unchanged. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(app/ui): SkillResourceTree groups bundled resources by top dir (#681) Presentational component that takes the `resources: PathBuf[]` from a loaded Skill and renders it as a grouped list (scripts, assets, references, etc. based on the first path segment). Selecting a leaf calls `onSelect(relativePath)` so the parent drawer can drive preview state. Stateless — no fetching, no effects. Styling follows the stone/coral design tokens used across the Skills page. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(app/ui): SkillResourcePreview size-gated viewer + specs (#681) Presentational component that fetches a single bundled resource via `skillsApi.readSkillResource`. Backend caps payloads at 128 KB and either returns UTF-8 text or a plain error string, so the preview pane has three visual states: loading, error, success. - On error (e.g. "path escape", ">128KB", "non-UTF-8"), renders the backend message verbatim in a coral panel. - On success, renders a monospace pre block with the byte count in the footer. - `key={id:path}` on the mount site (in SkillDetailDrawer, next commit) drives a remount when the selected resource changes — so no setState-in-effect hack is needed to reset loading state. Vitest specs cover: loading state, success rendering with byte footer, error rendering for traversal / oversize / encoding strings, cancelled fetch guard on unmount. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(app/ui): SkillDetailDrawer right-side detail panel + specs (#681) Slide-in right-hand drawer that displays frontmatter metadata (description, version, author, license, tags, allowed_tools) and hosts SkillResourceTree + SkillResourcePreview. Opened by clicking a skill card on the Skills page (wired in the next commit). - Focus management: on mount, focuses the close button via `window.requestAnimationFrame` and restores the previously focused element on unmount. - Esc + backdrop click dismiss. - Preview pane is conditionally rendered and keyed on `${skill.id}:${selectedResource}` so changing the selected resource remounts the previewer (avoids setState-in-effect pattern). Vitest specs cover: render with frontmatter, resource tree click opens preview, close button / Esc / backdrop dismiss paths, focus restoration, empty-resources case. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(app/ui): open SkillDetailDrawer on skill card click (#681) Wires the Skills page to the new drawer: clicking a skill card sets `selectedSkill` state, which mounts `SkillDetailDrawer`. Dismissing the drawer clears the state. Cards gain an explicit "View details" affordance for discoverability. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(skills/core): add skills.create RPC for scaffolded SKILL.md authoring (#681) Adds `create_skill` in ops.rs plus the `skills.create` controller + handler in schemas.rs. Writes a minimal SKILL.md (with optional license/author/tags/ allowed-tools frontmatter) under the selected scope, scaffolds scripts/ references/assets subdirs, and re-discovers the skill to return the parsed SkillSummary. Legacy scope rejects; Project scope requires the trust marker; User scope is always allowed when a home directory is available. Hardened against path traversal the same way read_skill_resource is: canonicalize the scope root, canonicalize the target dir, reject unless the target starts with the root. Slug derivation is ASCII-only (collapse whitespace/ -/_ to a single hyphen, drop other chars, trim hyphens, enforce MAX_NAME_LEN). Tests (hermetic via create_skill_inner): - user-scope happy path (slug, metadata, SKILL.md on disk, subdirs) - slug collision rejection - invalid name (no alphanumerics) rejection - project-scope without trust marker rejection - project-scope with trust marker happy path - legacy-scope rejection - empty-description rejection - slugify edge cases Closes part of #681 (backend scope for create flow). * feat(skills/core): add skills.install_from_url RPC via npx skills add (#681) Introduces `install_skill_from_url(url, timeout_secs?)` — a JSON-RPC method that shells out to `npx --yes skills add <url>` under the managed Node runtime so the UI can install published SKILL.md packages directly. Security posture: - https scheme only (no http, file, ssh, git+https…) - Rejects `localhost`, `*.localhost`, `*.local`, RFC1918 private IPv4, loopback, link-local, multicast, broadcast, unspecified, 100.64/10 CGN, 0.0.0.0/8, and IPv6 loopback/unspecified/multicast, fc00::/7 ULA, fe80::/10 link-local. Explicitly covers 169.254.169.254 cloud metadata. - Trims + caps URL at 2048 chars; parses with the `url` crate. - IPv6 brackets stripped from `host_str()` before address parse. Process posture: - Reuses `NodeBootstrap` so the managed toolchain resolves first. - `env_clear()` + explicit PATH injection (bootstrap bin_dir first) + a narrow safe-env allow-list (HOME, TERM, LANG, LC_ALL, LC_CTYPE, USER, SHELL, TMPDIR). Matches the npm_exec pattern from #723. - Default 60s wall-clock timeout, capped at 600s. - Captures stdout/stderr; returns both on success or failure. - Diff-based `new_skills`: snapshots discovered skills pre-install and reports slugs that appear post-install. Surface: - JSON-RPC: `openhuman.skills_install_from_url` params: { url: string, timeout_secs?: number } result: { url, stdout, stderr, new_skills[] } - Wired into `all_skills_controller_schemas()` and `all_skills_registered_controllers()`. Tests (5 new unit tests, all pass — 51/51 in skills::): - validate_install_url_accepts_public_https - validate_install_url_rejects_non_https_scheme (http, file, ftp, ssh, git+https, javascript) - validate_install_url_rejects_empty_and_oversized - validate_install_url_rejects_private_and_loopback (20 URLs inc. CGN, cloud metadata, IPv6 ULA/link-local/loopback/multicast) - validate_install_url_rejects_malformed (missing scheme, empty host, non-https scheme, unparseable bracketed host) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(app/api): skillsApi.createSkill + installSkillFromUrl wrappers (#681) Adds typed frontend wrappers for the two new skill-authoring RPC methods: - `skillsApi.createSkill(input)` — scaffolds a new SKILL.md skill via `openhuman.skills_create`. Accepts camelCase `allowedTools` and rekeys it to the `allowed-tools` spelling the SKILL.md frontmatter convention expects, matching `SkillsCreateParams` in `src/openhuman/skills/schemas.rs`. Optional fields are only sent when explicitly provided so the Rust `#[serde(default)]` defaults apply cleanly. - `skillsApi.installSkillFromUrl(input)` — installs a published skill package via `openhuman.skills_install_from_url`. Accepts camelCase `timeoutSecs` and rekeys it to `timeout_secs`. Normalizes the response (snake_case `new_skills` -> camelCase `newSkills`, missing list -> []). Both wrappers reuse the existing `unwrapEnvelope` helper so they tolerate either a bare RPC payload or the `{ data: … }` envelope some transports emit. Adds `CreateSkillInput`, `InstallSkillFromUrlInput`, and `InstallSkillFromUrlResult` type exports for downstream modal components. Tests (vitest, 6 new specs, all pass): - createSkill forwards inputs and rekeys allowedTools - createSkill omits optional fields when absent - createSkill unwraps envelope responses - installSkillFromUrl forwards url and rekeys timeoutSecs - installSkillFromUrl omits timeout_secs + defaults newSkills to [] - installSkillFromUrl unwraps envelope responses Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(app/ui): CreateSkillModal for scaffolding SKILL.md skills (#681) Adds a centered white modal that scaffolds a new SKILL.md skill via `skillsApi.createSkill`, matching the settings-modal design rules (520px desktop, 16px radius, backdrop+blur, Escape/click-out to close, focus capture). Form fields mirror the Rust `SkillsCreateParams` schema: - name (required) — display name, also slugified into the on-disk directory; a live slug preview surfaces what will hit disk - description (required) — short prose; written as the `description:` field in the generated YAML frontmatter - scope (user | project radio) — `legacy` is hidden because that layout is read-only and being phased out - license (optional) — free-form SPDX-style string - author (optional) - tags (optional, CSV) — normalised client-side; empty entries dropped - allowedTools (optional, CSV) — rekeyed to `allowed-tools` on the JSON-RPC wire by `skillsApi.createSkill` The slug preview mirrors `slugify_skill_name` on the Rust side (lowercase ASCII alnum + `-`, collapse repeats, trim edge hyphens) so the user sees what the Rust slugifier will produce; the Rust side stays authoritative when the skill is persisted. On success `onCreated(skill)` fires with the freshly-discovered `SkillSummary`, letting the parent grid insert the new row without a full refetch. On failure the Rust error string is surfaced verbatim in a coral-styled alert and the submit button re-enables. Vitest specs cover: required-field rendering, live slug preview, submit-disabled gating, Escape close, wire-format rekey of `allowedTools` → `'allowed-tools'`, `onCreated` dispatch, and error-banner recovery. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(app/ui): InstallSkillDialog for npx skills add <url> (#681) Adds a centered white modal that installs a published skill package via `skillsApi.installSkillFromUrl`. The Rust side shells out to `npx --yes skills add <url>` under the managed Node toolchain, with an allow-list on the URL (https only, no private/loopback/link-local/ multicast/cloud-metadata hosts) and a wall-clock timeout (default 60s, max 600s). UI contract: - Single URL input plus optional timeout in seconds. - Client-side `isLikelyValidUrl` fails fast on non-https URLs so the user doesn't pay a round-trip for shape errors the Rust side would reject anyway; the Rust side remains authoritative. - Timeout field validates `1 <= n <= 600` client-side to mirror the server-side clamp range. - While the RPC is in flight we render a spinner with "Running `npx skills add`…" copy and disable close / backdrop dismiss so we don't orphan the subprocess. - On success we surface the list of `newSkills` (ids that appeared post-install) plus captured stdout/stderr panes inside collapsible <details> elements, then hand the full result back to the caller via `onInstalled` so the parent can refetch the skills list and auto-select the new row. - On failure the Rust error string is rendered verbatim in a coral alert and the submit button re-enables. Vitest specs cover: required-field rendering, URL shape gating (empty, malformed, http://, https://), timeout range validation, `timeoutSecs` forwarding on submit, success panel with newSkills rendering, blank timeout omitted from payload, and error-banner recovery. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(app/ui): wire New skill + Install from URL into Skills page (#681) Adds a header row on the Skills page with two buttons: - **New skill** → opens `CreateSkillModal` - **Install from URL** → opens `InstallSkillDialog` Extracts the existing `listSkills` effect into a reusable `refreshDiscoveredSkills` helper so both new flows can reconcile their results against the freshly-discovered `SkillSummary` rows rather than relying on the optimistic payload from the RPC alone. Create flow: - Optimistically appends the returned `SkillSummary` to `discoveredSkills` (dedupe by id). - Auto-opens the detail drawer for the new skill so the user lands in context — matches the install flow's UX. - Follows up with `refreshDiscoveredSkills()` so version/author/ warnings picked up by the Rust discoverer end up in state too. Install flow: - Always refreshes the list (the install can add multiple skills if the package declares several). - Auto-opens the detail drawer for the first newly-installed skill when at least one id is reported back; otherwise leaves the grid in its refreshed state. Both buttons sit in a flush `max-w-lg` header above the existing search bar, styled consistent with `UnifiedSkillCard` CTAs — ocean primary for "New skill" (positive action), neutral stone for "Install from URL" (secondary). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor(skills/core): direct SKILL.md fetch replaces npx skills add (#681) Installer no longer shells out to the vercel-labs/skills CLI. It now fetches SKILL.md over HTTPS, validates YAML frontmatter, and writes into the user's skills dir. Size cap (1 MiB), timeout clamp (1-600s), GitHub blob->raw URL normalization, and path-traversal guards are covered by unit tests. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(app/ui): install dialog copy + categorized errors for direct fetch (#681) Dialog subtitle, helper text, and in-flight indicator reflect the new direct SKILL.md fetch flow. Errors from the core are categorized into friendly titles (URL rejected, too large, timeout, parse failure, already installed, write failed) with the raw backend message tucked under a details disclosure. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * test(app/ui): dialog specs cover direct-fetch fixtures + error categorization (#681) Fixtures updated to raw GitHub SKILL.md URLs. New cases assert the categorization helper surfaces the right title for invalid SKILL.md, unsupported URL form, and unknown backend errors (raw text hidden under details). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(skills/core): install_from_url writes to user scope (#681) Project scope (`<ws>/.openhuman/skills/`) is gated on a `<ws>/.openhuman/trust` marker that the workspace rarely has, so freshly-installed skills were invisible to `skills.list` until the user opted the workspace into trust. Route installs to `~/.openhuman/skills/<slug>` — the user-scope root that `discover_skills` always scans — so "Install from URL" surfaces the new skill immediately. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * docs(skills): align install_from_url docs with direct-fetch impl (#681) `install_from_url` stopped shelling out to `npx --yes skills add <url>` when it was rewritten to fetch SKILL.md over HTTPS directly, but schema descriptions and SDK wrappers still described the old subprocess flow. Fix the module-level rustdoc, the JSON-RPC schema `description`/`comment` fields, and the TS client wrapper doc comments so the surface documents what actually runs. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(skills/core): DNS-to-private-IP SSRF guard + install rollback (#681) Two fixes surfaced by CodeRabbit review on PR #740: * `validate_install_url` only inspected literal-IP hosts, so a public-looking hostname like `evil.example.com` with an A record pointing at `127.0.0.1` / `169.254.x` / etc. would still be handed to `reqwest`. Resolve the host via `tokio::net::lookup_host` before the GET and reject if any returned address falls in loopback / private / link-local / multicast / unspecified ranges. Document the remaining DNS-rebinding gap (pinning to a `SocketAddr` + custom reqwest resolver is tracked separately). * If `std::fs::write` or `std::fs::rename` fails after `create_dir_all` succeeded, the empty/partial target directory used to survive and permanently block retries under the same slug. Wrap the write+rename in a rollback that removes the temp file + the just-created directory on failure (best-effort; cleanup errors are logged and the original write error is surfaced). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * style(skills): cargo fmt break long Host::Ipv6 conditional (#681) CI's cargo fmt (stable) rewraps the long `.map(..).unwrap_or(false)` chain that passed local fmt. Apply the break so the pre-push hook and the upstream lint job agree. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
32b5afd1d9 |
feat(memory): topic trees (lazy hotness-driven materialisation) (#709) (#799)
Phase 3c of the memory architecture (umbrella #711). Adds per-entity topic trees spawned lazily when an entity's hotness crosses a threshold, so Phase 4 retrieval can resolve "what did Alice say about Phoenix?" without scanning the full chunk pool. ## What's in this commit New module `src/openhuman/memory/tree/topic_tree/`: - `types.rs` — `EntityIndexStats` (hotness input), `HotnessCounters` (the persisted row) and the `TOPIC_CREATION_THRESHOLD=10.0`, `TOPIC_ARCHIVE_THRESHOLD=2.0`, `TOPIC_RECHECK_EVERY=100` constants. - `hotness.rs` — pure arithmetic scorer plus deterministic `hotness_at(entity_id, stats, now_ms)` variant for tests, and a piecewise-linear `recency_decay` helper. - `store.rs` — SQLite helpers (`get`, `get_or_fresh`, `upsert`, `distinct_sources_for`, `count`) for the new `mem_tree_entity_hotness` table. - `registry.rs` — `get_or_create_topic_tree`, `force_create_topic_tree`, `list_topic_trees`, `archive_topic_tree`. Race-recovers on UNIQUE violations via `is_unique_violation`, mirroring `source_tree::registry`. - `curator.rs` — `maybe_spawn_topic_tree` (per-ingest tick) and `force_recompute` (admin path). Implements the `TOPIC_RECHECK_EVERY`-gated recompute: refresh `distinct_sources` from the entity index, compute hotness, spawn + backfill if threshold crossed. - `backfill.rs` — `backfill_topic_tree` walks the entity index and routes every historic leaf (ts-ASC ordered) through `source_tree::bucket_seal::append_leaf`. Capped at 500 leaves. Skips summary-node hits so topic trees only ever ingest raw leaves. Missing chunks log a warn and are skipped, never failing the spawn. - `routing.rs` — `route_leaf_to_topic_trees` fans a kept leaf out to every active matching topic tree and ticks the curator for each entity. Archived trees are skipped but counters still bump. Wired from ingest (`tree/ingest.rs::append_leaves_to_tree`) AFTER the source-tree `append_leaf` succeeds — non-fatal on error, logged at warn level so routing issues never poison the ingest hot path. ## Hotness scoring (pure) ``` hotness = ln(mentions + 1) // dampened volume + 0.5 * distinct_sources // cross-source bonus + recency_decay(last_seen) // 1.0 @ day 0 → 0 @ day 30 + graph_centrality // Phase 4+; None → 0 + 2.0 * query_hits // retrieval feedback; Phase 4+ ``` `graph_centrality` and `query_hits_30d` columns are persisted but the increment code paths are deferred to later phases, as planned. ## Schema (additive, idempotent) New table `mem_tree_entity_hotness` (keyed on `entity_id`) added to the Phase 1 `SCHEMA` constant in `tree/store.rs` so it migrates through the same `with_connection` path as the existing tables. `CREATE TABLE IF NOT EXISTS` keeps it re-run-safe. An ancillary index on `last_hotness` supports future sweep queries. ## Routing The ingest path is the only non-admin caller. After `append_leaf` puts a leaf in the source tree, `route_leaf_to_topic_trees` runs with the chunk's canonical entity list. For each entity: 1. If an active topic tree exists, append the leaf to it (reusing `source_tree::bucket_seal::append_leaf` with `entities=[entity_id]`). 2. Tick `maybe_spawn_topic_tree` — may bump counters or, on cadence, recompute hotness and spawn + backfill a new tree. Per-entity errors are caught and logged; a top-level failure is demoted to a warn in the ingest caller so the source-tree append always wins. ## Reuses from Phase 3a - `source_tree::bucket_seal::append_leaf` — same `&Tree` API works for `TreeKind::Topic` end-to-end. - `source_tree::summariser::{Summariser, InertSummariser}` — honest stub emits empty entity/topic vecs on summary nodes. - `mem_tree_trees` / `mem_tree_summaries` / `mem_tree_buffers` schema, discriminated by `kind = 'topic'` and `scope = <entity canonical id>`. - Registry race recovery via `is_unique_violation` (catches UNIQUE on `insert_tree`, re-queries on collision). - Idempotent append (duplicate `item_ids` in the L0 buffer are no-ops), so backfill is safe to re-run. ## Tests 40 new tests (10 hotness, 3 types, 6 store, 7 registry, 4 curator, 4 backfill, 5 routing, 1 integration). `memory::tree` suite: 201 passing, 0 regressions (Phase 3a baseline was 161 → +40 new). Coverage highlights: - Hotness pure math (zero-entity, spike-over-threshold, old-but-widely- cited retains signal, query-hit boost, recency decay edges). - Curator: first-ingest-just-bumps, no-spawn-below-threshold, spawn-fires-exactly-once-when-crossed, cadence gating. - Backfill: appends-all-entity-leaves, skips missing chunks, idempotent, skips summary nodes. - Routing: empty-entities-noop, appends-to-existing-tree, archived-tree- skipped, multi-entity-fan-out, end-to-end-integration-materialisation. - Registry: idempotent get-or-create, UNIQUE race recovery, archive flips status (not deletion), kind/scope cleanly separated from source. ## Deliberate non-goals (deferred) - No JSON-RPC surface (out of scope for Phase 3c core). - No archive cron sweep — `archive_topic_tree` primitive only. - `graph_centrality` / `query_hits_30d` increments deferred to later phases (columns exist, reads work, writes are Phase 4+). ## Stacked on #789 Base is `feat/709-summary-trees`. Merge #789 first, then rebase this branch. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
48897af9b3 |
feat(memory): global activity digest tree (#709) (#798)
Phase 3b of the memory architecture (umbrella #711). Adds a singleton cross-source `global` tree whose purpose is time-windowed recap ("what did I do in the last 7 days?") via a time-axis-aligned hierarchy: L0 = day, L1 = week (7 dailies), L2 = month (4 weeklies), L3 = year (12 monthlies). ## What's in this PR - `global_tree/registry.rs` — singleton `get_or_create_global_tree` with the same UNIQUE-race recovery pattern Phase 3a uses for source trees (scope is the literal `"global"`). - `global_tree/digest.rs` — `end_of_day_digest(config, day, summariser)` walks every active source tree, picks one representative contribution per tree (latest L1+ intersecting the day, fallback to root), folds them with the Summariser trait into one L0 daily node, inserts it into `mem_tree_summaries`, and triggers the L0→L1→L2→L3 cascade. Idempotent on re-run (returns `DigestOutcome::Skipped` when an L0 already exists for the day). - `global_tree/seal.rs` — count-based cascade-seal with thresholds 7 (weekly), 4 (monthly), 12 (yearly). Transactional append with idempotency on (tree_id, level, item_id) to survive partial retries. - `global_tree/recap.rs` — `recap(config, window)` maps the window to a level (<2d→L0, <14d→L1, <60d→L2, else L3), fetches covering summaries, and falls back downward when the chosen level has no sealed material yet (reports `level_used` so callers can surface "best available"). Empty tree returns `None`. - `source_tree/store.rs` — adds `list_trees_by_kind` used by the digest to enumerate source trees. - `tree/mod.rs` — exports the new `global_tree` module. ## Reuses from Phase 3a - `source_tree::store` CRUD for `mem_tree_trees` / `mem_tree_summaries` / `mem_tree_buffers` (no new schema tables). - `source_tree::summariser::{Summariser, SummaryContext, SummaryInput, InertSummariser}` — honest-stub entity/topic semantics kept as-is. - `source_tree::registry::new_summary_id` for id generation. - `source_tree::types::{Tree, SummaryNode, Buffer, TreeKind::Global, TreeStatus}` — `TreeKind::Global` was already declared in Phase 3a. - Entity backfill via `tree::score::store::index_summary_entity_ids_tx` so Phase 4 retrieval can resolve "summaries mentioning X" through the same inverted index as leaves. ## Design decision: seal.rs kept as parallel impl `source_tree::bucket_seal::cascade_all_from` uses a token-budget seal policy (`TOKEN_BUDGET`), not pluggable. The global tree needs count-based thresholds per level. Rather than refactoring the stable Phase 3a seal pipeline to accept a policy (regression risk), we keep `global_tree/seal.rs` as a parallel count-based implementation that routes through the same `source_tree::store` primitives. The shape of the transaction is intentionally identical so future consolidation is straightforward when we're ready to touch the Phase 3a seal path. ## Tests 15 new tests; 176 total `memory::tree::*` passing (was 161). Coverage: - registry idempotency, ID prefix, UNIQUE-race recovery - digest empty-day, populated-day, rerun idempotency, 7-day weekly cascade - seal below-threshold, weekly-threshold, append idempotency - recap level selection across all four bands, empty-tree, L0 fallback, L1 when sealed ## Stacked on #789 Base is `feat/709-summary-trees` (Phase 3a). Merge #789 first, then rebase this branch. PR body should flag the stack order. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
37a73df0c0 |
feat(memory): source trees + bucket-seal foundation (#709) (#789)
* feat(memory): LLM-based NER + importance signal with cheap-signals short-circuit (#708)
Adds an Ollama-backed entity extractor and an LLM-derived importance
score as a new signal in the admission gate. Builds on merged Phase 2
(#733) without changing its surface — additive only.
Why LLM (not GLiNER): zero new deps (reuses existing reqwest + async
infra), reuses the same Ollama setup openhuman uses for embeddings,
better per-entity quality scales with model choice, no native ONNX
runtime to ship. Latency cost (~100-300ms per chunk on a small model
like qwen2.5:0.5b) is amortised by the short-circuit.
The admission gate stays a hybrid - cheap deterministic signals
always run; the LLM is one signal among many, never the sole arbiter.
Backwards-compatible: with default SignalWeights the LLM weight is 0.0
and behaviour matches pre-LLM Phase 2 exactly.
What's new:
- src/openhuman/memory/tree/score/extract/llm.rs: LlmEntityExtractor
implementing the existing EntityExtractor trait. Posts a structured
JSON request to an Ollama-compatible /api/chat endpoint asking for
NER + an importance rating in one call. Span recovery via string
search; hallucinated entities (surface not in source text) dropped.
Soft fallback: HTTP failures log a warn and return empty extraction.
- ExtractedEntities gains llm_importance (Option<f32>) +
llm_importance_reason (Option<String>); merge() takes max importance.
- ScoreSignals gains llm_importance (f32, defaults to 0.0).
- SignalWeights gains llm_importance (default 0.0; with_llm_enabled()
helper sets it to 2.0 - comparable to metadata/source weights).
- signals::combine_cheap_only(): variant that excludes the LLM signal,
used for the short-circuit decision in score_chunk.
- ScoringConfig gains llm_extractor (Option<Arc<dyn EntityExtractor>>),
definite_keep_threshold (default 0.85), definite_drop_threshold
(default 0.15). New with_llm_extractor() constructor.
- score_chunk() pipeline:
1. Always-on regex extraction
2. Cheap signals + combine_cheap_only -> cheap_total
3. If cheap_total >= definite_keep OR <= definite_drop: skip LLM
4. Else (borderline band): run LLM extractor, merge results, recompute
5. Final combine + admission gate against drop_threshold
- mem_tree_score table gains nullable llm_importance + llm_importance_reason
columns via idempotent ALTER TABLE migration.
- ScoreRow + upsert/get persist the new column.
What's not changed:
- Existing JSON-RPC surface
- Default behaviour (LLM weight=0, no llm_extractor configured = same
outputs as before)
- mem_tree_chunks / mem_tree_entity_index schemas
Tests added:
- LLM extractor: prompt construction, JSON parsing, hallucination
drop, importance clamping, strict vs lenient unknown-kind handling
- Score pipeline: short-circuit on definite_keep/definite_drop skips
LLM call (verified via FakeLlm call counter); borderline band
consults LLM once; LLM failure falls back gracefully without erroring
LLM-NER work follows up on #708. Parent: #711.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(memory): address CI failures and CodeRabbit review on LLM-NER (#708)
CI was red on PR #775 due to 10 E0063 errors — existing test-only struct
literals for ExtractedEntities / ScoreSignals / ScoreRow weren't updated
when the new optional llm_importance fields landed. Local cargo check
on lib-only passed; cargo check --tests caught them, as does CI.
CodeRabbit also flagged four semantic issues:
1. LlmEntityExtractor::extract() returned Err on HTTP transport, non-2xx
status, and JSON parse failures. The soft-fallback contract (documented
in the module header) only worked because score_chunk catches errors;
any other caller got a hard error. Now split into extract_or_empty()
which logs a warn on each failure mode and returns
ExtractedEntities::default() unconditionally. The public extract()
stays on the async-trait surface but never returns Err.
2. find_char_span() always searched from byte offset 0, so when the LLM
returned the same surface twice (explicitly asked for duplicates in
the prompt), both entities got (start=0, end=len) — same span, not
distinct mentions. Added find_char_span_from(haystack, needle,
byte_from, char_from) that resumes from a cursor. into_extracted_entities
now tracks a per-surface (byte_after, char_after) cursor in a
HashMap<String, (usize, u32)>, so duplicate mentions get distinct
non-overlapping spans and over-claim (LLM returns 3 "Alice" when
source has 2) drops the excess entries with a debug log.
3. combine() always included w.llm_importance in the denominator even
when llm_importance=0.0 because the LLM didn't run. That artificially
lowered the total on short-circuit paths. score_chunk now branches on
llm_consulted: uses combine() when LLM ran (importance actually
contributes), combine_cheap_only() when LLM was skipped or failed
(denominator excludes the LLM weight). Observable in two new tests:
short_circuit_reports_cheap_only_total verifies r.total ==
combine_cheap_only(r.signals, weights) and strictly exceeds
combine(r.signals, weights) when llm_importance=0; llm_consulted_
reports_full_total verifies the opposite path.
4. Same issue from a different angle (CodeRabbit flagged both). Fixed
by the same llm_consulted branch.
Test-literal fixes:
- signals/ops.rs: ScoreSignals literals pick up llm_importance field;
make_entities helper uses ..Default::default() on ExtractedEntities.
- extract/types.rs: three test ExtractedEntities literals gain
llm_importance: None, llm_importance_reason: None.
- resolver.rs: canonicalise_batch_preserves_spans literal gains the
two fields.
- score/store.rs: sample_row gains signals.llm_importance and
llm_importance_reason.
New tests (7 added):
- find_char_span_from_advances_past_prior_match
- find_char_span_from_returns_none_after_exhaustion
- find_char_span_from_preserves_utf8
- find_char_span_from_rejects_non_char_boundary
- into_extracted_entities_gives_distinct_spans_to_duplicate_mentions
- into_extracted_entities_drops_extra_duplicate_when_source_only_has_one
- extract_soft_fallback_on_unreachable_endpoint (transport failure →
empty extraction, not Err)
- short_circuit_reports_cheap_only_total
- llm_consulted_reports_full_total
cargo check --lib clean; cargo fmt clean. The integration-test rmeta
errors visible locally are stale-metadata artifacts from incremental
builds with prior struct shapes; CI's clean build resolves them and
tests/*.rs files do not construct any of the touched types.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(memory): source trees + bucket-seal foundation (#709)
Phase 3a of the memory architecture (#711 umbrella). Lifts admitted leaves
into a per-source hierarchy so Phase 4 retrieval has a tree to walk.
## What's in this PR
* **Schema** — three new tables in `chunks.db` alongside Phase 1/2:
`mem_tree_trees`, `mem_tree_summaries`, `mem_tree_buffers`. Plus a
`parent_summary_id` backlink column on `mem_tree_chunks`. All migrations
are additive and idempotent (`ALTER TABLE ADD COLUMN`, same pattern as
Phase 2).
* **`source_tree/` module** — isolated subdir; does not touch the legacy
`openhuman::memory` layer.
- `types.rs` — `Tree`, `SummaryNode`, `Buffer`, `TreeKind` (Source/Topic/Global),
`TreeStatus`. Constants: `TOKEN_BUDGET = 10_000`, `DEFAULT_FLUSH_AGE_SECS = 7d`.
- `store.rs` — CRUD for all three tables via the shared `with_connection`
entry point. Writes are transactional; summary insert is idempotent
on primary key (INSERT OR IGNORE).
- `registry.rs` — `get_or_create_source_tree(scope)` — idempotent lookup
keyed on `(kind, scope)`.
- `bucket_seal.rs` — `append_leaf` + cascade: push a leaf into L0, seal
when `token_sum >= TOKEN_BUDGET`, recurse upward. One seal = one
transaction; children get a parent backlink (leaves via
`parent_summary_id`, summaries via `parent_id`). Tree's `max_level`
and `root_id` bump on root split.
- `flush.rs` — `flush_stale_buffers(max_age)` force-seals any buffer
whose `oldest_at` is older than `max_age`. Primitive only; wiring
into a scheduler is out of scope.
- `summariser/` — `Summariser` trait + `InertSummariser` fallback
(concatenate children, union entities preserving first-seen order,
hard-truncate to budget). Trait is async; Ollama implementation slots
in later without breaking callers.
* **Ingest wiring** — `tree/ingest.rs::persist` now calls `append_leaf`
once per kept chunk after chunks + scores land. Failures are logged at
warn level and do not fail the ingest — leaves are already persisted
and the next flush can still rebuild the tree.
## Concurrency / LLM
The async summariser call happens OUTSIDE any DB transaction, so a slow
LLM never holds SQLite locks. Blocking DB calls inside `append_leaf` are
acceptable for Phase 3a because the Inert summariser does no real I/O;
when a networked summariser lands, wrap the DB calls in
`tokio::task::spawn_blocking`.
## Tests (23 new, all green)
* `types` — enum round-trips, buffer staleness predicates
* `store` — tree insert + unique scope, summary insert + idempotence,
buffer upsert/clear, stale-buffer ordering
* `registry` — `get_or_create` idempotence, distinct scopes yield distinct
trees, id prefix format
* `summariser/inert` — provenance-prefixed concat, entity union with
order preservation, budget truncation, empty-content skip
* `bucket_seal` — append-below-budget is buffered only (no seal);
12k-token cross-over produces an L1 summary with correct `child_ids`,
L0 cleared, L1 carries the new summary id, tree `max_level`/`root_id`
updated, leaf → parent backlink populated
* `flush` — stale buffer force-seals under budget; recent buffer is a no-op
Broader `memory::tree` suite: 160 tests pass (23 new + 137 existing Phase
1/2), no regressions.
## Stacked on #775
This PR applies on top of `feat/708-memory-llm-ner` (PR #775, Phase 2
LLM-NER follow-up). Merge #775 first; this branch will rebase cleanly
onto main.
## Out of scope (tracked separately)
* **3b — Global activity digest tree** — time-aligned cross-source recap
* **3c — Topic trees** — hotness-driven per-entity materialisation
* **Phase 4 (#710)** — retrieval / query / gate
Closes part of #709 (source-tree foundation).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* style(memory): cargo fmt on source_tree module (#709)
CI rustfmt caught a few multi-line function signatures and import
groups that rustfmt wants collapsed / reordered. No behaviour change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(memory): address CodeRabbit review on source-tree (#709)
Three correctness fixes from CodeRabbit on PR #789, plus a design
clarification for the InertSummariser. False-positive findings
(schema-out-of-sync, rustfmt — already in
|
||
|
|
2b53566283 |
feat(memory): LLM-based NER + importance signal with cheap-signals short-circuit (#708) (#775)
* feat(memory): LLM-based NER + importance signal with cheap-signals short-circuit (#708) Adds an Ollama-backed entity extractor and an LLM-derived importance score as a new signal in the admission gate. Builds on merged Phase 2 (#733) without changing its surface — additive only. Why LLM (not GLiNER): zero new deps (reuses existing reqwest + async infra), reuses the same Ollama setup openhuman uses for embeddings, better per-entity quality scales with model choice, no native ONNX runtime to ship. Latency cost (~100-300ms per chunk on a small model like qwen2.5:0.5b) is amortised by the short-circuit. The admission gate stays a hybrid - cheap deterministic signals always run; the LLM is one signal among many, never the sole arbiter. Backwards-compatible: with default SignalWeights the LLM weight is 0.0 and behaviour matches pre-LLM Phase 2 exactly. What's new: - src/openhuman/memory/tree/score/extract/llm.rs: LlmEntityExtractor implementing the existing EntityExtractor trait. Posts a structured JSON request to an Ollama-compatible /api/chat endpoint asking for NER + an importance rating in one call. Span recovery via string search; hallucinated entities (surface not in source text) dropped. Soft fallback: HTTP failures log a warn and return empty extraction. - ExtractedEntities gains llm_importance (Option<f32>) + llm_importance_reason (Option<String>); merge() takes max importance. - ScoreSignals gains llm_importance (f32, defaults to 0.0). - SignalWeights gains llm_importance (default 0.0; with_llm_enabled() helper sets it to 2.0 - comparable to metadata/source weights). - signals::combine_cheap_only(): variant that excludes the LLM signal, used for the short-circuit decision in score_chunk. - ScoringConfig gains llm_extractor (Option<Arc<dyn EntityExtractor>>), definite_keep_threshold (default 0.85), definite_drop_threshold (default 0.15). New with_llm_extractor() constructor. - score_chunk() pipeline: 1. Always-on regex extraction 2. Cheap signals + combine_cheap_only -> cheap_total 3. If cheap_total >= definite_keep OR <= definite_drop: skip LLM 4. Else (borderline band): run LLM extractor, merge results, recompute 5. Final combine + admission gate against drop_threshold - mem_tree_score table gains nullable llm_importance + llm_importance_reason columns via idempotent ALTER TABLE migration. - ScoreRow + upsert/get persist the new column. What's not changed: - Existing JSON-RPC surface - Default behaviour (LLM weight=0, no llm_extractor configured = same outputs as before) - mem_tree_chunks / mem_tree_entity_index schemas Tests added: - LLM extractor: prompt construction, JSON parsing, hallucination drop, importance clamping, strict vs lenient unknown-kind handling - Score pipeline: short-circuit on definite_keep/definite_drop skips LLM call (verified via FakeLlm call counter); borderline band consults LLM once; LLM failure falls back gracefully without erroring LLM-NER work follows up on #708. Parent: #711. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(memory): address CI failures and CodeRabbit review on LLM-NER (#708) CI was red on PR #775 due to 10 E0063 errors — existing test-only struct literals for ExtractedEntities / ScoreSignals / ScoreRow weren't updated when the new optional llm_importance fields landed. Local cargo check on lib-only passed; cargo check --tests caught them, as does CI. CodeRabbit also flagged four semantic issues: 1. LlmEntityExtractor::extract() returned Err on HTTP transport, non-2xx status, and JSON parse failures. The soft-fallback contract (documented in the module header) only worked because score_chunk catches errors; any other caller got a hard error. Now split into extract_or_empty() which logs a warn on each failure mode and returns ExtractedEntities::default() unconditionally. The public extract() stays on the async-trait surface but never returns Err. 2. find_char_span() always searched from byte offset 0, so when the LLM returned the same surface twice (explicitly asked for duplicates in the prompt), both entities got (start=0, end=len) — same span, not distinct mentions. Added find_char_span_from(haystack, needle, byte_from, char_from) that resumes from a cursor. into_extracted_entities now tracks a per-surface (byte_after, char_after) cursor in a HashMap<String, (usize, u32)>, so duplicate mentions get distinct non-overlapping spans and over-claim (LLM returns 3 "Alice" when source has 2) drops the excess entries with a debug log. 3. combine() always included w.llm_importance in the denominator even when llm_importance=0.0 because the LLM didn't run. That artificially lowered the total on short-circuit paths. score_chunk now branches on llm_consulted: uses combine() when LLM ran (importance actually contributes), combine_cheap_only() when LLM was skipped or failed (denominator excludes the LLM weight). Observable in two new tests: short_circuit_reports_cheap_only_total verifies r.total == combine_cheap_only(r.signals, weights) and strictly exceeds combine(r.signals, weights) when llm_importance=0; llm_consulted_ reports_full_total verifies the opposite path. 4. Same issue from a different angle (CodeRabbit flagged both). Fixed by the same llm_consulted branch. Test-literal fixes: - signals/ops.rs: ScoreSignals literals pick up llm_importance field; make_entities helper uses ..Default::default() on ExtractedEntities. - extract/types.rs: three test ExtractedEntities literals gain llm_importance: None, llm_importance_reason: None. - resolver.rs: canonicalise_batch_preserves_spans literal gains the two fields. - score/store.rs: sample_row gains signals.llm_importance and llm_importance_reason. New tests (7 added): - find_char_span_from_advances_past_prior_match - find_char_span_from_returns_none_after_exhaustion - find_char_span_from_preserves_utf8 - find_char_span_from_rejects_non_char_boundary - into_extracted_entities_gives_distinct_spans_to_duplicate_mentions - into_extracted_entities_drops_extra_duplicate_when_source_only_has_one - extract_soft_fallback_on_unreachable_endpoint (transport failure → empty extraction, not Err) - short_circuit_reports_cheap_only_total - llm_consulted_reports_full_total cargo check --lib clean; cargo fmt clean. The integration-test rmeta errors visible locally are stale-metadata artifacts from incremental builds with prior struct shapes; CI's clean build resolves them and tests/*.rs files do not construct any of the touched types. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
b3b451fd28 |
fix(composio): keep chat integrations cache in sync on Windows (#749)
On Windows the `ComposioConnectionCreated` → `wait_for_connection_active` invalidation path often misses: the 60 s readiness poll can expire before the user finishes the hosted OAuth flow (Defender SmartScreen, slower browser launch, extra consent dialogs), so `invalidate_connected_integrations_cache` never fires and the chat runtime stays frozen on the pre-connect snapshot while Settings correctly shows "Connected" via its 5 s `listConnections` poll. Layer two cross-platform defenses on top of the existing event-bus path: 1. `composio_list_connections` (the RPC the UI polls every 5 s) now diffs the backend's active-toolkit set against every cached entry and invalidates on divergence, so chat converges to truth within one poll interval — regardless of how the OAuth round-trip completed. 2. Add a 60 s TTL on the `INTEGRATIONS_CACHE` so stale entries can't live forever even if nothing polls. 3. Grep-friendly `[composio][integrations]` logs at every decision point (cache hit / miss / expiry / divergence diff) for quick diagnosis from user-supplied debug dumps. Also covers the regression with six new unit tests (activate, disconnect, steady state, non-active rows, CONNECTED vs ACTIVE alias, TTL expiry) serialized via a shared test mutex so they don't race the existing mock-backend tests over the process-wide cache. |
||
|
|
6a28e75e94 |
feat(routing): pluggable local inference (MLX, llama.cpp, LM Studio) + openhuman:// deep-link + OpenSSL 3 PKCS12 fix (#750)
* feat(routing,local-ai): pluggable local inference + openhuman:// deep-link
Salvaged from recovery commit cfb1fd9f (Apr 19) — originally auto-backed up
by claude-mem recovery tool, not yet PR'd.
## Local AI routing becomes pluggable (factory.rs, health.rs)
Previously the IntelligentRoutingProvider hard-wired Ollama as the only local
inference backend. This commit lets operators point at any OpenAI-compatible
local server (llama.cpp / llama-server, LM Studio, MLX's mlx_lm.server /
mlx-omni-server, vLLM — anything exposing /v1/chat/completions + /v1/models).
Config surface:
- `OPENHUMAN_LOCAL_INFERENCE_URL` env var — full /v1 base URL. When set,
health is probed via GET {base}/models (OpenAI-compat) instead of Ollama's
/api/tags.
- `LocalAiConfig.provider` accepts "llamacpp" or "llama-server" as aliases
that default to http://127.0.0.1:8080/v1.
- Default (no env, no override) stays Ollama — zero config change for
existing users.
Motivation: Ollama's embedded llama.cpp cannot yet load Gemma 4 E2B and
other recent models; MLX is significantly faster on Apple Silicon.
Operators should be able to pick the backend; end users see nothing.
## Deep-link scheme (Info.plist)
Adds CFBundleURLTypes entry registering `openhuman://` for the macOS bundle
so browser-issued deep links (e.g. openhuman://auth?token=...) launch the
installed app. See .claude/rules/14-deep-link-platform-guide.md for the
flow — requires a .app bundle (not `tauri dev`).
## OpenSSL 3.x PKCS12 fix (setup-dev-codesign.sh)
Adds `-legacy` to the `openssl pkcs12 -export` call. Required on OpenSSL 3.x
because the modern SHA-256 MAC / AES-256-CBC defaults are not yet supported
by macOS `security` tool, which silently fails to import the cert.
Notes:
- Dropped the CLAUDE.md.new noise and the mic-description copy downgrade
from the original recovery commit; kept only the load-bearing changes.
- Cargo.lock files reset to upstream/main to avoid dependency drift — the
new code does not introduce new deps.
Co-Authored-By: WOZCODE <contact@withwoz.com>
* fix(routing,codesign): address CodeRabbit review on PR #750
- setup-dev-codesign.sh: probe for openssl `-legacy` support before
using it so older OpenSSL/LibreSSL installs don't silently fail;
drop the stderr suppression on pkcs12 so import errors surface.
- routing/factory.rs: trim provider before alias matching, lower the
local-inference diagnostic to `debug` and drop raw URLs from the
log fields, and honor `OPENHUMAN_OLLAMA_BASE_URL` via the existing
`ollama_base_url()` helper in the default Ollama branch.
- local_ai/mod.rs: re-export `ollama_base_url` for the routing factory.
* style(app/src-tauri): apply rustfmt to merged main
`cargo fmt --check` in the merge workspace flagged two hunks inherited
from upstream main:
- app/src-tauri/src/lib.rs: move `mod notification_settings;` between
the cfg(cef) imessage_scanner and slack_scanner declarations so the
ordering matches rustfmt's reordering output.
- app/src-tauri/src/webview_accounts/mod.rs: wrap the long
`try_state::<NotificationSettingsState>()` binding.
No behavior change.
* format
---------
Co-authored-by: Jwalin Shah <jshah1331@gmail.com>
Co-authored-by: WOZCODE <contact@withwoz.com>
Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai>
|
||
|
|
e405e2cc99 |
Prune obsolete config API keys (#739)
* feat(memory_recipes): introduce memory recipes module for source-type-specific ingestion - Added a new `memory_recipes` module that includes functionality for handling various source types, such as email and chat, with dedicated recipes for ingestion. - Implemented core types and the `Recipe` trait to facilitate the ingestion process, allowing for structured handling of raw content and metadata. - Created specific recipes for email and chat, enhancing the ability to process and normalize content before storing it in memory. - Updated the core controller registration to include new endpoints for memory recipes, enabling JSON-RPC interactions for ingesting content and listing available recipes. - Enhanced task management by integrating task storage and query helpers, allowing for better tracking and management of action items extracted during ingestion. These changes significantly improve the ingestion capabilities of the application, providing a robust framework for handling diverse content types. * refactor: remove API key references from configuration and schema - Eliminated the `api_key` field from various configuration schemas and related structures, including `Config`, `ModelSettingsPatch`, and `ComposioConfig`, to streamline the configuration process. - Updated the handling of API keys in the application to rely solely on session JWTs, enhancing security and simplifying the authentication flow. - Adjusted related documentation and comments to reflect the removal of the API key, ensuring clarity in the new authentication approach. - Refactored multiple components and functions to remove dependencies on the API key, improving code maintainability and reducing complexity. These changes significantly enhance the security posture of the application by removing reliance on static API keys and promoting the use of session-based authentication. * chore: update .env.example and documentation for API key removal - Removed references to `OPENHUMAN_API_KEY` from the `.env.example` file to reflect recent changes in the authentication approach. - Updated documentation in `AGENTS.md` and `CLAUDE.md` to clarify that `OPENHUMAN_API_URL` now overrides `config.api_url`, ensuring consistency across configuration references. - Bumped the version in `Cargo.lock` to 0.52.23 to align with the latest changes. These updates enhance clarity in configuration management and documentation following the removal of static API keys. * fix: address config pruning review comments * feat(tests): add validation tests for SearchResponse deserialization - Introduced new tests to ensure that the SearchResponse struct correctly rejects JSON inputs missing required fields: searchId, results, and costUsd. - Updated DelegateTool instantiation to remove the unused fallback_credential parameter, simplifying the constructor and related tests. These changes enhance the robustness of the SearchResponse handling and improve code clarity by removing unnecessary parameters. * refactor: remove api_key params from provider factory signatures The OpenHuman backend now only uses the app-session JWT for auth — the api_key parameter was ignored (as _api_key) after the config pruning. Drop it from the signatures entirely instead of leaving dead None arguments at every call site. Also removes the unused ChannelContext.api_key field. * refactor: remove api_key/composio_key params from factory signatures Every caller was passing None after the config pruning: - create_embedding_provider / create_memory* — the api_key param was only threaded to the openai / custom embedding branches, but no caller has ever supplied a non-None value since the pruning. Embeddings use an empty key; the param is gone. - all_tools / all_tools_with_runtime — both composio_key and composio_entity_id only fed the ComposioTool registration block, which was already dead after the JWT migration. Drop both args and the block; the modern path is all_composio_agent_tools via build_composio_client(config). * style: apply cargo fmt |
||
|
|
bdbb83772e |
feat(observability): Sentry release tracking, source maps, and end-to-end DSN plumbing (#734)
* ci(release): bake Sentry DSN into shipped tauri bundle
Released builds weren't reporting anything to Sentry. Root cause: the
tauri.conf.json `beforeBuildCommand` re-runs `vite build` inside
`cargo tauri build`. The prior `yarn build` step set `VITE_SENTRY_DSN`
for its own run, but the tauri step did not — so the rebuild produced
a DSN-less `dist/` that overwrote the good one, and the shipped web UI
initialized Sentry with an empty DSN (`initSentry` returns early when
`!SENTRY_DSN`).
Fix:
- `release.yml` / `build-desktop` — declare `VITE_SENTRY_DSN` and
`VITE_DEBUG` on the tauri-build step so the `beforeBuildCommand`
rebuild bakes them into the final bundle.
- `release-packages.yml` / `build-cli-linux-arm64` — guard against a
missing `vars.OPENHUMAN_SENTRY_DSN` so the Linux arm64 CLI tarball
cannot ship without error reporting baked in via `option_env!`.
The core sidecar's `option_env!("OPENHUMAN_SENTRY_DSN")` already gets
the value from the dedicated "Build sidecar core binary" step; the
tauri shell doesn't rebuild it (separate crate, not a workspace dep),
so the baked DSN survives into the bundled installer.
* feat(observability): Sentry release tracking + source maps (#405)
Tags every Sentry event with a canonical release identifier shared by
the frontend and Rust core, uploads source maps so stack traces are
symbolicated in the dashboard, and adds a CLI probe for repeatable
verification of any future release.
Release identifier
openhuman@<semver>[+<short_git_sha>]
- Frontend (`app/src/utils/config.ts::SENTRY_RELEASE`) builds the tag
from `VITE_BUILD_SHA`.
- Core sidecar (`src/main.rs::build_release_tag`) builds the same tag
from `option_env!("OPENHUMAN_BUILD_SHA")`, so events from both
surfaces group under one release. Cargo's fingerprint already tracks
`option_env!` changes.
Environment separation
- Frontend: new `APP_ENVIRONMENT` export (`development` | `staging` |
`production`) derived from `VITE_OPENHUMAN_APP_ENV`, passed to
`Sentry.init`.
- Core: `resolve_environment` honors `OPENHUMAN_APP_ENV` at runtime,
falling back to `debug_assertions` detection.
Source-map upload
- `@sentry/vite-plugin` added as an app devDependency.
- `vite.config.ts` emits source maps unconditionally and registers the
plugin only when `SENTRY_AUTH_TOKEN` is present, so local dev skips
silently. The plugin uploads `dist/**/*.js{,.map}` under the
canonical release name and then deletes the on-disk `.map` files so
they never ship to end users.
CI wiring (`release.yml` + `release-packages.yml`)
- `Build frontend` and `Build and package Tauri app` both receive
`VITE_BUILD_SHA`, `SENTRY_RELEASE`, `SENTRY_AUTH_TOKEN`, `SENTRY_ORG`,
`SENTRY_PROJECT_FRONTEND`. The tauri step needs the same env because
its `beforeBuildCommand` re-runs `vite build`.
- `Build sidecar core binary` receives `OPENHUMAN_BUILD_SHA` so
`option_env!` bakes the short SHA into the release tag.
- `build-cli-linux-arm64` mirrors `OPENHUMAN_BUILD_SHA` and
`OPENHUMAN_APP_ENV` for the arm64 CLI tarball.
Verification support
- New `openhuman sentry-test` CLI subcommand captures an `Error`-level
event against the currently-initialized client, flushes, and prints
the event UUID. Optional `--panic` flag exercises the panic
integration. Requires a DSN resolvable at runtime or baked in at
compile time; exits non-zero otherwise so misconfiguration is loud.
- `src/main.rs` now loads `.env` before `sentry::init`, so a DSN
defined only in the repo-local dotenv file (common dev case) is
honored by the startup-time Sentry client.
Docs
- `docs/sentry.md` covers the release identifier, environment table,
source-map pipeline, required CI variables, and a verification
runbook with troubleshooting tips.
- `.env.example` + `app/.env.example` document the new build-time vars.
|
||
|
|
8858b3cc59 |
fix(autocomplete): guard Ctrl+Tab and drop low-quality suggestions (#753)
Three fixes for the system-wide autocomplete accept-on-Tab behaviour: 1. Any-modifier guard: try_accept_via_tab now bails when Shift/Ctrl/Option/Cmd is held, so Ctrl+Tab (app-switch), Shift+Tab (outdent), Cmd+Tab and Option+Tab no longer trigger accept. Implemented via CGEventSourceFlagsState probed through the existing Input Monitoring cache. 2. Quality gate: is_low_quality_suggestion rejects <2-char, whitespace/punct-only and tail-echo suggestions before they reach the overlay. 3. Clean edge-state reset when modifier is held so a clean Tab press afterwards still accepts. Tests: 9 unit tests pass, cargo check clean. Co-authored-by: Jwalin Shah <jshah1331@gmail.com> Co-authored-by: WOZCODE <contact@withwoz.com> |
||
|
|
4a5a00cb96 |
feat(notifications): webview notifications end-to-end (Phase 7) (#741)
* wip(notifications): core module skeleton + controller registration Phase 7 PR #714 work-in-progress. Shipped: - src/openhuman/webview_notifications/ — mod, types, dispatch, bus, schemas - pub mod declaration in src/openhuman/mod.rs - controllers entry in src/core/all.rs Remaining: schemas line in all.rs, Tauri shell edits (OpenHuman: prefix, feature flag gate, click event emit), new shell module for feature flag state + commands, frontend app/src/lib/webviewNotifications/ IPC wrapper, Redux click handler. * feat(notifications): register webview_notifications schemas in core registry Adds the schemas line next to the already-registered controllers entry so the domain participates in declared-schema validation. v1 controller list is empty (settings live shell-side), but the wiring is in place for future additions. * feat(notifications): add OpenHuman: prefix, feature flag, and click-routing event Updates the CEF notification hook in webview_accounts to: - Prefix OS toast titles with "OpenHuman:" so they visually disambiguate from natively installed apps (Slack, Gmail, Discord desktop) firing the same DM twice. - Gate delivery on a new shell-side feature flag (off by default). - Mirror each fire to the React frontend via a `webview-notification:fired` Tauri event carrying {account_id, provider, title, body, tag}, so the UI can route click-to-focus back to the originating webview. Adds a new notification_settings shell module holding the runtime toggle as an AtomicBool (lock-free read from the CEF callback thread) plus notification_settings_{get,set} Tauri commands for the settings UI to flip the flag. State lives shell-side rather than in the core sidecar so the toggle doesn't require a JSON-RPC round-trip. * feat(notifications): frontend IPC wrapper + Redux click routing Mirrors the Rust-side `webview-notification:fired` Tauri event into Redux so the UI can: - bump an unread badge on the originating account (sidebar surface reads `accounts.unread[accountId]`) - route a subsequent click intent back to the right embedded webview via `focusAccountFromNotification`, which sets `activeAccountId` and clears the unread counter in one action Plumbing: - `app/src/lib/webviewNotifications/` — typed subscribe/unsubscribe, idempotent `started` guard mirroring `webviewAccountService.ts`, `handleNotificationClick(accountId)` as the public click entrypoint so in-app toast UI or a future OS-notification click hook share one Redux intent - `accountsSlice`: `noteWebviewNotificationFired` and `focusAccountFromNotification` reducers (both no-op for unknown account ids — guards against stale payloads from a closed webview) - `App.tsx`: start the service at boot, right next to the existing `startWebviewAccountService()` call Tests: - `accountsSlice.webviewNotifications.test.ts` — reducer behavior - `service.test.ts` — fired dispatches + click focuses via real store Also: `cargo fmt` noise fixups on `src/core/all.rs` and `src/openhuman/webview_notifications/mod.rs` so the branch passes `cargo fmt --check` in CI. --------- Co-authored-by: Jwalin Shah <jshah1331@gmail.com> |
||
|
|
72860b8c89 |
fix(agent): orchestrator never sends users to external dashboards for connect (#744)
Agent was improvising guidance like 'open your Composio dashboard at app.composio.dev' when users asked to connect a service, which is broken UX for non-technical users. Add an explicit rule: route connect requests to the in-app Settings → Connections path, never paste external URLs or explain OAuth/Composio internals. Co-authored-by: Jwalin Shah <jshah1331@gmail.com> |
||
|
|
5fff5568d3 |
feat(memory): Phase 2 memory tree - preprocessing, scoring, admission gate (#708) (#733)
* feat(memory): phase 1 memory tree - multi-source ingestion & canonical chunks (#707) Adds an isolated memory tree layer under src/openhuman/memory/tree/ implementing Phase 1 of the new memory architecture (umbrella #711). Zero edits to existing memory/*.rs files - the new layer coexists with the legacy TinyHumans-backed client. - Source adapters: chat / email / document -> canonical Markdown - Token-bounded chunker with deterministic SHA-256 chunk IDs - SQLite persistence at <workspace>/memory_tree/chunks.db with full provenance metadata (source_kind, source_id, owner, timestamps, tags, time_range) and back-pointer to raw source - Unified JSON-RPC ingest (dispatches on source_kind + JSON payload): openhuman.memory_tree_ingest, _list_chunks, _get_chunk - DataSource enum covering the 8 providers from m.excalidraw step 1 (Discord/Telegram/Whatsapp/Gmail/OtherEmail/Notion/MeetingNotes/DriveDocs) - ~40 unit tests (chunk ID stability, UTF-8-safe splitting, canonicalisation idempotence, store round-trip, filter behavior) Additive only: new tables in a new DB file, new JSON-RPC namespace, no existing behavior changes. Feeds #708 (scoring), #709 (summary trees), #710 (query tools). Closes #707. Parent: #711. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(memory): phase 2 memory tree - preprocessing, scoring, admission gate (#708) Adds the scoring / admission layer between Phase 1's chunker and store. Stacked on feat/707-memory-ingestion (PR #732) - depends on Phase 1's chunk substrate. - Pluggable EntityExtractor trait + CompositeExtractor chain - RegexEntityExtractor: mechanical entities (emails, URLs, @handles, #hashtags) - Always on, deterministic, zero deps, UTF-8-safe char spans - Five weighted signals: token count, unique-word ratio, metadata weight, source weight (per-DataSource), interaction (reply/sent/mention/dm tags), entity density - Exact-match entity canonicalisation (email lowercased, @ and # stripped) - Admission gate drops chunks below configurable threshold (default 0.3) - Score rationale persists for EVERY chunk (kept or dropped) for debugging - Entities indexed for KEPT chunks only - Two new SQLite tables added to the memory_tree DB: - mem_tree_score: per-chunk score rationale with all signal values - mem_tree_entity_index: inverted index entity_id -> node_id - Idempotent ALTER TABLE migration adds embedding BLOB column to mem_tree_chunks (used in Phase 3 retrieval, wired but not populated here) - Ingest pipeline converted to async to accommodate the extractor trait; blocking SQLite work isolated on spawn_blocking; JSON-RPC surface unchanged (same memory_tree_ingest / list / get methods) - Phase 2 deliberately ships without GLiNER/semantic NER - per-chunk semantic entities land later behind a cargo feature flag; the composite extractor interface keeps that drop-in trivial Additive only: new tables, new columns, new module. Existing Phase 1 behavior unchanged except that low-signal chunks are now dropped before reaching mem_tree_chunks. Raise score_drop_threshold to 0 to disable the gate and restore Phase-1-identical behavior. Closes #708. Parent: #711. Depends on: #707 (#732). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Fix memory tree scoring persistence issues * Fix memory tree scoring robustness issues from PR review - ingest: fail fast if scorer returns fewer/more results than chunks (silent zip truncation would drop chunks or their score rationale) - score::persist_score{,_tx}: clear stale entity-index rows before re-indexing a re-scored chunk, since INSERT OR REPLACE never deletes rows whose entity_id is no longer in the new extraction - score::store::lookup_entity: clamp limit to i64::MAX before casting to prevent a large usize wrapping into a negative LIMIT Adds clear_entity_index_drops_stale_rows regression test. --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai> |
||
|
|
0ce1253fe1 |
feat(memory): Phase 1 memory tree - multi-source ingestion & canonical chunks (#707) (#732)
* feat(memory): phase 1 memory tree - multi-source ingestion & canonical chunks (#707) Adds an isolated memory tree layer under src/openhuman/memory/tree/ implementing Phase 1 of the new memory architecture (umbrella #711). Zero edits to existing memory/*.rs files - the new layer coexists with the legacy TinyHumans-backed client. - Source adapters: chat / email / document -> canonical Markdown - Token-bounded chunker with deterministic SHA-256 chunk IDs - SQLite persistence at <workspace>/memory_tree/chunks.db with full provenance metadata (source_kind, source_id, owner, timestamps, tags, time_range) and back-pointer to raw source - Unified JSON-RPC ingest (dispatches on source_kind + JSON payload): openhuman.memory_tree_ingest, _list_chunks, _get_chunk - DataSource enum covering the 8 providers from m.excalidraw step 1 (Discord/Telegram/Whatsapp/Gmail/OtherEmail/Notion/MeetingNotes/DriveDocs) - ~40 unit tests (chunk ID stability, UTF-8-safe splitting, canonicalisation idempotence, store round-trip, filter behavior) Additive only: new tables in a new DB file, new JSON-RPC namespace, no existing behavior changes. Feeds #708 (scoring), #709 (summary trees), #710 (query tools). Closes #707. Parent: #711. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(memory): enhance memory tree functionality and tests - Added support for "memory_tree" namespace description in `namespace_description`. - Implemented clamping for zero token budget in `chunk_markdown` to prevent empty leading chunks. - Introduced detailed logging for document ingestion, including title length. - Updated output schemas in `schemas.rs` for improved clarity. - Enhanced chunk listing with clamping limits and ordering by sequence in `list_chunks`. - Normalized source references in canonicalization functions to drop blank values. - Added comprehensive tests for new features and edge cases in chunking and canonicalization. These changes improve the robustness and usability of the memory tree layer, aligning with ongoing development efforts for Phase 1 of the memory architecture. * fix(memory): address follow-up CodeRabbit comments --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai> |
||
|
|
cb455e807d |
feat(channels): register iMessage in the channel registry (local-only) (#725)
* feat(imessage): add chat.db scanner that ingests into memory_doc_ingest
Adds a macOS-only Tauri-side scanner at
`app/src-tauri/src/imessage_scanner/` that reads
`~/Library/Messages/chat.db` read-only on a 60s tick, groups messages
by `(chat_identifier, day)`, and posts one
`openhuman.memory_doc_ingest` JSON-RPC call per group — matching the
convention documented in `docs/webview-integration-playbook.md` and
used by the WhatsApp scanner.
This is the first local-native integration to follow the memory-doc
convention (webview sources like WhatsApp already do). No CEF / CDP /
DOM / IDB needed — iMessage persists everything locally in SQLite, so
a single-tick scanner is sufficient.
Changes:
- New module `imessage_scanner/{mod,chatdb}.rs` with unit tests.
- `lib.rs`: register `ScannerRegistry` and spawn on setup (macOS only).
- `Cargo.toml`: add `anyhow`, `parking_lot`, `chrono`, and
macOS-only `rusqlite` (bundled).
Feature-gated to `target_os = "macos"`; non-macOS builds get a no-op
stub. Requires Full Disk Access to read chat.db — the read_since
helper surfaces a clear error pointing at System Settings on
permission failure.
* test(imessage): add real chat.db integration tests (ignored by default)
Two tests validating against the actual ~/Library/Messages/chat.db:
- real_chat_db_opens_and_returns_messages — confirms the SQL JOIN is
compatible with macOS's actual schema, rusqlite bindings deserialize
rows into our Message struct, and Full Disk Access errors surface
cleanly when denied.
- real_chat_db_empty_past_cursor — confirms cursor semantics (rows
past i64::MAX-1 return empty).
Both gated with #[ignore] so CI remains green without FDA. Run with:
cargo test --manifest-path app/src-tauri/Cargo.toml \
--lib imessage_scanner -- --ignored
Verified locally against a 463K-message chat.db: both pass.
* fix(imessage): address CodeRabbit review — data loss, upsert correctness, client reuse, local TZ
Four fixes stacked on top of the scanner from the previous commit:
1. Data-loss bug (major): newer macOS versions store the body in
attributedBody (NSKeyedArchiver/typedstream blob) with text = NULL.
chatdb now fetches attributedBody alongside text. format_transcript
falls back to a heuristic extractor (longest printable-ASCII run,
skipping known typedstream type markers) when text is absent. This
recovers the plain-text body on macOS >= 11 without pulling in a
full typedstream decoder. Emoji / non-Latin glyphs in attributedBody
are deferred (follow-up).
2. Correctness bug (major): per-tick groups were ingested as partial
deltas but keyed on (chat, day) — each tick\'s upsert replaced the
full-day transcript with just the minute\'s messages. Now on each
tick we collect unique (chat, day) keys touched by the new rows,
call chatdb::read_chat_day() to fetch the complete day slice, and
post the full transcript. Upsert is idempotent; re-runs after a
crash reproduce the same document.
3. Cursor persistence: last_rowid is now read from / written to a file
under the Tauri app-data dir (per account) so a restart doesn\'t
replay the whole 30-day backfill.
4. Nits: reqwest::Client is shared via OnceLock (one TLS/pool init),
and day grouping uses chrono::Local so the user-facing (chat, day)
keys line up with the user\'s calendar day instead of UTC.
Tests: 6 unit tests green (added extractor + message_body + local-tz
coverage); 2 real-db integration tests (ignored by default) still pass
against the 463k-message chat.db on my machine.
* fix(imessage): gate scanner startup on feature="cef" to match module decl
Module declaration is #[cfg(feature = "cef")], so the macOS setup block
that references imessage_scanner::ScannerRegistry must carry the same
feature gate or wry builds fail to resolve the symbol.
* feat(channels): register iMessage channel with local-only auth + config persistence
iMessage already has the AppleScript send/receive bridge in channels/providers/imessage.rs
and the startup wiring in channels/runtime/startup.rs, but was missing from the public
channel registry, so the UI can't list or connect it and `connect_channel` returns
"unknown channel: imessage".
This adds:
- imessage_definition() in the channel registry with ManagedDm auth mode and an
optional allowed_contacts field (comma-separated phone numbers / emails; * for any)
- Short-circuit branch in connect_channel: iMessage has no credentials to store, so
it persists channels_config.imessage (IMessageConfig { allowed_contacts }) and
returns connected without calling store_provider_credentials (which rejects
empty credential payloads)
- Matching branch in disconnect_channel: skips remove_provider_credentials and
clears channels_config.imessage
- Three unit tests: contacts persisted, empty contacts allowed, disconnect clears
Capabilities: SendText + ReceiveText. No SendRichText (AppleScript bridge is
plaintext only).
Test plan:
- cargo test -p openhuman --lib channels::controllers → 66 passed
- Still need GUI end-to-end with FDA granted; covered in follow-up.
* fix(imessage-scanner): address CodeRabbit review (PR #725)
- chatdb: filter queries to `service = 'iMessage'` so SMS/MMS rows
in chat.db are never ingested into the iMessage channel.
- scanner: gate every tick on `channels_config.imessage` via the
existing `openhuman.config_get` JSON-RPC — no ingestion before
the user connects, and scanning stops as soon as they disconnect.
- scanner: apply the configured `allowed_contacts` allowlist before
rebuilding/ingesting each chat (`*` or empty list means allow all).
- scanner: keep the cursor pinned when a full-day read or memory
write fails, so a transient core outage no longer permanently
skips messages.
- tests: add `chat_allowed` coverage for empty list, wildcard, and
case-insensitive exact match.
---------
Co-authored-by: Jwalin Shah <jshah1331@gmail.com>
Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai>
|
||
|
|
3da80d852e |
feat(skills,node): integrate SKILL.md + managed Node runtime (#681) (#723)
* build(skills): add serde_yaml for SKILL.md frontmatter (#681) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(skills): parse SKILL.md frontmatter and add multi-path discovery (#681) Extends the skills module with agentskills.io-style discovery: * Parses YAML frontmatter in SKILL.md (name, description, version, author, tags, allowed-tools, license) via serde_yaml, with a catch-all extras map for forward-compatible keys. * Adds SkillScope (User / Project / Legacy) and discover_skills(), which scans ~/.openhuman/skills, ~/.agents/skills, <ws>/.openhuman/ skills, <ws>/.agents/skills and the legacy <ws>/skills directory. * Gates project-scope loading behind an explicit <ws>/.openhuman/trust marker (is_workspace_trusted helper). * Resolves name collisions with project > user > legacy precedence and surfaces shadowing as per-skill warnings. * Inventories bundled resources under scripts/, references/, assets/. * Keeps the existing load_skills(workspace_dir) API as a backwards-compatible wrapper for existing callers. * Preserves legacy skill.json fallback (marked legacy = true). * Lenient validation: missing / mismatched / oversized name or description produce warnings instead of errors. * Adds 12 unit tests covering frontmatter parsing, trust gating, collision shadowing, lenient fallbacks, legacy JSON and resource inventory. One existing prompt test updated to use ..Default::default() for the expanded struct. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(skills): align frontmatter with agentskills.io spec (#681) Per the agentskills.io SKILL.md spec, only name, description, license, compatibility, metadata, and allowed-tools are valid top-level keys. Our SkillFrontmatter had version, author, and tags as top-level fields, which drifted from the spec and would silently swallow mis-shaped data for skills authored against the canonical schema. Demote version/author/tags into the metadata map. Non-spec top-level keys still parse via #[serde(flatten)] into extra, and when present there we emit a migration warning and still populate the derived Skill fields so existing skills keep working. Add compatibility as an optional string alongside license. New tests cover the spec-compliant metadata shape, the deprecated top-level fallback path, and verify that the full spec frontmatter parses without leaking into extras. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * build(runtime): add tar+xz2+zip for node runtime extraction (#681) Node.js distributions ship as .tar.xz on Unix and .zip on Windows. The upcoming Node runtime bootstrap extracts these in pure Rust; xz2 is built with the `static` feature so liblzma is bundled and not a system dependency. zip is pinned to default-features = false + deflate to avoid pulling in bzip2/zstd/aes which we don't need for Node archives. Deps only — no behavior change. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(config): add NodeConfig with env overrides (#681) Introduce managed Node.js runtime configuration so skills that require `node`/`npm` (agentskills.io packages with build steps) can be wired in behind a single config switch. Fields: - `node.enabled` — master switch (default true) - `node.version` — pinned release (default `v22.11.0` LTS) - `node.cache_dir` — absolute path for managed distributions; empty = use workspace default - `node.prefer_system` — reuse matching system `node` when found (default true); set false for reproducible CI / airgapped deploys Env overrides land in load.rs alongside the existing LOCAL_AI_TIER block: - OPENHUMAN_NODE_ENABLED - OPENHUMAN_NODE_VERSION - OPENHUMAN_NODE_CACHE_DIR - OPENHUMAN_NODE_PREFER_SYSTEM No resolver / downloader yet — subsequent commits in the #681 series consume this config to bootstrap the runtime. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(node_runtime): detect compatible system node on PATH (#681) Introduce the `openhuman::node_runtime` module and its first piece: a synchronous resolver that walks `PATH`, probes `node --version`, and returns a `SystemNode` when the host toolchain major-version matches the configured target. Why resolve first: a successful probe lets the bootstrap skip a ~60 MB download per managed install. Matching is intentionally loose on the patch level (Node LTS lines are ABI-stable and skills pin their own deps via package-lock.json); operators needing strict pinning can set `node.prefer_system = false`. Tracing is verbose by design — resolver decisions gate the download path, so operators need a clear breadcrumb trail at `debug`/`info`. Includes unit tests for `parse_node_version` covering `v` prefix, whitespace, major-only, and malformed inputs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(node_runtime): SHASUMS256-verified distribution downloader (#681) Add the second piece of the managed Node.js runtime: streaming download of OS/arch-specific prebuilt archives from nodejs.org, gated by a SHA-256 match against the release's signed `SHASUMS256.txt`. Key contracts: - `NodeDistribution::for_host(version)` picks the right archive for the current OS/arch tuple. Supported matrix: * darwin-{arm64,x64}.tar.xz * linux-{arm64,x64,armv7l}.tar.xz * win-{arm64,x64}.zip Unsupported hosts surface a clear error so the caller can flip `node.enabled = false` or point at a pre-installed toolchain. - `fetch_shasums(client, version)` parses `SHASUMS256.txt` into a filename -> hex digest map. Tolerant of trailing / signature blocks. - `download_distribution(...)` streams chunks into the target path, computes SHA-256 on the fly, and wipes the partial file if the digest does not match. Integrity check is mandatory — skills will execute untrusted code inside the resolved runtime. Verbose tracing at `info` and `debug` follows the repo debug-logging rule; operators can grep `[node_runtime::downloader]` to trace every GET, chunk boundary (via total-bytes log), and hash decision. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(node_runtime): archive extraction + atomic install (#681) Extract downloaded Node.js distributions and move them into the final cache path without leaving the reader observing a half-populated directory. - `extract_distribution(archive, extract_root, is_zip)` — wraps the synchronous `tar`/`xz2`/`zip` crates in `spawn_blocking` and returns the single top-level folder produced by the archive (`node-vX.Y.Z- <os>-<arch>/`). `set_preserve_permissions(true)` + `set_overwrite( true)` on the tar side keeps the `node` binary's `+x` bit. The zip path restores Unix mode bits via `unix_mode()` so cross-OS builds still land correct permissions. - `atomic_install(staged, final_dest)` — renames a staged directory into place via a single `rename(2)`, moving any pre-existing install to a `.old-<pid>` sibling first and cleaning it up on success. This gives concurrent readers a "before" or "after" view, never a partial one. - Unsafe zip paths (`enclosed_name()` rejection) are logged and skipped rather than traversed — defence against malicious archives, even though the digest check in the downloader already rules out tampered official releases. No new tests here: the logic leans on upstream tar/zip crates, and meaningful end-to-end coverage requires a real archive on disk — that comes in the integration-test commit later in this series. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(node_runtime): bootstrap mutex + cache + bin path helper (#681) Stitch resolver, downloader, and extractor into a single idempotent entry point callers use at startup (or lazily before the first `node_exec`/`npm_exec` call). `NodeBootstrap::resolve()` contract: 1. Return cached `ResolvedNode` if a previous call already succeeded. 2. Bail when `node.enabled = false`. 3. If `node.prefer_system`, probe the host PATH. Matching major version wins — return a `System`-sourced `ResolvedNode`. 4. Otherwise compute the install path (`{cache_dir}/node-v{version}-{os}-{arch}/`). If it already contains valid bins, reuse it. This makes the bootstrap ~free across restarts once a managed install lands. 5. Else fetch `SHASUMS256.txt`, locate the expected digest for our archive, stream the download, extract into a `.stage-<pid>` scratch dir, `atomic_install` the top-level folder into the final path, and remove scratch + archive to reclaim disk. Concurrency is handled by a `tokio::sync::Mutex<Option<ResolvedNode>>` — parallel callers queue behind the first one; the winner memoises, the losers pick up the cached result. No race can produce two concurrent downloads of the same archive. Platform-specific bin layout lives in `managed_bin_dir` / `build_ resolved`: - Unix: `<install>/bin/{node,npm}` - Windows: `<install>/{node.exe,npm.cmd}` This closes the resolver/downloader/cache layer promised in the #681 checkpoint. Tools (`node_exec`, `npm_exec`) and shell-PATH injection layer on top in the next batch of commits. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(tools): node_exec runs JS via managed node runtime (#681) Executes `inline_code` (via `node -e`) or a workspace-relative `script_path` through the NodeBootstrap-resolved `node` binary. POSIX single-quote quoting keeps user input inert; `env_clear` + allow-list mirrors the shell tool so secrets never leak. 300s default timeout (capped at 1800s), 1MB stdout/stderr caps. PATH is prepended with the resolved bin dir so child processes see managed `node`/`npm`. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(tools): npm_exec runs npm via managed node runtime (#681) Thin npm CLI wrapper paired with node_exec. Subcommands go through `is_sane_subcommand` (alphanumerics + `._-:` only) and a deny-list (publish/adduser/login/token/…) blocks registry-mutation and auth flows. `cwd` is resolved under the workspace — absolute paths and `..` components are rejected. 600s default timeout (1800s ceiling), 1MB stdout/stderr caps. PATH prepended with managed bin dir so npm's own node/corepack lookups hit the managed toolchain. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(tools): shell prepends managed node bin to PATH when cached (#681) Adds a non-blocking `NodeBootstrap::try_cached()` primitive that peeks the memoised `ResolvedNode` without holding the async lock or triggering a download. ShellTool gains an optional `node_bootstrap` field wired through a new `with_node_bootstrap` constructor; when set, each shell invocation consults `try_cached()` and, on a hit, prepends the managed bin dir to the child PATH using the platform separator. Unrelated shell commands stay byte-identical — no download is ever forced from the shell path. Consequence: once `node_exec`/`npm_exec` have resolved the toolchain once, skills that shell out to `node`/`npm`/`npx`/`corepack` transparently pick up the managed install without any per-command coordination. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(tools): register node_exec + npm_exec behind node.enabled (#681) Wires the skills-oriented Node.js toolchain into the tool registry: * Constructs one session-scoped `NodeBootstrap` in `all_tools_with_runtime` when `root_config.node.enabled` is true — all three consumers (ShellTool, NodeExecTool, NpmExecTool) share the same `Arc<NodeBootstrap>` so the download/extract/install pipeline runs at most once per session and the memoised `ResolvedNode` is reused across every shelling-out call. * Swaps ShellTool to `with_node_bootstrap(...)` when the bootstrap exists so shell PATH injection fires automatically once any node/npm tool has resolved the runtime. * Registers `node_exec` and `npm_exec` only when the flag is on — flipping `node.enabled = false` cleanly removes both tools and falls back to the legacy ShellTool construction. * Adds two regression tests (`all_tools_registers_node_exec_when_node_enabled` and `all_tools_excludes_node_exec_when_node_disabled`) so future refactors can't silently drop the wiring. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(agents): grant node_exec + npm_exec to code_executor + tool_maker (#681) code_executor is the sandboxed developer sub-agent — it writes, runs, and debugs code — and tool_maker is the narrow self-healing agent that polyfills missing commands. Both now see the managed Node.js tools so skills and polyfills can call into the resolved runtime directly rather than relying on whatever `node`/`npm` happens to be on the host PATH. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(core): apply cargo fmt auto-fixes (#681) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * chore(cargo): sync Cargo.lock to OpenHuman v0.52.26 after rebase (#681) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(node_runtime): strip leading v/V defensively in resolve_from_system (#681) CodeRabbit R1 flagged that build_resolved receives a raw version string from SystemNode. detect_system_node already trims the prefix, but trim defensively at the resolve_from_system boundary too so any future code path constructing SystemNode with an un-normalised version won't emit "vvX.Y.Z". Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(node_runtime): propagate flush errors and clean up partial archives (#681) CodeRabbit R2 flagged that download_distribution silently swallowed flush errors via `.ok()` and left partial files on disk when any chunk/write/flush step errored. Wrap the streaming loop in an async block returning Result<()>, propagate flush errors, and on any failure delete the partial archive so a retry starts clean and callers never see a half-written file. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(node_runtime): restore previous install on staged rename failure (#681) CodeRabbit R3 flagged that atomic_install left the user with no Node runtime on disk if the staged->final rename failed after a backup had been taken. Wrap the rename in `if let Err`, restore the backup if present, log restore failures separately (warning), and always return the original error so the caller sees the true failure cause. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(node_runtime): enforce real 5s timeout on node --version probe (#681) CodeRabbit R4 flagged that probe_node_version used Command::output() with no real timeout — a broken shim or FUSE-backed binary could hang the bootstrap forever. Add wait-timeout crate dep and rewrite the probe to spawn with piped stdio, wait_timeout(5s), kill on timeout, and read stdout/stderr from the piped handles after exit. Logs a warning when a probe times out so the install can be diagnosed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(tools): reject script_path escapes in node_exec (#681) CodeRabbit R5 flagged that node_exec joined user-supplied script_path onto the workspace without rejecting `..` segments or absolute paths, letting a prompt-injected `../../../etc/passwd` read arbitrary files via node. Add a resolve_script_path helper mirroring npm_exec::resolve_cwd — reject empty, absolute, parent-dir, and Windows-prefix components. Extra positional args stay opaque (shell-quoted, not path-checked) and get an explanatory comment at the call site. Unit tests cover each rejection case plus the relative- subdir happy path. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor(skills): drop dead SKILL.md branch in legacy manifest loader (#681) CodeRabbit N1 flagged dead code: load_from_legacy_manifest only runs when load_skill_dir already determined SKILL.md is absent, so the fallback that re-checked for SKILL.md and its helper read_skill_md_description could never execute. Simplify to a direct description/location decision and delete the dead helper. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor(config): extract parse_env_bool helper with warn on unknown values (#681) CodeRabbit N2 flagged two near-identical boolean env-override match blocks for OPENHUMAN_NODE_ENABLED and OPENHUMAN_NODE_PREFER_SYSTEM. Extract a module-level parse_env_bool helper that accepts 1/true/yes/on and 0/false/no/off (case-insensitive) and emits a tracing::warn when the value is unrecognised so silent mis-spellings don't invisibly leave the config unchanged. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(node_runtime): detect corrupted cache missing npm (#723) `probe_managed_install` now verifies the npm launcher exists before reusing an extracted install. A download interrupted after `node` was extracted but before `npm` would otherwise be cached forever and `npm_exec` could never self-heal — now the corrupted cache forces a fresh download via the normal resolve path. Addresses CodeRabbit review feedback on PR #723. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(node_runtime): skip non-executable PATH shims when probing node (#723) `which_node` previously returned the first matching filename on `PATH` regardless of its execute bit. A non-executable `node` placeholder earlier in `PATH` (e.g. an unprivileged shim left by a failed install) would mask a valid later install and force the managed runtime download. Now checks `file && (mode & 0o111 != 0)` on Unix to mirror shell `which` behaviour. Windows remains unchanged — `.exe` suffix already encodes executability for the loader. Addresses CodeRabbit review feedback on PR #723. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(skills): deterministic entry order in scan_root (#723) `read_dir` order is unspecified by the OS. When two sibling skill directories declare the same logical `frontmatter.name` (which can differ from the folder name), cross-scope/same-scope deduplication downstream would pick a non-deterministic winner across runs. Sort entries by on-disk directory name for a stable, reproducible order so collision resolution is deterministic. Addresses CodeRabbit review feedback on PR #723. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(skills): surface YAML parse errors as skill warnings (#723) `parse_skill_md` previously swallowed the `serde_yaml` error via `log::warn!` and fell back to an empty `SkillFrontmatter`, then the catalog reported a generic "could not parse — exposing directory as placeholder". Skill authors had no way to see the real cause without scraping logs. Return parse-level diagnostics as a third tuple element. `load_from_skill_md` merges them into the skill's user-visible `warnings`, so the catalog now surfaces the actual YAML error (e.g. "frontmatter parse error: mapping values are not allowed here at line 3 column 12"). Addresses CodeRabbit review feedback on PR #723. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(skills): skip symlinks when walking skill resources (#723) `walk_files` used `is_dir()` / `is_file()` which transparently follow symlinks. Two failure modes: 1. **Unbounded recursion** — a skill resource symlink that points back at an ancestor (e.g. `resources/self -> resources/`) would cause infinite traversal, eventually blowing the stack. 2. **Silent out-of-tree leakage** — a symlink pointing at `/`, `/etc`, or another skill's directory would enumerate its contents into the current skill's resource listing. Switch to `entry.file_type()` and skip symlinks before descending. Directories and regular files behave as before. Addresses CodeRabbit review feedback on PR #723. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(node_runtime): default managed cache to user-owned OS cache (#723) Default `cache_root()` to `dirs::cache_dir()/openhuman/node-runtime/` so a repo cannot ship a checked-in `./node-runtime/` and have the bootstrap reuse it as a trusted managed install. Explicit `config.cache_dir` still wins; workspace-local falls back only when `dirs::cache_dir()` is unavailable, and we emit a warning on that fallback. As a second line of defence, `probe_managed_install()` now canonicalises both the install dir and the cache root and refuses any install that escapes the cache tree. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(node_runtime): require npm when accepting system node (#723) On distros that package `node` and `npm` separately (Debian/Ubuntu, Alpine `nodejs-current`, some NixOS setups) the host `node` can be present without `npm`. `npm_exec` then fails every call because the resolved `SystemNode` has no usable npm launcher. Before returning `Some(SystemNode)`, locate `npm` on `PATH` and probe `npm --version` through the same `wait_timeout` path as the node probe. Either missing gate falls back to the managed download flow. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(skills): surface user-scope skills via load_skills (#723) Existing production callers (`agent::harness::session::builder`, `channels::runtime::startup`) reach the skill catalog via `load_skills(workspace_dir)`. Previously this shim passed `None` for the home directory, so skills installed under `~/.openhuman/skills/` and `~/.agents/skills/` were silently dropped even after the multi-scope discovery landed in `discover_skills`. Delegate to `discover_skills_inner` with `dirs::home_dir()` so user-scope skills reach the runtime; project-scope still wins on name collision. Tests stay hermetic via a `load_skills_ws` helper that preserves the old workspace-only semantics. A new `load_skills_surfaces_user_scope` test drives a tempdir-as-home through `discover_skills` and asserts the user-scope skill is returned with `SkillScope::User`. Addresses CodeRabbit review comment 3116332244. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(skills): skip symlinked skill dirs during scan (#723) `scan_root` previously accepted any entry where `path.is_dir()` returned true. `is_dir()` dereferences symlinks, so a link from `<skills-root>/foo -> /some/external/tree` would load as a legitimate skill even though `walk_files` already rejects symlinks deeper in the resource walker. Attacker-authored symlinks in a skills root would therefore have one remaining escape hatch. Switch to `entry.file_type()` (non-dereferencing) and reject both symlinked and non-directory entries at the top level. Treat a failed `file_type()` probe as "not safe to traverse" and skip. A new `symlinked_skill_dirs_are_skipped` test (Unix-only, since symlinks are the platform guarantee being tested) creates an external skill tempdir, links it into the workspace skills root, and asserts `load_skills_ws` returns empty. Addresses CodeRabbit review comment 3116332252. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(skills): reject symlinked resource roots (#723) `inventory_resources` gated each resource sub-root (`scripts/`, `references/`, `assets/`) on `root.is_dir()`. `is_dir()` follows symlinks, so a `scripts -> /etc` link inside a skill directory would pass the check and `walk_files` would inventory the external tree. Deeper symlinks inside the walk were already rejected by `walk_files`, but the root-level check was the missing layer. Use `std::fs::symlink_metadata` for a non-dereferencing probe and reject roots whose own `file_type().is_symlink()` returns true. Treat any `symlink_metadata` error as a non-existent root and skip. A new `symlinked_resource_roots_are_rejected` test (Unix-only) links a skill's `assets/` sub-root to an external tempdir with a file inside and asserts `inventory_resources` returns empty. Addresses CodeRabbit review comment 3116332260. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
1792135aea |
fix(settings): tool capability toggles now persist and are enforced at runtime (#720)
* chore: update OpenHuman version to 0.52.26 and refine tool preference handling - Bumped the OpenHuman package version to 0.52.26 in Cargo.lock files. - Enhanced the ToolsPanel component to include user feedback on save status. - Implemented filtering of tools based on user preferences in the Rust backend, allowing for more customizable tool availability. - Added new utility functions to map UI tool IDs to Rust tool names for better integration. These changes improve user experience and maintain compatibility with the latest features. * style: apply cargo fmt to user_filter.rs |
||
|
|
ee3f6472ca |
feat: improve sub-agent tooling, conversation timeline UX, and Tauri setup (#646)
* refactor(tauri): update development scripts and configuration for CEF integration - Modified `package.json` scripts to consistently export `CEF_PATH` for all `cargo tauri` commands, ensuring a unified CEF binary distribution location. - Removed the overlay window configuration from `tauri.conf.json` and updated related Rust functions to reflect this change, while retaining helper functions for potential future use. - Updated documentation in `install.md` to clarify the importance of setting `CEF_PATH` for consistent CEF integration across builds. - Enhanced the `ensure-tauri-cli.sh` script to set `CEF_PATH` and ensure proper installation of the vendored CEF-aware `tauri-cli`. These changes streamline the development workflow and improve the reliability of CEF integration in the application. * refactor(release): enhance macOS signing script for nested frameworks and helper apps - Introduced a new `codesign_hardened` function to streamline the signing process with consistent options. - Improved the signing logic for nested frameworks and helper applications, ensuring all binaries are signed correctly. - Updated output messages for better clarity during the signing process, including detailed listings of bundle contents. - Disabled the summarizer payload threshold in the configuration to prevent recursive invocations until the issue is resolved. These changes improve the reliability and maintainability of the macOS signing and notarization workflow. * feat(orchestrator): add current_time tool and enhance agent capabilities - Introduced the `current_time` tool to provide the current date and time in UTC and local time zones, facilitating scheduling and reminders. - Updated `agent.toml` to include new tools: `current_time`, `cron_add`, `cron_list`, `cron_remove`, and `schedule`, enhancing the orchestrator's functionality. - Expanded documentation in `prompt.md` to guide users on utilizing the new direct tools effectively. These changes improve the orchestrator's ability to handle time-related queries and scheduling tasks directly, enhancing user experience. * feat(gmail): implement post-processing for Gmail responses to convert HTML to markdown - Added a new `post_process` module specifically for Gmail, which modifies action responses to convert HTML content into markdown format, improving usability and reducing context token usage. - Enhanced the `ComposioProvider` trait with a `post_process_action_result` method to allow providers to handle response modifications. - Introduced a `post_process` function that checks for a `raw_html` flag in the arguments to determine whether to apply the conversion. - Implemented tests to validate the HTML detection and conversion logic, ensuring the integrity of the post-processing functionality. These changes enhance the handling of Gmail responses, making them more suitable for further processing and display in the application. * refactor(gmail): improve HTML to markdown conversion and tool result budget - Enhanced the `post_process` module for Gmail to handle large HTML payloads more efficiently, implementing a fallback mechanism for oversized content. - Updated the `DEFAULT_TOOL_RESULT_BUDGET_BYTES` to `0`, disabling the budget temporarily while reworking the oversized-output path. - Refined the `extract_markdown_body` function to better manage HTML content, ensuring cleaner markdown output and improved performance. - Added utility functions for stripping HTML noise and handling large email bodies, enhancing the overall robustness of the email processing logic. These changes optimize the handling of Gmail responses, improving usability and performance in processing large HTML content. * feat(thread): implement thread title generation from user and assistant messages - Added a new `generateTitleIfNeeded` function in the `threadApi` to create a thread title based on the first user message and the assistant's reply. - Introduced a new `GenerateConversationThreadTitleRequest` struct to handle requests for title generation. - Updated the `ChatRuntimeProvider` to dispatch the title generation action after processing inference responses. - Enhanced the `threadSlice` with a new async thunk for generating thread titles, ensuring proper error handling and thread loading. - Added tests for the new title generation functionality to validate the integration with the threads RPC. These changes improve the user experience by automatically generating relevant thread titles, enhancing the organization of conversations. * feat(conversations): add thread title update functionality - Implemented `update_thread_title` method in `ConversationStore` to allow updating the title of existing conversation threads. - Added a corresponding public function `update_thread_title` for external access. - Enhanced tests to verify that thread titles are correctly updated and persisted in the store. These changes improve the management of conversation threads by enabling dynamic title updates, enhancing user experience and organization. * feat(docs): add comprehensive agent and subagent tool flow documentation - Introduced a new document detailing the runtime flow of the agent harness, including execution paths for main agents and tools. - Explained the differences between typed and fork subagents, and provided guidance for debugging harness and delegation issues. - Included a file map outlining key components and their roles within the Rust implementation. - Added a flow diagram to visually represent the interaction between agents, tools, and subagents. These changes enhance the understanding of the agent architecture and improve the documentation for developers working with the system. * feat(subagent): implement extraction tool and handoff cache for oversized results - Introduced `extract_from_result` tool to allow targeted queries against oversized tool outputs, improving efficiency by directly interacting with the extraction model. - Added `ResultHandoffCache` to manage oversized payloads, enabling progressive disclosure and reducing context length issues in sub-agent history. - Implemented hygiene helpers for cleaning tool outputs before caching, ensuring only relevant data is stored. - Enhanced the sub-agent runner with new modules for tool preparation and execution, streamlining the overall agent workflow. These changes enhance the sub-agent's ability to handle large tool results effectively, improving performance and user experience. * feat(conversations): enhance message bubble rendering and timeline entry formatting - Introduced new utility functions for parsing and rendering agent messages, including `splitAgentMessageIntoBubbles` and `parseMarkdownTable`, to improve the display of messages in conversation threads. - Implemented `BubbleMarkdown` and `TableCellMarkdown` components for better formatting of user and agent messages, ensuring consistent styling and interaction. - Enhanced the `formatTimelineEntry` function to provide clearer titles and details for tool timeline entries, improving the user experience during interactions with subagents. - Updated the `ChatRuntimeProvider` to utilize the new formatting functions, ensuring that tool timeline entries are displayed with relevant context and detail. These changes improve the overall presentation and usability of conversation messages and tool interactions, enhancing user engagement and clarity. * feat(subagent): enforce restrictions on sub-agent spawning tools - Introduced a filter to prevent sub-agents from invoking their own spawning tools, specifically `spawn_subagent` and `delegate_*`, to avoid recursion issues and ensure proper delegation by the top-level orchestrator. - Updated the `is_subagent_spawn_tool` function to identify these tools and integrated checks in both `run_typed_mode` and `run_fork_mode` to maintain the integrity of the sub-agent execution environment. - Enhanced logging to track the removal of restricted tools from the sub-agent's tool surface, improving observability and debugging capabilities. These changes strengthen the sub-agent architecture by enforcing strict boundaries on tool invocation, enhancing stability and performance. * feat(conversations): add ToolTimelineBlock component and enhance timeline entry formatting - Introduced the `ToolTimelineBlock` component to display tool timeline entries with improved formatting and user interaction, including auto-expansion for running entries. - Enhanced the `formatTimelineEntry` function to include user-friendly titles for specific tool actions, such as 'Viewing your Integrations'. - Updated the rendering logic in the `Conversations` component to filter and display visible messages more effectively, improving user experience during conversations. These changes enhance the clarity and usability of tool interactions within conversation threads, providing users with better context and engagement. * refactor(conversations): streamline component imports and enhance formatting consistency - Consolidated import statements in `Conversations.tsx` and `ChatRuntimeProvider.tsx` for improved readability. - Refactored the `ToolTimelineBlock` component to simplify its props structure. - Enhanced formatting consistency in the rendering logic of agent message bubbles and timeline entries, ensuring cleaner code and better maintainability. - Updated test cases for `splitAgentMessageIntoBubbles` and `formatTimelineEntry` to reflect formatting changes and ensure accuracy. These changes improve code clarity and maintainability while enhancing the overall user experience in conversation threads. * fix: satisfy pre-push lint on fix/tauri * fix(chat): refresh usage counters after responses * refactor(ChatRuntimeProvider): remove redundant import of requestUsageRefresh - Eliminated the duplicate import statement for `requestUsageRefresh` in `ChatRuntimeProvider.tsx`, streamlining the code for better readability and maintainability. * fix(chat): satisfy pre-push checks * refactor(conversations): improve error handling and remove unused title generation logic - Removed the unused `generateThreadTitleIfNeeded` function call from the `Conversations` component, simplifying the message dispatch logic. - Enhanced error handling during message dispatch by using `unwrap()` to catch and log errors, providing clearer feedback on send failures. - Updated the `ChatRuntimeProvider` to ensure proper error logging when generating thread titles, improving observability of issues related to title generation. These changes streamline the conversation handling process and improve the robustness of error management in the chat system. * refactor(conversations): improve formatting of title redaction and streamline test assertions - Reformatted the `redact_title_for_log` function for better readability by adjusting the formatting of the output string. - Simplified the assertion in the timezone test to enhance clarity and maintainability. These changes contribute to cleaner code and improved test structure in the conversations module. * refactor(tokenjuice): sort fact parts for improved formatting in inline summary - Modified the `format_inline` function to sort the fact parts before generating the summary string. This change enhances the readability and consistency of the output by ensuring that facts are presented in a stable order. These changes contribute to better formatted summaries in the token juice module. * fix: address remaining CodeRabbit review comments * refactor: streamline error handling and improve code readability - Updated error handling in prompt loading to use `std::io::Error::other` for better clarity. - Simplified conditional checks using `is_none_or` and `is_some_and` for improved readability. - Refactored string trimming logic to utilize array syntax for better clarity. - Enhanced default implementations for several structs to reduce boilerplate code. These changes contribute to cleaner code and improved maintainability across various modules. * refactor: improve code formatting and structure - Adjusted formatting in `post_process.rs` for better readability by aligning the conditional block. - Combined derive attributes in `tools.rs` for the `ComputerControlConfig` struct to reduce redundancy. - Streamlined entry retrieval in `compatible.rs` by condensing multiple lines into a single line for clarity. These changes enhance code readability and maintainability across the affected modules. |
||
|
|
e5f571cec7 |
feat(tokenjuice): Rust port of terminal-output compaction engine (#644)
* feat(tokenjuice): implement core functionality for terminal-output compaction engine - Introduced the `tokenjuice` module, which includes the classification and reduction of tool outputs based on JSON-configured rules. - Added new dependencies for Unicode handling: `unicode-segmentation` and `unicode-width`. - Implemented the `classify` module to match tool execution inputs against predefined rules, enhancing the ability to process and summarize terminal outputs. - Created a comprehensive set of types and utilities for managing tool execution inputs and classification results. - Established a built-in rule set for common tools, improving the initial setup and usability of the `tokenjuice` engine. - Enhanced testing framework with integration tests to ensure the accuracy of output compaction and classification. These changes lay the groundwork for a robust terminal-output management system, facilitating better interaction with various tools and improving overall user experience. * feat(tokenjuice): implement tokenjuice module for terminal output compaction - Introduced the `tokenjuice` module, which includes functionality for classifying and reducing terminal output based on JSON-configured rules. - Added new dependencies: `unicode-segmentation` and `unicode-width` to support text processing. - Created a new `classify.rs` file for rule classification logic, including matching helpers and scoring functions. - Implemented a `reduce.rs` file to handle the main reduction pipeline and text normalization. - Established a structured approach for loading and compiling rules from multiple sources, including built-in and user-defined rules. - Added integration tests to ensure the correctness of the output reduction process. These changes enhance the application's ability to manage and compact verbose tool outputs, improving overall efficiency and user experience. * test(tokenjuice): enhance test coverage for classification and reduction logic - Added a series of unit tests to `classify.rs` to validate the behavior of tool name filters and argument matching, ensuring correct classification of tool executions. - Introduced tests for edge cases in `reduce.rs`, including command tokenization and normalization of execution inputs, to improve robustness against various input formats. - Expanded tests in `builtin.rs` to cover duplicate ID reporting and compile issues, enhancing error handling and reporting mechanisms. - Implemented additional tests in `compiler.rs` to verify regex handling in rule definitions, ensuring invalid patterns are correctly ignored. These enhancements improve the overall test coverage and reliability of the tokenjuice module, facilitating better maintenance and future development. * test(tokenjuice): add edge-case tests for gh table and reduction pipeline * style(tokenjuice): apply cargo fmt * feat(tokenjuice): wire into agent tool loop output compaction Add `tokenjuice::compact_tool_output` helper and call it in the agent tool loop after credential scrubbing (and on error paths with exit=1), before any optional payload_summarizer. Derives argv/command heuristically from JSON tool arguments (command / args / argv / cmd shapes) so shell-wrapping tools still match upstream family rules (git/*, package/*, tests/*, etc.). Pass-through safe: outputs under 512 bytes or where compaction saves <5% are returned untouched. * fix(tokenjuice): address coderabbit review comments - classify: derive command from argv join when input.command is unset, so commandIncludes* rules still match argv-only callers - rules/loader: log read_dir / file_type / read_to_string failures at debug level so permission or filesystem issues are observable rather than silently skipped - text/ansi: add trace log at strip_ansi entry/exit with lengths (no text content) per the project debug-logging rules - tests: remove orphan src/openhuman/tokenjuice/tests/integration.rs which was never wired into any module declaration; the real fixture- parity runner lives at tests/tokenjuice_integration.rs and asserts hard when the fixtures directory is missing Vendored-rule issues (docker-ps / kubectl-describe / git/branch / grep casing / counter-pattern overbreadth / etc.) come from upstream and are left as-is; this module is a straight port of the upstream rule set and should not fork from it in v1. |
||
|
|
8f4696bdfb |
feat(composio): per-toolkit tool curation, user scopes, and Gmail HTML→markdown (#643)
* feat(composio): add user scope management for toolkits - Introduced `get_user_scopes` and `set_user_scopes` functions to manage per-toolkit user scope preferences, allowing for read, write, and admin classifications. - Updated `all_controller_schemas` and `all_registered_controllers` to include new schemas for user scope management. - Implemented `evaluate_tool_visibility` to determine tool visibility based on user-defined scopes, enhancing security and control over tool actions. - Added `UserScopePref` struct to store user preferences and integrated it with memory storage for persistence. - Enhanced existing tools to respect user scope preferences during execution, ensuring actions align with user-defined permissions. These changes improve the flexibility and security of toolkit interactions, allowing users to customize their access levels for different actions. * feat(gmail): expand GMAIL_CURATED tools for enhanced email management - Updated the GMAIL_CURATED constant to include additional tools for reading and writing emails, such as GMAIL_LIST_MESSAGES, GMAIL_LIST_THREADS, GMAIL_GET_ATTACHMENT, and GMAIL_FORWARD_MESSAGE. - Improved organization of tools by categorizing them under distinct sections for reading messages, managing drafts, and handling labels. - Enhanced admin tools with new functionalities like GMAIL_BATCH_DELETE_MESSAGES and GMAIL_UNTRASH_THREAD, improving overall email management capabilities. These changes provide a more comprehensive toolkit for interacting with Gmail, enhancing user experience and functionality. * refactor(tool_scope, gmail): improve code formatting and organization - Reformatted the `ADMIN` and `GMAIL_CURATED` constants for better readability by aligning the entries vertically. - Enhanced the clarity of the `classify_unknown` function by improving the structure of the constants, making it easier to maintain and understand. - Updated test assertions in `toolkit_from_slug` for consistency in formatting, ensuring clearer test outputs. These changes improve the overall readability and maintainability of the codebase, aligning with ongoing refactoring efforts. * feat(github): add GitHub toolkit and curated tools for enhanced integration - Introduced a new GitHub provider module, including a curated catalog of GitHub actions tailored for common tasks such as repository management, issue tracking, and pull request handling. - Implemented the `catalog_for_toolkit` function to allow fallback to a static curated list for toolkits without a native provider, ensuring consistent tool visibility and access. - Updated the `evaluate_tool_visibility` function to prioritize curated tools from registered providers, enhancing the overall user experience and security by enforcing whitelist checks. These changes expand the capabilities of the Composio toolkit, providing users with a comprehensive set of tools for interacting with GitHub, while maintaining a focus on user-defined permissions and visibility. * refactor(tools): improve formatting and organization of curated tools - Reformatted the `GITHUB_CURATED`, `NOTION_CURATED`, and other tool constants for better readability by aligning entries vertically. - Enhanced the clarity of the code structure, making it easier to maintain and understand. - Updated related documentation to reflect the changes in formatting and organization. These changes improve the overall readability and maintainability of the codebase, aligning with ongoing refactoring efforts. * feat(catalogs): introduce curated catalogs for various toolkits - Added a new module `catalogs.rs` containing curated tool lists for Slack, Discord, Google Calendar, Google Drive, Google Docs, and more, enhancing the Composio toolkit's integration capabilities. - Updated the `mod.rs` file to include the new `catalogs` module and modified the `catalog_for_toolkit` function to support these curated lists, allowing for better organization and access to toolkit actions. - This addition improves the overall functionality and user experience by providing a comprehensive set of tools for interacting with popular platforms. * feat(post-process): implement HTML to markdown conversion for Gmail responses - Introduced a new `post_process` module to handle per-toolkit response modifications, specifically for converting HTML content to markdown format. - Enhanced the `ComposioExecuteTool` to apply post-processing on successful responses, improving the clarity and usability of data returned from Gmail. - Added tests to ensure the correct functionality of HTML detection and conversion, validating the integrity of the post-processing logic. These changes enhance the user experience by streamlining the handling of HTML content in responses, making it more suitable for further processing and display. * refactor(post_process): reorganize HTML detection constants for improved readability - Moved HTML detection markers in the `looks_like_html` function to a more structured format, enhancing clarity and maintainability. - This change aligns with ongoing efforts to improve code organization and readability within the post-processing module. * refactor(catalogs): enhance formatting and organization of curated tool constants - Reformatted the `SLACK_CURATED`, `DISCORD_CURATED`, and `GOOGLECALENDAR_CURATED` constants for improved readability by aligning entries vertically. - This change enhances the clarity and maintainability of the code, aligning with ongoing refactoring efforts to improve code organization. * feat(connect-modal): wire read/write/admin scope toggles to composio prefs Adds the three scope toggles to the connected-state of the ComposioConnectModal so users can gate which Composio actions the agent may invoke per integration. Loads the stored pref via `composio_get_user_scopes` once the modal lands in the connected phase and persists changes through `composio_set_user_scopes` with optimistic updates and rollback on error. - Adds `getUserScopes` / `setUserScopes` to composioApi. - Adds `ComposioUserScopePref` type mirror. - Renders accessible role="switch" toggles with hint text per row. * refactor(ComposioConnectModal): simplify SCOPE_ROWS definition - Streamlined the definition of SCOPE_ROWS in ComposioConnectModal by removing unnecessary line breaks, enhancing code readability and maintainability. - Updated the agent.toml configuration to include "composio_execute" in the tools list, expanding the capabilities of the integrations agent. * fix(composio): apply curated whitelist + scope pref to integrations_agent prompt The agent prompt for integrations_agent was rendering every action returned by the backend's `composio_list_tools` for each connected toolkit, bypassing the curation/scope filter that the meta-tool layer applies. Concretely the GitHub integrations_agent prompt was showing ~500 actions including non-curated entries like GITHUB_ADD_OR_UPDATE_TEAM_REPOSITORY_PERMISSIONS. Adds `is_action_visible_with_pref(slug, pref)` — a sync helper that mirrors the meta-tool layer's decision logic — and applies it in: - `fetch_connected_integrations_uncached` (bulk session-cached path) - `fetch_toolkit_actions` (per-toolkit spawn-time path) One pref load per toolkit (not per action) keeps the cost minimal. * refactor(fetch_connected_integrations): streamline action visibility filter Simplified the action visibility filter in `fetch_connected_integrations_uncached` by consolidating the filter logic into a single line. This change enhances code readability while maintaining the existing functionality of applying user preferences to the displayed actions. * fix(prompt): drop duplicate `### Available Tools` listing in text-mode preamble Text-mode subagent prompts were rendering the tool catalog twice: once in the prompt template's `## Tools` section (with the richer `Call as: NAME[arg|arg]` signatures from `prompts::ToolsSection::build`) and once in `### Available Tools` under `## Tool Use Protocol` (`Parameters: name:type, ...` format). For an integrations_agent toolkit spawn (~50 actions) this doubled the tool listing bytes for no informational gain. Keep only the protocol preamble (essential for text mode); the catalog stays in `## Tools`. Removes `summarise_parameters` and `first_line_truncated` which were the sole consumers, plus the now-unused `std::fmt::Write` import. * review: address PR feedback (UTF-8 boundary, structured logs, doc, debug) Real fixes: - post_process: walk back to UTF-8 char boundary before truncating to 4096 bytes; previous `&s[..4096]` could panic mid-codepoint. Adds regression test that places a 3-byte char straddling the cutoff. - composioApi: move misplaced `execute` docstring back above `execute` (it had drifted above the new `getUserScopes`). - schemas: include `get_user_scopes` / `set_user_scopes` in the `every_known_schema_key_resolves` test. Diagnosability: - schemas: structured `[composio:scopes]` debug/error logs at entry, exit, and every early-return in `handle_get_user_scopes` / `handle_set_user_scopes` (method + toolkit + pref fields). The memory-not-ready branch now logs an error before returning. - composioApi + ConnectModal: grep-friendly `[composio][scopes]` debug logs around getUserScopes / setUserScopes RPC round-trips and the toggle handler (old → new state, persisted result, errors). - user_scopes: load_or_default now logs the same normalized `key` that `load()` does, so traces correlate across both code paths. Cleanups: - tools: replace external `idx` + `Vec::retain` in `filter_list_tools_response` with a drain + zip + filter_map pattern. - tool_scope: document the no-underscore-in-toolkit-name assumption of `toolkit_from_slug`, and call out the `microsoft_teams` alias. - Cargo.toml: comment on the html2md vs htmd choice. Pushback (no change): - Reviewer asked to split the type-only import in ConnectModal into a separate `import type` line; ESLint's `no-duplicate-imports` rejects that, so the inline `type` form (functionally identical) stays. |
||
|
|
fff24a6c6f |
fix(prompts): v2 prompt pipeline, integrations_agent, and subagent session plumbing (#642)
* refactor(transcript): update session transcript paths and enhance directory structure
- Changed the source of truth path for session transcripts from `sessions/{DDMMYYYY}/{agent}_{index}.jsonl` to `session_raw/{DDMMYYYY}/{agent}_{index}.jsonl` to better reflect the organization of files.
- Updated the logic for creating and resolving transcript paths to accommodate the new directory structure, ensuring compatibility with legacy `.md` files.
- Improved documentation to clarify the changes in file organization and their implications for transcript management.
This refactor enhances the clarity and maintainability of session transcript handling by establishing a more logical file structure.
* refactor(prompts): update tool handling in prompt builders
- Replaced `Vec<String>` with `Vec<ToolSummary<'_>>` for available tools in multiple agent prompt builders, enhancing type safety and clarity.
- Introduced `render_tool_catalog` and `render_connected_integrations` functions to dynamically generate sections in prompts based on available tools and connected integrations.
- Updated the `build` function in various agent prompts to utilize the new rendering functions, ensuring that prompts accurately reflect the current context and available resources.
These changes improve the maintainability and functionality of the prompt generation process across different agents.
* refactor(prompts): streamline prompt builders for agent templates
- Updated the prompt builders for various agents to utilize the sibling `prompt.md` template directly, enhancing clarity and maintainability.
- Replaced `Vec<ToolSummary<'_>>` with `Vec<ToolSummary>` and `Vec<ConnectedIntegration>` for improved type safety in test cases.
- Adjusted the `build` function to ensure consistent formatting and handling of tool catalogs across different agents.
These changes simplify the prompt generation process and prepare the codebase for future enhancements.
* refactor(harness): unify tool filtering and prompt loading for debug consistency
- Exposed `filter_tool_indices` and `load_prompt_source` as `pub(crate)` to ensure that both the live runner and debug dump share the same filtering and loading logic, eliminating discrepancies.
- Enhanced documentation for both functions to clarify their purpose and usage, improving maintainability and understanding of the codebase.
These changes streamline the tool management process and enhance the reliability of debug outputs, ensuring consistency across different contexts.
* refactor(prompts): enhance prompt builders for agent templates
- Updated the prompt builders for various agents to return fully-assembled system prompts, incorporating section helpers from `crate::openhuman::context::prompt`.
- Replaced `Vec<ToolSummary<'_>>` with `Vec<ToolSummary>` and `Vec<ConnectedIntegration>` for improved type safety in test cases.
- Adjusted the `build` function to ensure consistent formatting and handling of user files, tools, and workspace sections across different agents.
These changes streamline the prompt generation process, improve maintainability, and prepare the codebase for future enhancements.
* refactor(prompts): enhance prompt context handling for dynamic sources
- Updated the prompt builders to support fully-assembled prompts from dynamic sources, allowing for more flexible prompt generation.
- Introduced `PromptTool` and `PromptContext` structures to replace `ToolSummary`, improving type safety and clarity in prompt construction.
- Refactored the handling of prompt sources in both the subagent runner and session builder to streamline the integration of dynamic prompts and legacy sources.
These changes improve the maintainability and functionality of the prompt generation process, ensuring accurate representation of available tools and context in agent interactions.
* refactor(prompts): improve prompt context and tool handling
- Enhanced the `PromptContext` structure to include additional fields for better context management, such as `skills`, `dispatcher_instructions`, and `tool_call_format`.
- Replaced `ToolSummary` with `PromptTool` for improved type safety and clarity in prompt generation.
- Updated the handling of dynamic prompt sources in both the subagent runner and debug dump, ensuring consistent integration and rendering of prompts.
- Introduced a mechanism to handle empty visible tool names, enhancing the robustness of prompt generation.
These changes streamline the prompt construction process and improve the overall maintainability of the codebase.
* refactor(prompts): reorganize prompt handling and introduce SystemPromptBuilder
- Moved prompt-related types and builders from `openhuman::context::prompt` to `openhuman::agent::prompts` for better modularity.
- Introduced `SystemPromptBuilder` to streamline the construction of system prompts, allowing for flexible section management.
- Updated module exports to maintain compatibility while enhancing the organization of prompt-related code.
These changes improve the clarity and maintainability of the prompt generation process, aligning it more closely with the agents that utilize these prompts.
* refactor(prompts): unify agent prompt handling and update CLI references
- Removed the "main" alias for the orchestrator in the prompt dumping process, treating it as just another registered agent.
- Updated the `debug-agent-prompts.sh` script to reflect this change, ensuring all agents are included uniformly.
- Revised documentation and error messages in `agent_cli.rs` to replace references to "main" with "orchestrator" for clarity.
- Enhanced the debug dump functionality to maintain consistency across agent prompts, improving overall maintainability and usability.
These changes streamline the prompt handling process and clarify the usage of agent identifiers in the CLI, aligning with the new architecture.
* refactor(prompts): remove CACHE_BOUNDARY references from agent prompts
- Eliminated the CACHE_BOUNDARY marker from various agent prompt files, streamlining the prompt generation process.
- Updated the build functions in multiple agents to ensure consistent handling of workspace rendering without the cache boundary.
- Refactored related prompt handling logic to enhance clarity and maintainability, aligning with the new architecture.
These changes simplify the prompt structure and improve the overall efficiency of prompt generation across agents.
* refactor(prompts): remove cache boundary references from tests and prompts
- Eliminated all instances of cache boundary references from the subagent runner and related tests, simplifying the prompt handling logic.
- Updated test assertions to reflect the removal of cache boundary checks, ensuring consistency across the testing framework.
- Refactored the session manager to streamline the system prompt assembly process without relying on cache boundaries.
These changes enhance the clarity and maintainability of the prompt generation process, aligning with the recent architectural updates.
* refactor(harness): clean up unused imports and streamline code
- Removed unnecessary imports from multiple files, including `RandomState`, `Hasher`, and `SerializeMap`, to enhance code clarity and maintainability.
- Simplified the structure of several modules by eliminating redundant use statements, ensuring a cleaner and more efficient codebase.
These changes contribute to a more organized and readable code structure, aligning with ongoing refactoring efforts.
* refactor(prompts): enhance agent prompt structures and integration handling
- Updated the `orchestrator`, `skills_agent`, and `welcome` prompts to streamline the rendering of connected integrations and delegation guides.
- Introduced dedicated functions for rendering integration information, ensuring clarity in the agent's voice and responsibilities.
- Removed redundant sections from the shared prompt builder, allowing each agent to manage its own prompt content more effectively.
- Improved test coverage for prompt generation, ensuring accurate representation of connected integrations and skills.
These changes enhance the maintainability and clarity of the prompt generation process, aligning with the recent architectural updates.
* refactor(session): update integration handling and clean up prompt parameters
- Revised documentation for `connected_integrations` in the `Agent` struct to clarify its role in the agent's prompt rendering.
- Updated the parameter name in `render_subagent_system_prompt_with_format` to `_connected_integrations` to indicate it is unused, enhancing code clarity.
- Cleaned up import statements in the context module for better organization and maintainability.
These changes improve the clarity of integration handling and streamline the code structure, aligning with ongoing refactoring efforts.
* refactor(agent_cli): simplify command options and improve documentation
- Removed the `--skill` option from the `dump-prompt` command, streamlining the command usage and focusing on essential parameters.
- Updated documentation to clarify the usage of the `dump-prompt` command and its parameters, enhancing user understanding.
- Cleaned up the `DumpFlags` structure by removing unused fields, contributing to a more maintainable codebase.
These changes improve the clarity and usability of the agent CLI, aligning with ongoing refactoring efforts.
* refactor(cli): streamline dotenv loading and clean up prompt rendering
- Introduced `load_dotenv_for_cli` to load environment variables for all CLI entrypoints, ensuring consistent configuration across commands.
- Updated documentation to clarify the purpose of the dotenv loading mechanism.
- Removed unnecessary blank lines in prompt rendering functions across multiple agents, enhancing code readability.
These changes improve the maintainability and clarity of the CLI and prompt handling, aligning with ongoing refactoring efforts.
* refactor(debug-agent-prompts): enhance environment loading and streamline workspace resolution
- Updated the script to load environment variables from a `.env` file, ensuring consistent configuration for prompt generation.
- Simplified workspace resolution by delegating to the binary's internal logic, improving reliability and reducing code duplication.
- Revised documentation to clarify the usage of command options and the impact of environment variables on prompt rendering.
These changes improve the maintainability and clarity of the debug agent prompts script, aligning with ongoing refactoring efforts.
* refactor(agent): remove category filter and simplify agent definitions
- Eliminated the `category_filter` from various agent definitions and related tests, streamlining the agent configuration.
- Updated the `run_list` function in `agent_cli.rs` to reflect the removal of category filtering, enhancing output clarity.
- Revised documentation and comments to remove references to the now-removed category filter, improving overall code maintainability.
These changes contribute to a cleaner and more efficient agent architecture, aligning with ongoing refactoring efforts.
* feat(agents): introduce integrations_agent and tools_agent for enhanced service handling
- Added the `integrations_agent` to manage service integrations via Composio, including a new TOML configuration and prompt structure.
- Introduced the `tools_agent` for general ad-hoc tasks using built-in OpenHuman tools, with its own configuration and prompt.
- Updated the loader to include both agents in the built-in agent list, increasing the total number of agents from 13 to 14.
- Revised orchestrator and welcome prompts to delegate integration tasks to the new `integrations_agent`, ensuring clarity in agent responsibilities.
- Enhanced tests to verify the registration and functionality of the new agents, improving overall test coverage.
These changes expand the capabilities of the agent architecture, allowing for more specialized handling of integrations and tool usage.
* refactor(agents): update references from skills_agent to integrations_agent
- Changed all instances of `skills_agent` to `integrations_agent` across various files, including prompts, CLI commands, and tool registrations.
- Updated documentation and comments to reflect the new agent name, ensuring clarity in agent responsibilities and usage.
- Revised debug scripts to align with the new prompt structure for the integrations agent.
These changes enhance consistency in the codebase and improve the clarity of agent interactions.
* refactor(agents): rename skills_agent to integrations_agent throughout the codebase
- Updated all instances of `skills_agent` to `integrations_agent` in various files, including tests, documentation, and comments.
- Ensured consistency in agent references to improve clarity in agent responsibilities and interactions.
- Revised related code structures to align with the new naming convention, enhancing overall maintainability.
These changes support the transition to the new agent architecture and improve code readability.
* refactor(prompts): implement dynamic prompt rendering for enhanced context handling
- Introduced `DynamicPromptSection` to allow prompts to be built dynamically using a function pointer, enabling real-time access to the `PromptContext`.
- Updated `SystemPromptBuilder` to support dynamic prompts, ensuring that late-arriving state like `connected_integrations` is accurately reflected in the rendered output.
- Revised the prompt handling logic in the agent builder to streamline the integration of dynamic prompts, improving overall flexibility and responsiveness.
These changes enhance the prompt generation process, aligning with the ongoing improvements in agent architecture and context management.
* refactor(prompts): refine delegation guide to display only connected integrations
- Updated the `render_delegation_guide` function to list only the toolkits that are actively connected, omitting unauthorized toolkits to prevent hallucinations during delegation.
- Revised related tests to ensure the prompt correctly reflects the current state of integrations, including scenarios where no integrations are connected.
- Introduced a new utility function to filter out welcome-only tools from non-welcome agents, enhancing the clarity and safety of tool visibility.
These changes improve the accuracy and focus of the delegation guide, aligning with the ongoing enhancements in agent prompt handling.
* refactor(planner): update tool usage and prompt guidelines for read-only operations
- Modified the `agent.toml` configuration to clarify that the planner operates in a read-only mode, specifying that it does not mutate the workspace or memory.
- Revised the prompt guidelines to reflect the read-only nature of the planner, emphasizing the need for explicit nodes for any required writes to be handled by downstream agents.
These changes enhance the clarity of the planner's operational constraints and improve the overall structure of the planning process.
* refactor(config): remove web search enable flag and update related configurations
- Eliminated the `OPENHUMAN_WEB_SEARCH_ENABLED` environment variable and associated logic, as web search is now always enabled by default.
- Updated the configuration schema to reflect the removal of the enable flag from `WebSearchConfig`.
- Adjusted tool registration to ensure web search is always available, simplifying the configuration process.
These changes streamline the web search functionality, ensuring it is consistently available across all sessions.
* refactor(config): update http_request flag to always enabled
- Changed the `http_request` configuration to always be enabled, removing the dependency on the `config.http_request.enabled` flag.
- This adjustment simplifies the configuration process and ensures consistent behavior across the application.
These changes contribute to a more streamlined configuration and enhance the overall reliability of the onboarding process.
* refactor(debug-agent-prompts): transition to dump-all command for agent prompts
- Replaced the previous method of listing agent IDs and dumping prompts with a new `dump-all` command that consolidates the functionality into a single call.
- Updated the script to handle output directory and workspace options more efficiently, leveraging Rust's `dump_all_agent_prompts` for processing.
- Enhanced the handling of the `integrations_agent` to generate separate dumps for each connected toolkit, improving the clarity and organization of output files.
- Revised related logging and summary generation to reflect the new structure, ensuring a more streamlined user experience.
These changes modernize the prompt dumping process, aligning it with the latest architectural improvements and enhancing usability.
* feat(composio): implement dynamic fetching of toolkit actions for integrations
- Added a new `fetch_toolkit_actions` function to retrieve the current action catalogue for a specified Composio toolkit, enhancing the responsiveness of the integrations agent.
- Updated the `subagent_runner` to utilize the fresh action list at spawn time, ensuring that the toolkit's actions reflect the latest backend state.
- Modified the `render_integrations_agent` function to refresh the action catalogue during prompt generation, improving the accuracy of the displayed tools.
These changes enhance the integration experience by providing up-to-date action information, aligning with the ongoing improvements in agent functionality.
* refactor(integrations-agent): update tool visibility and configuration handling
- Modified the `agent.toml` to replace `wildcard` with `named` tools, enhancing control over tool visibility for the integrations agent.
- Updated the `subagent_runner` to ensure that tool visibility aligns with the new TOML configuration, preventing unnecessary stripping of tools.
- Revised the `render_integrations_agent` function to respect the updated tool scope, improving the accuracy of the tool list generated for subagents.
These changes streamline the tool management process, ensuring that only explicitly defined tools are available during agent execution.
* feat(composio): add composio_list_connections tool for dynamic integration detection
- Introduced the `composio_list_connections` tool in the orchestrator's configuration, allowing the agent to detect newly-authorized Composio integrations mid-session.
- Enhanced the `ComposioListConnectionsTool` to filter and return only currently-connected integrations with ACTIVE or CONNECTED status, improving the accuracy of integration management.
- Updated the tool's description to clarify its functionality and usage context.
These changes enhance the agent's ability to manage integrations dynamically, aligning with ongoing improvements in the integration experience.
* fix(prompt): update delegation guide to reference Skills page
- Modified the `render_delegation_guide` function to change the reference from **Settings → Integrations** to the **Skills** page for connecting integrations. This update clarifies the user instructions for integration management.
* feat(session): introduce session key management for sub-agents
- Added `session_key` and `session_parent_prefix` fields to `ParentExecutionContext` to facilitate hierarchical transcript naming for sub-agents.
- Updated `persist_subagent_transcript` to generate transcript filenames based on the parent's session key, ensuring a flat file structure that reflects the parent-child relationship.
- Enhanced `AgentBuilder` and `Agent` to support the new session key management, allowing for better organization of session transcripts.
- Adjusted related functions to ensure proper handling of session keys during agent execution and transcript persistence.
These changes improve the clarity and organization of session transcripts, aligning with the ongoing enhancements in agent functionality.
* refactor(tests): update integrations agent tests for tool scope and transcript handling
- Renamed the `integrations_agent_is_wildcard` test to `integrations_agent_tool_scope_honours_toml` to better reflect its purpose of validating tool scope based on TOML configuration.
- Enhanced the test to assert that the `integrations_agent` correctly recognizes named tools instead of a wildcard.
- Removed the `integrations_agent_has_extra_tools_for_export` test as it is no longer relevant to the current tool management strategy.
- Improved the `latest_in_dir` function to clarify the handling of transcript naming schemes, ensuring proper differentiation between legacy and keyed formats.
These changes streamline the testing process and improve the accuracy of tool scope validation, aligning with recent updates in agent functionality.
* refactor(subagent_runner): improve transcript persistence and streamline inner loop
- Removed the `persist_subagent_transcript` function, transitioning transcript persistence to occur per-iteration within the `run_inner_loop`, enhancing reliability by ensuring transcripts are written immediately after each provider response.
- Updated the handling of session keys to maintain consistent naming for transcripts, reflecting the parent-child relationship in the file structure.
- Simplified the code by eliminating redundant post-loop transcript writes, aligning with recent changes in agent functionality and improving overall clarity in transcript management.
* refactor(agent_cli, subagent_runner, session): improve code readability and formatting
- Enhanced formatting in `agent_cli.rs` for better readability by adjusting the structure of string formatting.
- Streamlined conditional checks in `subagent_runner.rs` to improve clarity and maintainability.
- Simplified the handling of agent IDs in `builder.rs` to reduce line length and improve code flow.
- Updated test cases in `tests.rs` for better alignment and readability of expected values.
- Improved formatting in `debug_dump.rs` to enhance the clarity of toolkit action fetching and logging.
These changes collectively enhance the overall readability and maintainability of the codebase, aligning with ongoing refactoring efforts.
* fix(tests): add missing session_key fields to ParentExecutionContext stub
* fix: address PR review feedback
* fix(prompts-v2): round 2 PR review — dispatcher instructions, workspace-file preservation, cached-tool fallback, transcript persistence
- subagent_runner: populate PromptContext.dispatcher_instructions for Dynamic prompts (was empty string, dropping the ## Tool Use Protocol block in render_tools for PFormat/Json/Native sub-agents)
- subagent_runner: add post-tool persist_transcript after tool results are appended so a mid-round crash doesn't lose tool outputs
- prompts::sync_workspace_file: preserve user-edited workspace files — only overwrite when the file doesn't exist OR its current hash matches the stored builtin hash
- context::debug_dump: mirror runner's cached-tool fallback — keep cached action catalogue on empty/error from fetch_toolkit_actions instead of blanking it
- core::agent_cli: add entry/exit debug logs around dump_all / dump_prompt calls and a trace log around each prompt file write; update module banner to note --toolkit is required when --agent is integrations_agent
|
||
|
|
8d4934e634 |
feat(agent): progressive-disclosure handoff + token-based summarizer threshold (#574) (#586)
* feat(agent): summarizer sub-agent compresses oversized tool results Issue #574. Before this change, tool results larger than a few KB landed verbatim in orchestrator history, burning context budget on raw JSON/HTML/file dumps. The only guardrail was tool_result_budget_bytes which hard-truncated mid-payload, dropping everything past the cut. This PR introduces a dedicated `summarizer` sub-agent (model.hint = "summarization") that the runtime automatically dispatches whenever a tool result exceeds summarizer_payload_threshold_bytes (default 100 KB). The summarizer compresses the payload per an extraction contract that preserves identifiers and key facts, and the compressed summary replaces the raw payload before it enters agent history. Payloads above summarizer_max_payload_bytes (default 5 MB) skip summarization and fall through to the existing truncation path — paying for an LLM call on a 5 MB blob is counterproductive. Scoped to the orchestrator session only. Welcome, skills_agent, researcher, planner, and every other typed sub-agent get None and their tool results are untouched. Gated in build_session_agent_inner by checking agent_id == "orchestrator". Instrumented at both tool-loop paths: - run_tool_call_loop in tool_loop.rs (event-bus/channels path) - Agent::execute_tool_call in session/turn.rs (web channel path via run_single) Both paths call into the same `PayloadSummarizer::maybe_summarize` trait so the threshold check, circuit breaker (3 consecutive failures disables for the session), and sub-agent dispatch policy live in exactly one place (agent/harness/payload_summarizer.rs). The summarizer sub-agent is runtime-dispatched only — it is NOT exposed as a delegation tool to the orchestrator's LLM. It is listed in the orchestrator's `subagents = [...]` for explicit registration, but `collect_orchestrator_tools` filters out the `summarizer` id and never synthesises a `delegate_summarizer` tool. Files: * NEW: src/openhuman/agent/agents/summarizer/{agent.toml,prompt.md} * NEW: src/openhuman/agent/harness/payload_summarizer.rs (trait + SubagentPayloadSummarizer impl + circuit breaker + tests) * src/openhuman/agent/agents/mod.rs — register summarizer in BUILTINS * src/openhuman/agent/agents/orchestrator/agent.toml — list summarizer in subagents (runtime-only, no LLM delegation tool) * src/openhuman/agent/harness/mod.rs — declare module * src/openhuman/agent/harness/builtin_definitions.rs — expect summarizer in `expected_builtin_ids_are_present` * src/openhuman/agent/harness/tool_loop.rs — thread Option<&dyn PayloadSummarizer> into run_tool_call_loop + agent_turn, intercept at the tool-execution success site, plus a new integration test using a MockSummarizer * src/openhuman/agent/harness/tests.rs — thread None through the existing run_tool_call_loop callsites * src/openhuman/agent/harness/session/turn.rs — same interception in Agent::execute_tool_call * src/openhuman/agent/harness/session/types.rs — payload_summarizer field on Agent and AgentBuilder * src/openhuman/agent/harness/session/builder.rs — .payload_summarizer() setter + orchestrator-only construction in build_session_agent_inner * src/openhuman/agent/bus.rs — pass None for the bus path * src/openhuman/config/schema/context.rs — summarizer_payload_threshold_bytes + summarizer_max_payload_bytes on ContextConfig * src/openhuman/tools/orchestrator_tools.rs — filter out the summarizer id from delegation-tool synthesis Tests: unit tests in payload_summarizer pin the pass-through rules (below threshold, above max cap, breaker tripped) and the prompt construction. Integration test in tool_loop::tests uses a MockSummarizer to verify the interception wires through end-to-end. Closes tinyhumansai/openhuman#574. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(tools): csv_export tool + skills_agent oversized output handling When a Composio tool returns a large payload inside skills_agent, the agent now has two prompt-driven paths: Path A — user wants an answer derived from the data: skills_agent extracts the answer in its next iteration and returns a targeted response (no file I/O needed). Path B — user wants the actual raw dataset: skills_agent calls csv_export (tabular data) or file_write (non-tabular) to persist the output to workspace/exports/, then returns a summary + file path. Changes: * NEW: src/openhuman/tools/impl/filesystem/csv_export.rs — CsvExportTool: parses JSON array, formats as CSV, writes to workspace/exports/{filename}. Handles missing keys (empty cells), nested values (JSON-serialised), and optional column ordering. Sandboxed via SecurityPolicy. * src/openhuman/tools/impl/filesystem/mod.rs — wire module * src/openhuman/tools/ops.rs — register CsvExportTool * src/openhuman/agent/harness/definition.rs — new extra_tools field on AgentDefinition, allowing named system tools to bypass category_filter * src/openhuman/agent/harness/subagent_runner.rs — inject extra_tools into allowed_indices after category filtering * src/openhuman/agent/agents/skills_agent/agent.toml — add file_write + csv_export via extra_tools * src/openhuman/agent/agents/skills_agent/prompt.md — new "Handling Oversized Tool Results" section with Path A (extract answer) vs Path B (export file) decision tree, intent-detection heuristics, and examples Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(agent): progressive-disclosure handoff for skills_agent + token-based summarizer threshold (#574) Two layered fixes for #574 plus supporting changes. ## Summarizer thresholds in tokens, not bytes Renamed `ContextConfig::summarizer_payload_threshold_bytes` -> `summarizer_payload_threshold_tokens` (default 500 000, was 100 KB in bytes) and `summarizer_max_payload_bytes` -> `summarizer_max_payload_tokens` (default 2 000 000). Token count is estimated at ~4 chars/token via a local helper that mirrors `tree_summarizer::types::estimate_tokens`. Serde aliases on both fields keep existing config.toml files parsing. `payload_summarizer.rs` now compares token estimates for the threshold and cap, with both `tokens=` and `bytes=` surfaced in log events. ## Progressive-disclosure handoff for skills_agent `skills_agent` typed subagents (when spawned with a Composio toolkit) now receive a per-spawn `ResultHandoffCache` and a new built-in `extract_from_result` tool. When a per-action tool returns more than `HANDOFF_OVERSIZE_THRESHOLD_TOKENS` (20 000 tokens), the raw payload is stashed in the cache and a compact placeholder replaces it in history: [oversized tool output: 187432 bytes - stashed as result_id="res_1"] Preview (first 1500 chars): ... If the preview does not answer your task, call: extract_from_result(result_id="res_1", query="<specific question>") The subagent can answer from the preview, call `extract_from_result` with a targeted query, or re-call the original tool with tighter filters. `extract_from_result` dispatches the `summarizer` subagent against the cached payload with the query as the extraction contract. For payloads over `SUMMARIZER_CHUNK_CHAR_BUDGET` (60 000 chars) the extractor runs a chunked map-reduce: - split at blank-line / newline boundaries - map each chunk to a per-chunk partial via bounded concurrency (`buffer_unordered(3)`) to avoid storming the provider gateway - reduce partials in one more summarizer turn, or fall back to the concatenation if the reduce stage fails Falls back gracefully when the summarizer definition is missing from the registry (placeholder + preview still work; just no extractor). ## Text-mode dispatcher for skills_agent + tighter tool filter Provider-side grammar decoders (Fireworks etc.) cap tool-schema rules at 65 535 (uint16_t). Large Composio toolkits - Gmail, Notion, GitHub, Salesforce, HubSpot, Google Workspace - ship per-action JSON schemas dense enough that even a handful of them exceed the cap. - `subagent_runner::run_inner_loop` now detects `skills_agent` and omits `tools: [...]` from the API request, instead appending an `<tool_call>{...}</tool_call>` XML protocol block (with compact per-tool parameter summaries) into the system prompt. Tool calls are parsed out of the model's free-form response via the shared `parse_tool_calls` helper. Mirrors XmlToolDispatcher behaviour. - `top_k_for_toolkit()` picks 12 for HEAVY_SCHEMA_TOOLKITS (the six listed above plus googledocs, googlesheets, microsoftteams) vs the default 25 for lighter toolkits. ## Skills_agent agent.toml / prompt changes - Dropped `csv_export` from `skills_agent.extra_tools` (kept `file_write`) - the export tool was triggering superfluous file writes after extraction had already answered the query. - `builtin_definitions::skills_agent_has_extra_tools_for_export` test inverted to assert `csv_export` is NOT present. - `prompt.md` re-aligned: Path B now references only `file_write`. ## Wiring - `session/builder.rs`: threshold rename wiring (`_tokens`). - `subagent_runner.rs::run_inner_loop`: new `handoff_cache` parameter (threaded through both call sites; fork-mode passes `None`). ## Tests All green: - `payload_summarizer::tests`: 6/6 (thresholds in tokens) - `tool_loop::tests`: 10/10 (MockSummarizer integration) - `subagent_runner::tests`: 19/19 - `builtin_definitions::tests`: 5/5 (csv_export removal) - `csv_export::tests`: 7/7 (implementation kept, wiring removed) Closes #574. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * perf(agent): drop reduce LLM call and pre-clean tool outputs in chunked extraction Two optimisations to the oversize-handoff / extract_from_result pipeline: 1. Concat chunk summaries in order; drop reduce stage. The reduce-stage summarizer call was the slowest single turn of the pipeline and a single point of failure when the upstream provider hung — a hung reduce burned minutes before timing out. Map summaries are now sorted by original chunk index and concatenated with a plain-separator join. For listing/extraction queries this is equivalent; for top-N / global-ordering queries the caller can post-process. Observed: 8m24s → ~70s on a Notion SEARCH run. 2. Generic cleaner before the oversize check. Strips <script>/<style>/<svg> blocks, HTML comments, data:...;base64,... inline URIs, remaining HTML tags, and collapses whitespace. Applied on every tool output so results that are mostly markup drop below the 20k-token threshold and skip the extract pipeline entirely; the ones that remain oversized get chunked on clean bytes. Debug log emits before/after bytes + saved_pct per call. No changes to the extract_from_result tool surface or placeholder format. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(chat): rearm 2-min silence timer on inference progress events The client-side safety timer in Conversations.tsx was a flat 120s deadline from the send, not a silence window. Long agent turns (chained tool calls, chunked extraction fan-outs, subagent spawns) would trip the timeout even though the backend was actively making progress — showing "No response from the assistant after 2 minutes" while the server was still working. The effect now watches inferenceStatusByThread and rearms the timer on every status change for the sending thread. Any tool_call / tool_result / iteration_start / subagent_{spawned,done} event resets the 120s window. When status is cleared (chat_done / chat_error), the timer is dropped. A truly silent server still fails after 120s as before. Tracks the sending thread id in a ref (sendingThreadIdRef) so switching threads mid-turn doesn't move the timer's reference point. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
c7487324cc |
feat(channels): dynamic filler messages during long agent turns (#600) (#636)
* refactor(channels): add extract_message_id helper for varied response shapes
Backend send_channel_message responses use at least three shapes:
{"id":"..."}, {"data":{"id":"..."}}, and {"messageId":1456,"success":true}.
The last one returns the id as a JSON number, so the prior inline
as_str()-only extraction silently dropped it. Consolidate the extraction
into a single helper that handles str/i64/u64 candidates and routes both
streaming-edit and thinking-message send paths through it. Fixes the
silent id loss that left thinking bubbles undeletable (#600).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(channels): latch thinking edits off when first POST yields no id
When the initial thinking POST returned 200 but carried no message id
(either because the response shape was unexpected or the send itself
failed before C1), every subsequent thinking_dirty tick re-entered the
"send new message" branch. The user then saw one standalone italic
bubble per accumulated snippet instead of a single evolving one. Add a
thinking_edit_disabled latch that is set in both failure paths and
guard the edit_timer branch on it so we post at most one thinking
bubble per turn when the id is unrecoverable (#600).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* style(channels): label thinking bubble with explicit "Thinking:" header
A bare italicized snippet under a 💭 emoji reads ambiguously in chat —
it could be a user quote, a system note, or assistant output. Prefix
the italic body with an explicit "Thinking:" line so the ephemeral
bubble is unmistakably the LLM's reasoning stream and visually distinct
from both filler messages and the final reply (#600).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(channels): reply-first finalize with orphan draft recovery
Two problems in the old finalize path. (1) The thinking bubble was
deleted before the reply was sent, leaving the chat momentarily empty
between the two round-trips. (2) When the final edit failed, the half-
streamed draft was left in place and the user never received the
canonical response — a silent data-loss hole during edit-endpoint
flakiness. Wrap the three delivery paths in a 'send: labeled block so
they share a single cleanup tail, delete the orphan draft and send a
fresh atomic reply on edit failure, and move the thinking-bubble
deletion to after the reply is on screen so the chat never blinks
empty (#600).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat(channels): ephemeral filler messages every 13s with dynamic snippets
Long agent turns (30–90 s) leave the chat static once the thinking
stream goes quiet and progressive edits stop, which reads as a frozen
bot. Add a third timer branch in the inbound loop that posts a short
"still working" message every FILLER_INTERVAL (13 s, tuned to stay
inside Telegram's ~1 msg/sec chat cap with headroom).
Each filler prefers a tail slice of the live thinking_accumulator
(last MAX_FILLER_CHARS = 200 Unicode scalars, trimmed at a word
boundary so it reads cleanly) so the user sees the agent's actual
reasoning instead of canned text. When the accumulator hasn't advanced
since the last filler we fall through to a rotating STATIC_FILLERS
pool ("💭 Still working on it…", etc.) so the chat still moves. All
filler ids are tracked in StreamingState.filler_message_ids and
deleted in finalize_channel_reply alongside the thinking bubble, so
the user ends each turn seeing only the canonical reply. A
filler_disabled latch stops hammering endpoints that reject filler
sends twice in a row.
Verified end-to-end on Telegram with both long (74 s, 5 fillers) and
short (32 s, 2 fillers) turns — all ephemeral messages cleaned up
cleanly after the final reply landed (#600).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
da5fe9260b |
fix(local_ai): hard-override to disabled until explicit opt-in (#573) (#637)
* refactor(local_ai): default to opt-in on all devices (#573) Local AI now defaults to disabled whenever the user has not explicitly picked a tier, regardless of device RAM. The onboarding flow and Settings panel remain the only ways to turn it on. Previously the bootstrap only disabled local AI on <8 GB devices and auto-applied a recommended preset on larger hosts; this flip completes the MVP goal of cloud-first defaults with a single, opt-in local model. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * style(local_ai): apply cargo fmt to bootstrap tests (#573) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(onboarding): present cloud AI as default on sufficient-RAM path (#573) Flips the LocalAIStep sufficient-RAM screen so the primary button is "Continue with Cloud" and local AI appears as an explicit opt-in ("Use local AI instead"). This aligns onboarding with the new opt-in bootstrap: every device now starts on cloud unless the user chooses local AI. The low-RAM cloud-fallback screen is unchanged. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * test(onboarding): cover opt-in local AI semantics in LocalAIStep (#573) Updates the sufficient-RAM path tests to match the cloud-primary UI: the default "Continue with Cloud" click advances without triggering local AI bootstrap, and the secondary "Use local AI instead" opt-in still starts the recommended-preset bootstrap and propagates errors. Low-RAM cloud-fallback tests are unchanged. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(local_ai): add opt_in_confirmed marker to local AI config (#573) Bootstrap will hard-override `enabled=false` unless this marker is true, ensuring existing installs with a stale `selected_tier` from the pre-MVP default-on era fall back to cloud until the user explicitly re-opts in. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(local_ai): hard-override local AI to disabled until explicit opt-in (#573) Every bootstrap path now returns `enabled=false` unless `opt_in_confirmed` is true, regardless of device RAM or `selected_tier`. This closes the regression where upgrading users with a persisted `selected_tier` bypassed the onboarding opt-in and started local AI without consent. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(local_ai): set opt_in_confirmed on apply_preset and surface MVP tier only (#573) `apply_preset` is the single source of truth for the opt-in marker: any non-disabled tier flips it true, `disabled` clears it. The preset RPC now returns `mvp_presets()` so the Settings UI exposes only the allowlisted `ram_2_4gb` tier, matching the MVP scope already enforced on the onboarding path. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * revert(local_ai): keep full preset catalog in presets RPC (#573) Revert the `mvp_presets()` swap in `handle_local_ai_presets`. PR #588 already renders all 5 tier cards in Settings with non-MVP tiers shown as "Coming soon" / non-selectable, and that roadmap visibility is the intended UX. Returning only the MVP tier from the RPC hid the other 4 cards entirely and broke that signal. The opt-in gate still holds: `apply_preset` remains the single writer of `opt_in_confirmed`, the RPC guard continues to reject non-MVP apply_preset calls, and the bootstrap hard-override still clamps stale configs. This commit only rolls back the UI catalog surface. Fixes failing `json_rpc_local_ai_device_profile_and_presets` integration test which expects 5 presets. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |