Commit Graph
1141 Commits
Author SHA1 Message Date
f098cfda40 test(coverage): batches 13–18 — Rust unit tests for 40+ modules (#530) (#618)
* test(coverage): batch 13–14 — core/all registry, proxy_config tool (#530)

Add 36 new tests:
- core/all: registry integrity (no duplicates, schema-controller parity),
  rpc_method_name, namespace_description, validate_params, schema lookup
- proxy_config: parse_scope, parse_string_list (CSV, array, errors),
  parse_optional_string_update, env_snapshot, proxy_json, security gates

All 4096 tests pass.

* test(coverage): batch 15 — memory/store/unified/helpers pure functions (#530)

Add 31 new tests for helpers module:
- vec_to_bytes/bytes_to_vec roundtrip, cosine_similarity (identical,
  orthogonal, mismatched, zero), collapse_whitespace, normalize_search_text,
  tokenize_search_terms, normalize_graph_entity/predicate, json_string_array,
  merge_unique_string_arrays, json_i64 (int/float/missing/string),
  recency_score (current/old/future), chunk_document_content

All 4127 tests pass.

* test(coverage): batch 16 — terminal, supervision, security detect, trigger history (#530)

Add 27 new tests across 4 modules:
- accessibility/terminal: is_text_role, is_terminal_app, looks_like_terminal_buffer,
  extract_terminal_input_context, noise line detection
- channels/runtime/supervision: compute_max_in_flight_messages clamping
- security/detect: all sandbox backend fallback paths (landlock, firejail,
  bubblewrap, docker on non-linux), disabled-via-enabled-false
- composio/trigger_history: list_recent with limit, empty store, field validation

All 4154 tests pass.

* test(coverage): batch 17 — learning, update, encryption, doctor, service schemas + skills/types (#530)

Add 49 new tests across 6 modules:
- learning/schemas: catalog, schema validation, unknown fallback
- update/schemas: check/apply schemas, optional staging_dir, controller parity
- encryption/schemas: encrypt/decrypt schemas, read_required helper
- doctor/schemas: report/models schemas, read_optional helper (none/null/value/error)
- service/schemas: 8 lifecycle functions, restart optional params, daemon_host
- skills/types: ToolResult success/error/json/mixed, serde roundtrip, ToolContent

All 4203 tests pass.

* test(coverage): batch 18 — accessibility/keys key-state probes (#530)

Add tests for is_tab_key_down/is_escape_key_down (platform-safe),
macOS-specific constant validation.

All 4216 tests pass.

* test(coverage): batch 19 — service/restart, memory/store/factories (#530)

Add 7 new tests:
- service/restart: default source/reason, whitespace trimming, empty-string
  defaults, RestartStatus serde, startup delay noop
- memory/store/factories: effective_memory_backend_name, migration-disabled error

All 4223 tests pass.

* test(coverage): batch 20 — tree_summarizer schemas, subconscious schemas (#530)

Add 38 new tests across 2 modules:
- tree_summarizer/schemas: catalog parity, all 5 functions, param helpers
  (read_required, read_optional, read_optional_timestamp), type_name,
  namespace_input
- subconscious/schemas: catalog parity, all 10 functions, required input
  validation, field/field_req/field_opt helpers

All 4261 tests pass.

---------

Co-authored-by: Steven Enamakel <31011319+senamakel@users.noreply.github.com>
2026-04-16 20:42:50 -07:00
Mega MindandGitHub ec309c8f90 feat(onboarding): complete conversational onboarding stack and UX audit (#619)
* fix(onboarding): guard complete_onboarding against premature flag flip (#591)

- Remove premature `chat_onboarding_completed = true` flip from
  `set_onboarding_completed` in config/ops.rs. The flag is now
  exclusively owned by the welcome agent via `complete_onboarding`.

- Strip auto-finalize side-effect from `check_status` action. It is
  now pure read-only and returns `onboarding_status`, `exchange_count`,
  and `ready_to_complete` in the snapshot instead of `finalize_action`.

- Add engagement guard to `complete` action: rejects with a descriptive
  error unless exchange_count >= 3 OR ≥1 Composio integration connected.
  Auth check also enforced before any write.

- Add process-global `WELCOME_EXCHANGE_COUNT` (AtomicU32) with
  `increment_welcome_exchange_count()` / `get_welcome_exchange_count()`.
  Dispatch layer calls `increment_welcome_exchange_count()` each time a
  user message routes to the welcome agent.

- Update `welcome_proactive.rs` snapshot call to new signature and
  prompt copy to reflect `onboarding_status: "pending"` / no pre-flip.

- Add 7 pure-logic unit tests for `engagement_criteria_met`, plus tests
  for the new exchange counter and updated snapshot shape.

Fixes #591

* style: cargo fmt for complete_onboarding.rs

* feat(onboarding): inject CONNECTION_STATE block into welcome-agent turns (#593)

Add `build_connection_state_block()` to dispatch.rs: fetches current
Composio integration status (with a 3s timeout for graceful degradation)
and formats it as a `[CONNECTION_STATE]...[/CONNECTION_STATE]` block.

After `resolve_target_agent` selects the welcome agent, the block is
appended to the last user message in history so the agent always has
up-to-date connection state without spending a tool call. This captures
OAuth completions that happened mid-conversation (user clicked auth link
in chat, authenticated in browser, came back).

Scoped strictly to welcome-agent turns (chat_onboarding_completed=false).
Orchestrator turns are unaffected.

Fixes #593
Part of #599

* style: cargo fmt for dispatch.rs

* feat(welcome): parallel template messages with LLM inference for faster perceived welcome (#592)

Show two template messages immediately while the LLM runs in the
background, cutting the perceived wait from ~15s to ~0s:

- Template 1 (t≈0ms): time-of-day greeting that names any connected
  channels, built from the status snapshot without extra I/O
- Template 2 (t=4s): "Getting everything ready for you..." loading
  indicator, published via tokio::join! alongside the LLM future
- LLM response: published when inference completes, opened directly
  with personalised setup content (no duplicate greeting)

`run_proactive_welcome` now fires three `ProactiveMessageRequested`
events rather than one. The two template helpers (`time_of_day_greeting`,
`build_template_greeting`) are pure functions covered by 6 new unit
tests. `prompt.md` gains a proactive-invocation section explaining that
greeting templates are pre-delivered and the agent must skip them.

* feat(welcome): add composio_authorize tool and inline auth link guidance (#594)

Wire `composio_authorize` into the welcome agent's tool allowlist and
teach the prompt how to offer OAuth links directly in chat:

- agent.toml: add "composio_authorize" to the named tools list so the
  welcome agent can call it during multi-turn conversations
- prompt.md: new "Offering inline auth links" section covering the
  consent-first flow (offer → user agrees → call tool → markdown link →
  confirm success via [CONNECTION_STATE] block on next turn)
- prompt.md: toolkit slug reference table for the common services
- prompt.md: auth link rules (no speculative calls, no bare URLs,
  one service at a time, await CONNECTION_STATE before confirming)
- prompt.md: two new "What NOT to do" bullets for composio_authorize
  misuse patterns

* feat(welcome): conversational onboarding prompt for issue #595

- Rewrite prompt.md: multi-turn flow, Gmail-first, no silent first turn
- Align with check_status/complete and ready_to_complete semantics
- Update proactive injection copy to match new tool wording

Made-with: Cursor

* feat(onboarding): add completion readiness reason

Made-with: Cursor

* docs(ux): add onboarding and welcome audit for #563

Made-with: Cursor

* docs(welcome): clarify OAuth link behavior in onboarding prompt

Updated the onboarding prompt to explicitly state that clicking the Gmail connection link opens the user's default browser for authentication. Added guidance to return to the chat after completing the authorization process.

* fix(onboarding): resolve CI test and proactive greeting review

Made-with: Cursor
2026-04-16 14:14:24 -07:00
github-actions[bot] dced586a18 chore(release): v0.52.18 v0.52.18 2026-04-16 19:46:22 +00:00
Steven EnamakelandGitHub 7db71408bd feat(local_ai): default to cloud fallback on <8GB RAM devices (#589)
* Enhance draft message handling in streaming edits

- Introduced a new `draft_sent` field in the `StreamingState` struct to track when a draft message has been posted, decoupling the existence of a draft from the ability to edit it.
- Updated the `flush_streaming_edit` function to set `draft_sent` upon posting a draft, ensuring that duplicate messages are not sent if the backend fails to return an ID.
- Modified the `finalize_channel_reply` function to prevent sending a fresh message if a draft was already posted without an ID, improving user experience by avoiding duplicate bubbles.

These changes enhance the robustness of message handling during streaming edits, ensuring a smoother interaction for users.

* feat(onboarding): enhance LocalAIStep for low-RAM device handling

- Added logic to determine if the device is below the RAM threshold, defaulting to a cloud AI fallback if necessary.
- Introduced a new UI for low-RAM devices, informing users about the cloud mode and providing options to continue with cloud AI or force-enable local AI.
- Updated the `ensureRecommendedLocalAiPresetIfNeeded` function to return a `recommend_disabled` flag based on device RAM.
- Enhanced tests to cover new cloud fallback UI and local AI consent handling.

These changes improve the onboarding experience by providing clearer options based on device capabilities.

* style: apply prettier formatting to LocalAIStep

* feat(local_ai): unlock model selection + add Disabled/cloud fallback option

- Remove MVP ceiling that clamped selection to the 2-4 GB tier, in bootstrap
  and in the apply_preset RPC — users can now pick any tier.
- Add special "disabled" tier string to apply_preset that toggles
  config.local_ai.enabled = false so the app uses the cloud summarizer.
- Presets RPC now returns local_ai_enabled so the UI can render the
  currently-active state correctly.
- Rewrite DeviceCapabilitySection: tiers are now clickable buttons that
  call apply_preset; adds a "Disabled — Cloud fallback" card at the top
  marked "Recommended" on low-RAM devices and "Active" when enabled=false.
- Remove the MVP message copy from the model tier panel.
- Remove unread-count badge from the bottom tab bar.

* refactor(onboarding): streamline LocalAIStep and update local AI preset handling

- Removed the `ensureRecommendedLocalAiPresetIfNeeded` function call from `LocalAIStep`, replacing it with `openhumanLocalAiPresets` to directly fetch preset information.
- Updated the logic in `ensureRecommendedLocalAiPresetIfNeeded` to ensure the recommended tier is applied correctly based on user consent, improving clarity in the local AI setup process.
- Enhanced tests to mock the new `openhumanLocalAiPresets` function, ensuring coverage for low-RAM device scenarios and local AI consent handling.
- Updated documentation in the `schemas.rs` file to reflect changes in the data structure returned by the presets RPC.

These changes improve the onboarding experience by providing a more direct and efficient method for managing local AI presets based on device capabilities.

* test(LocalAIStep): improve mock implementation for local AI presets

- Refactored the mock for `openhumanLocalAiPresets` to enhance readability and maintainability.
- Ensured the mock returns consistent device information and preset details for testing scenarios.
- This change supports better test coverage and clarity in the LocalAIStep component's behavior during onboarding.

* fix(local_ai): preserve hadSelectedTier semantics in bootstrap return

hadSelectedTier / selectedTier represent the incoming state ("was a tier
already selected before this call"), not the post-apply state. Setting
both to the just-applied tier broke the existing localAiBootstrap
contract and the unit test that encodes it.

The Rust-side persistence fix is independent: removing the
recommend_disabled short-circuit ensures openhumanLocalAiApplyPreset
actually writes the selected tier to disk on the opt-in path, so
config_with_recommended_tier_if_unselected() honors the user's choice.
2026-04-16 10:20:28 -07:00
923c920ad5 fix(tauri): hide main window on close instead of destroying it (#601) (#610)
* fix(tauri): hide main window on close instead of destroying it (#601)

On macOS, clicking the close button destroyed the main window. Since a
tray icon keeps the process alive, subsequent dock-icon clicks fired
RunEvent::Reopen but get_webview_window("main") returned None — the
window was gone. Now CloseRequested is intercepted: the close is
prevented and the window is hidden instead, so Reopen and tray clicks
can show it again.

Closes #601

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style(tauri): cargo fmt line wrap

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 10:18:46 -07:00
Steven EnamakelandGitHub 2bb20faa55 feat(threads): dedicated threads controller with per-thread session scoping (#590)
* feat(conversations): implement thread management features including creation and deletion

- Added functionality to create new conversation threads with unique IDs and titles based on the current date and time.
- Introduced a deleteThread action to remove existing threads, updating the selected thread accordingly.
- Enhanced the UI to display threads in a sidebar, allowing users to select and delete threads easily.
- Updated the Conversations component to handle thread loading and selection more efficiently, ensuring a smoother user experience.

Also updated the OpenHuman package version to 0.52.15 in Cargo.lock files.

* refactor(conversations): rename createThreadLocal to createNewThread and streamline thread creation logic

- Updated the function name from `createThreadLocal` to `createNewThread` for clarity and consistency.
- Simplified the thread creation process by removing the manual ID and title generation, leveraging the new API method for automatic thread creation.
- Adjusted the Conversations component to utilize the new `handleCreateNewThread` function, enhancing readability and maintainability.
- Removed unused thread ID and title generation functions to clean up the codebase.

This refactor improves the overall structure and clarity of the thread management functionality.

* refactor(memory): remove deprecated conversation thread management functions

- Eliminated unused functions related to listing, creating, updating, appending, and deleting conversation threads in memory operations.
- This cleanup enhances code maintainability and reduces complexity in the memory module.
- The refactor focuses on streamlining the conversation management logic, aligning with recent changes in the thread handling API.

* refactor(api): update thread API method names and remove deprecated functions

- Renamed thread-related API methods to align with the new naming convention, improving clarity and consistency.
- Removed deprecated functions related to thread creation, streamlining the API and enhancing maintainability.
- Adjusted the implementation of existing methods to reflect the updated API structure, ensuring proper functionality.

* refactor(thread): simplify thread state management and remove unused properties

- Streamlined the thread state by removing the lastViewedAt property and related logic, enhancing clarity in unread message counting.
- Updated the BottomTabBar component to reflect the new unread count logic, which now simply returns the length of threads.
- Removed the setLastViewed action from the Conversations component, further simplifying the thread management logic.
- Introduced a new deleteThread action to handle thread deletion, ensuring proper state updates and selection handling.
- Overall, these changes improve maintainability and reduce complexity in the thread management system.

* style(threads): apply cargo fmt

* refactor(tabbar): remove conversations unread badge

* update(Cargo.lock): bump OpenHuman package version to 0.52.15

* test(threadApi): update RPC method names to threads namespace

* refactor(conversations): update model ID for chat functionality

- Changed the model ID used in the chatSend function from `agentic-v1` to `reasoning-v1`, clarifying the purpose of each model.
- Added comments to explain the distinction between the reasoning model and the agentic model, enhancing code readability and maintainability.
2026-04-16 10:16:04 -07:00
YellowSnnowmannandGitHub ead0862d50 fix(onboarding): prevent home page flash after onboarding completion (#609)
* fix(onboarding): prevent home page flash after onboarding completion

Navigate to /conversations before persisting onboarding_completed so
the overlay stays rendered during the route transition. A local
isDismissing flag keeps the overlay visible until the flag is persisted
and the conversations screen is active, eliminating the brief home page
flash.

Closes #598

* fix(onboarding): enhance onboarding overlay with location tracking and improved dismissal logic
2026-04-16 10:14:04 -07:00
CodeGhost21andGitHub ea6895f19d test(coverage): batch 5–8 — Rust unit tests toward 80% for 20 modules (#607)
* test(coverage): batch 5–8 — Rust unit tests toward 80% for 20 modules (#530)

Add 244 new unit tests across 20 modules to push line coverage from
75.06% → 75.70% overall. Modules improved:

- api/socket: 77% → 100%
- rpc/dispatch: 75% → 100%
- config/schema/channels: 77% → 100%
- composio/gmail/sync: 78% → 97%
- composio/notion/sync: 77% → 96%
- webhooks/router: 78% → 96%
- agent/hooks: 78% → 92%
- config/schema/proxy: 76% → 90%
- local_ai/device: 79% → 89%
- text_input/schemas: 73% → 89%
- agent/harness/interrupt: 76% → 88%
- billing/schemas: 79% → 87%
- screen_intelligence/schemas: 78% → 81%
- about_app/types, health/schemas, app_state/schemas,
  core/types, config/schema/identity_cost, cron/scheduler

Tests cover: schema catalog integrity, param deserialization,
serde roundtrips, helper functions, edge cases, error paths.
All 3974 tests pass.

* test(coverage): batch 9–10 — browser_open, update_memory_md, credentials profiles, cost tracker (#530)

Add 53 new tests across 4 more modules:
- browser_open: IPv4/IPv6 private ranges, host matching, normalize_domain edges
- update_memory_md: empty file, section creation, unknown action, param validation
- credentials/profiles: token expiry, CRUD operations, active profile management
- cost/tracker: budget warnings, monthly exceeded, model stats aggregation

All 4027 tests pass.

* test(coverage): batch 11–12 — channels schemas, conversation store (#530)

Add 33 new tests:
- channels/controllers/schemas: per-function input validation, param
  deserialization, helper coverage
- memory/conversations/store: thread lifecycle (create, delete, idempotent),
  multi-thread, empty/nonexistent thread, purge empty store

All 4060 tests pass.
2026-04-16 10:13:33 -07:00
7f686003a0 feat(core): gate service lifecycle on user login/logout (#582) (#603)
* refactor(voice): wrap CancellationToken in Mutex for restart support

The VoiceServer singleton uses OnceCell so it can't be recreated.
CancellationToken is one-shot — once cancelled, stays cancelled.
Wrapping it in std::sync::Mutex + adding fresh_cancel() allows
stop() then run() to work within the same process (logout → re-login).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(screen_intelligence): wrap CancellationToken in Mutex for restart support

Same pattern as voice server — enables stop() then run() within the
same process for logout → re-login cycles.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(credentials): add start/stop helpers for login-gated services

Add start_login_gated_services() and stop_login_gated_services() to
credentials/ops.rs. Wire start into store_session() (after login) and
stop into clear_session() (on logout) so voice, autocomplete, screen
intelligence, and local AI are properly managed around auth lifecycle.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(core): defer service startup behind login gate in jsonrpc.rs

Replace the unconditional service startup block with a login check.
If active_user.toml exists, start services immediately via the new
start_login_gated_services() helper. Otherwise defer until the login
handler triggers it. Autocomplete shutdown hook remains unconditional.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(screen_intelligence): wait for Stopped state in stop() to prevent restart race

stop() now polls until the run-loop sets ServerState::Stopped (5s timeout).
Prevents fast logout→login from seeing stale Idle/Running state and
skipping the restart.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(dictation): add stop() to prevent listener accumulation across re-logins

Store the spawned task JoinHandle and abort it on stop(). Prevents
duplicate rdev hotkey listeners from stacking across logout→login cycles.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(credentials): call dictation_listener::stop() on logout

Wire the new dictation stop into stop_login_gated_services() so the
hotkey forwarder task is aborted when the user logs out.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 10:12:56 -07:00
github-actions[bot] cf35f27cd6 chore(release): v0.52.17 v0.52.17 2026-04-16 14:28:49 +00:00
YellowSnnowmannandGitHub e8639adae0 fix: Improve deep link authentication state management and improve logout (#608)
* feat(deepLink): implement deep link authentication state management

- Added a new module for managing deep link authentication state, including processing states and error handling.
- Integrated deep link authentication state management into the desktop deep link listener, enhancing the handling of authentication flows.
- Introduced functions to begin, complete, and fail deep link authentication processing, improving user feedback during the authentication process.

* refactor(settings): simplify logout process and enhance error handling

- Removed onboarding completion flag from the logout process to streamline functionality.
- Improved error handling during logout to provide user feedback if the logout fails.
- Updated comments to clarify the behavior of session clearing and routing after logout.
- Integrated deep link authentication state management into the settings component for better user experience.

* refactor(settings, deepLink): enhance logout and session management

- Simplified the logout process by removing unnecessary flags and improving error handling.
- Introduced a new function to manage signed-out state in the core state provider.
- Streamlined session token application in the deep link listener for better authentication flow.
- Updated comments for clarity on session clearing and routing behavior post-logout.

* chore(dependencies): update OpenHuman to version 0.52.16 in Cargo.lock files

* refactor(format): format the code
2026-04-16 19:07:14 +05:30
a78506f6a3 fix(agent): use canonical assistant history format in subagent_runner (#606)
build_native_assistant_payload in subagent_runner.rs serialised the
assistant tool-call message using {"text": …, "tool_calls": [{
"type":"function","function":{…}}]} — two mismatches vs the format
parse::build_native_assistant_history produces and the backend
jinja template expects:

  1. "text" instead of "content" — the downstream convert_messages
     parser looks for "content" to detect assistant-with-tool-calls
     messages; "text" is not recognised, so the message is treated
     as a plain assistant message.
  2. Nested {"type":"function","function":{"name":…,"arguments":…}}
     wrappers instead of flat {"id":…,"name":…,"arguments":…}.

Result: every sub-agent that made a tool call and entered iteration
1+ hit a 400 from the backend: "jinja template rendering failed.
Message has tool role, but there was no previous assistant message
with a tool call!" — the backend saw a role=tool message without a
recognised assistant-with-tool-calls predecessor.

The fix replaces the broken inline copy with a direct call to
parse::build_native_assistant_history (the same serialiser
tool_loop.rs uses successfully for the orchestrator's tool calls).
The dead build_native_assistant_payload function is removed.

Introduced in PR #474 (6465f3d3, 2026-04-09). Latent since then
because the orchestrator self-heals by retrying via other agents.

E2E verified: skills_agent with gmail toolkit now progresses through
iterations 0→1→2 successfully (previously died at iteration 1 with
the 400 error every time).

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 18:25:03 +05:30
github-actions[bot] fd213d40a2 chore(release): v0.52.16 v0.52.16 2026-04-16 04:35:49 +00:00
c26ca795ca fix: keep chat processing alive across tab switches (#587)
* fix(chat): keep in-flight responses alive across tab switches

Made-with: Cursor

* chore: satisfy lint and format push gates

Made-with: Cursor

* fix: address CodeRabbit review on chat runtime PR

Made-with: Cursor

* feat(chat): add explicit inference turn lifecycle in chat runtime slice

Made-with: Cursor

* feat(chat): implement thinking summary feature in chat responses

Added a new mechanism to accumulate and send model reasoning text as a separate message during chat interactions. This includes handling "thinking_delta" events to gather reasoning content, formatting it for clarity, and ensuring it is sent before the main response. Updated the StreamingState struct to include a thinking accumulator for this purpose. This enhancement improves user experience by providing insight into the model's reasoning process.

* feat(telegram): update bot username handling for staging and production environments

Refactored the Telegram bot username resolution logic to differentiate between staging and production environments. Introduced constants for default usernames based on the application environment and updated the GitHub Actions workflow to set the appropriate environment variables. This change enhances the flexibility and clarity of bot username management in the application.

* fix: resolve CodeRabbit review issues on feat/thinking-telegram-summary

- bus.rs: fix UTF-8 char boundary panic in format_thinking_summary truncation
- threadSlice.ts: remove premature activeThreadId clear from addInferenceResponse.rejected
- Conversations.tsx: add composerBlocked global lock, clear tool timeline in safety timeout
- ChatRuntimeProvider.tsx: replace stale toolTimelineRef/inferenceStatusRef reads with live store.getState() calls in all event handlers
- LocalAIDownloadSnackbar.tsx: reset dismissed/collapsed on not-downloading → downloading transition edge
- MemoryGraphMap.tsx: derive activeSelectedNode to guard stale selectedNode after relations refresh; add debug logs to useMemo graph recompute

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: use render-phase update for download dismiss reset in LocalAIDownloadSnackbar

Replace effect-based setState with the React render-phase update pattern so
the not-downloading → downloading transition resets dismissed/collapsed without
triggering the react-hooks/set-state-in-effect lint warning.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: apply prettier and cargo fmt auto-formatting

Formatting changes applied by the pre-push hook during the previous commit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: replace endInferenceTurn with clearRuntimeForThread in Conversations component

Updated the Conversations component to replace the endInferenceTurn dispatch with clearRuntimeForThread. This change simplifies the handling of thread runtime state during error scenarios and timeout conditions, ensuring a cleaner state management approach.

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 06:27:48 +05:30
CodeGhost21andGitHub a8664f066f test(coverage): batch 1–4 — Rust unit tests toward 80% for critical modules (#530) (#581)
* test(coverage): phase 1 — webhooks/types, workspace/ops, webhooks/schemas

Lines: webhooks/types 0% → 100%; workspace/ops 0% → 96.98%;
webhooks/schemas 35.40% → 79.18%.

- webhooks/types.rs: serde round-trip tests for WebhookRequest /
  WebhookResponseData / TunnelRegistration (including the
  default_webhook_target_kind fallback) / WebhookActivityEntry /
  WebhookDebugLogEntry / the three debug result wrappers / and
  WebhookDebugEvent, exercising every `#[serde(default)]` and
  camel-case rename.
- workspace/ops.rs: ensure_workspace_file covers create / leave / force
  / write-error branches; BOOTSTRAP_FILES contract test locks in SOUL
  and IDENTITY presence; init_workspace covers fresh-install, idempotent
  second-call, and forced-overwrite paths against a temp OPENHUMAN_WORKSPACE
  (serialised via the shared TEST_ENV_LOCK).
- webhooks/schemas.rs: catalog integrity (length / namespace /
  uniqueness / schema↔handler parity), per-function required-field
  assertions for all 11 RPC methods plus the unknown fallback, and
  deserialize_params / json_output / to_json coverage.

Also fixes a pre-existing test bug in composio/ops.rs discovered while
running llvm-cov: fetch_connected_integrations_via_mock_aggregates_tools
was not mocking /composio/toolkits, so list_toolkits failed and the
expected aggregation never happened. Adds the missing route so the
test now observes the 2-integration result it asserts.

* test(coverage): phase 2 — webhooks/bus, webhooks/ops

Lines: webhooks/bus 0% → 100%; webhooks/ops 14.34% → 97.96%.

- webhooks/bus.rs: base64_encode / error_body helpers; Default vs new
  equivalence; EventHandler name and domain filter; handle() on a
  non-webhook variant (early return) and on a WebhookIncomingRequest
  without a registered socket manager (graceful fallthrough).
- webhooks/ops.rs: require_token for missing / whitespace / valid
  stored sessions; the four stub RPC ops (list_registrations, list_logs
  including ignored-limit, clear_logs, register/unregister_echo) lock
  in their current payload + log shape; build_echo_response decodes
  back to the expected echo body and sets the echo-target header;
  trimmed-input validation for create_tunnel (empty/whitespace name)
  and the id-bearing ops (get/update/delete); and full mock-backend
  round-trips for list_tunnels, create_tunnel (trim + drop-whitespace
  description), get_tunnel, update_tunnel, delete_tunnel, and
  get_bandwidth, plus a guard asserting authed HTTP calls fail fast
  without a session token.

* test(coverage): phase 3 — voice/postprocess, voice/text_input

Lines: voice/postprocess 58.94% → 92.06%; voice/text_input 18.83% → 51.18%.

voice/postprocess.rs
- Convert existing short-circuit tests to #[tokio::test] so the async
  function is awaited directly instead of via a freshly-constructed
  runtime per case.
- Add `enabled_but_llm_not_ready_returns_raw_text` for the "cleanup is
  on but the local LLM hasn't reached ready/degraded yet" branch.
- Add five LLM-ready tests that spin up a mock Ollama behind the
  OPENHUMAN_OLLAMA_BASE_URL override: happy-path cleanup + trim,
  whitespace-only response fallback, HTTP 500 fallback, conversation
  context embedded in the prompt, and whitespace-only context
  ignored. Assertions are permissive about "LLM called → cleaned" vs
  "LLM short-circuited → raw" because ~30 sibling tests touch the
  shared `local_ai::global()` singleton without LOCAL_AI_TEST_MUTEX
  and can race our `state = "ready"` setup. Either branch is
  documented as acceptable; the function must always return a
  deterministic String and never panic. Full end-to-end correctness
  of the cleanup output is pinned by the deterministic short-circuit
  tests above.

voice/text_input.rs
- Add `\\t` / `\\n`-only input case and verify the OpenWhispr timing
  constants (PASTE_DELAY 120ms, CLIPBOARD_RESTORE_DELAY 450ms) so
  nobody silently shortens them and breaks paste reliability.
- macOS: escape_applescript_string backslash/quote/idempotence cases
  and restore_focus_to_app error path against a bogus app name.
- Ceiling note: the clipboard/enigo body of insert_text is not
  testable in a headless environment (Clipboard::new / Enigo::new
  fail without a display). Pushing past ~51% here requires
  dependency-injecting those traits — a production refactor, not a
  test addition.

* test(coverage): batch 4.1 — skills/bus, text_input/{types,ops}

Lines: skills/bus 0% → 100%; text_input/types 0% → 100%;
text_input/ops 0% → 57.67% (accessibility-gated ceiling).

- skills/bus.rs: idempotent no-op for the legacy
  register_skill_cleanup_subscriber() hook.
- text_input/types.rs: FieldBounds ↔ accessibility::ElementBounds
  round-trip, ReadFieldParams default/omitted-key serde, and
  round-trips for every request/result struct (InsertText,
  ShowGhostText, DismissGhostText, AcceptGhostText).
- text_input/ops.rs: empty-text guard assertions for insert_text,
  show_ghost, and accept_ghost; dismiss_ghost idempotent-success
  contract; plus a deterministic-shape check that the accessibility
  failure path wraps the error into InsertTextResult rather than
  panicking. Anything past the guard reaches `accessibility::*`,
  which requires a live focused field on an OS display and is
  therefore not reachable in a headless unit-test environment —
  lifting the ceiling past ~58% requires dependency-injecting the
  accessibility surface, a production refactor.

* test(coverage): batch 4.2 — service/bus, migration/ops, referral/ops

Lines: service/bus 0% → 85.25%; migration/ops 0% → 89.71%;
referral/ops 0% → 94.63%.

- service/bus.rs: RestartSubscriber name and domain metadata, plus
  two handle() branches that are safe to exercise — non-restart
  event (early return) and duplicate-suppression (gate already set).
  Deliberately does NOT cover the success path of a real
  SystemRestartRequested — it spawns a tokio task that calls
  std::process::exit(0) and would terminate the test runner.
  register_restart_subscriber idempotency test confirms the
  OnceLock guard skips re-registration.
- migration/ops.rs: dry-run against an empty temp workspace returns
  the canonical "migration completed" log; missing source-workspace
  path exercises the Err-propagation branch.
- referral/ops.rs: require_token for missing / whitespace / valid
  sessions; get_stats + claim_referral guard fast-fail without a
  session; claim_referral round-trip against a mock backend
  asserting (a) the referral code is trimmed, (b) a whitespace-only
  deviceFingerprint is dropped from the outgoing body, and (c) a
  non-empty fingerprint is trimmed and forwarded.

* test(coverage): batch 4.3 — security/ops, service/{bus,ops}, update/{ops,scheduler}

Adds unit tests covering:
- security/ops: security_policy_info payload shape + default values
- service/ops: daemon_host_get/set happy path and error branches
- service/bus: fix register_restart_subscriber test to use #[tokio::test]
  so it has a runtime under `cargo llvm-cov`
- update/ops: validate_asset_name and validate_download_url edge cases,
  update_apply short-circuit guards
- update/scheduler: min-interval invariant, disabled-run short-circuit,
  tick resilience when the event bus is uninitialised

All 3583 lib tests pass under `cargo llvm-cov`. Refs #530.

* style: cargo fmt cleanup in coverage test modules

Auto-fixes from `cargo fmt` on test code added in batches 4.1–4.3.
No behavioral changes.

* test(coverage): batch 5.1 — cron/{ops,schemas}

- cron/ops.rs: 74.73% → 91.20% (+add_once_at, pause/resume, async cron_list/update/remove/runs, enabled/disabled and empty-id branches)
- cron/schemas.rs: 29.11% → 77.00% (all schemas() branches, registry helpers, read_required/read_optional_u64/type_name)

30 new deterministic tests. Refs #530.

* test(coverage): batch 5.2 — memory/{rpc_models,schemas}

- memory/rpc_models.rs: 70.91% (targets resolved_limit priority across QueryNamespace/RecallContext/RecallMemories, deny_unknown_fields enforcement, ApiError/ApiMeta/ApiEnvelope round-trips)
- memory/schemas.rs: 45.67% (all 31 controller schemas present, registry parity with handlers, parse_params success + error paths, unknown function placeholder)

19 new deterministic tests. Refs #530.

* test(coverage): batch 5.3 — socket/manager webhook router paths

- socket/manager.rs: 67.54% (adds set_webhook_router populates/overwrites paths and emit-after-disconnect guard)

3 new deterministic tests. Refs #530.

* test(coverage): batch 5.4 — config/{schemas, schema/observability}

- config/schemas.rs: 52.37% (adds required_string / optional_string / optional_bool field builder coverage, exercises deserialize_params across ModelSettings / MemorySettings / WorkspaceOnboarding / SetBrowserAllowAll / OnboardingCompleted params, pins DEFAULT_ONBOARDING_FLAG_NAME constant)
- config/schema/observability.rs: 66.67% (default-values invariant, serde defaults for optional fields, explicit analytics flag, round-trip)

16 new deterministic tests. Refs #530.

* test(coverage): batch 5.5 — local_ai/{core,gif_decision}

- local_ai/core.rs: 33.33% (pins model_artifact_path structure: models/local-ai dir, `.ollama` suffix, colon→dash normalisation for Windows-safe filenames; Arc sharing across global() calls)
- local_ai/gif_decision.rs: 44.04% (trim whitespace in parse, length/word-count boundary cases, tenor_search empty-query guard, local_ai_should_send_gif empty-message early return)

10 new deterministic tests. Refs #530.

* test(coverage): batch 5.6 — local_ai/sentiment, migration/schemas, referral/schemas

- local_ai/sentiment.rs: 68.03% (negative-confidence clamp to zero, unknown valence fallback, all documented emotion/valence labels accepted, neutral() constructor invariants, empty-message early return)
- migration/schemas.rs: 36.73% (controller registry parity, openclaw input shape, MigrateOpenClawParams defaults + round-trip, unknown function placeholder, to_json wrapping)
- referral/schemas.rs: 37.18% (controller registry parity, claim input required/optional mapping, ReferralClaimParams camelCase alias + missing-code rejection, json_output + to_json helpers)

25 new deterministic tests. Refs #530.

* test(coverage): batch 5.7 — tools/traits, local_ai/device

- tools/traits.rs: 76.77% (Tool default-method values for permission/scope/category, PermissionLevel total order + default + Display + serde round-trip, ToolCategory default + Display + snake_case serde, ToolScope variant distinctness)
- local_ai/device.rs: 63.16% (total_ram_gb truncation for sub-GB and partial-GB, detect_gpu branches for Apple-brand / ARM-on-mac / Intel-Mac, DeviceProfile serde round-trip)

17 new deterministic tests. Refs #530.

* test(coverage): address PR review — tighten assertions, fix flaky test, add env-var RAII guard

Inline review fixes:
- migration/ops.rs: migrate_openclaw_returns_error_for_missing_source_workspace now requires Err() (the underlying helper bails when the source dir doesn't exist) and asserts a non-empty error message.
- text_input/ops.rs: replace tautological `!inserted || inserted` in insert_text_surfaces_accessibility_failure_as_inserted_false with the real contract: require Ok(..) and pin `inserted`↔`error` mutual exclusion. Headless runs legitimately see inserted=false, so the assertion still holds while catching regressions in either branch.
- update/scheduler.rs: remove tick_runs_without_panicking_when_event_bus_is_uninitialised — it hit real api.github.com HTTPS and was flaky under offline CI / rate limits. Replace with a comment documenting the decision and the integration-test path for exercising tick().

Nitpick fixes:
- security/ops.rs: extend security_policy_info_matches_default_policy_values with assertions for autonomy and allowed_commands so the full default shape is pinned.
- voice/postprocess.rs: tighten ready_llm_with_whitespace_only_context_never_embeds_header from `assert_eq!(result.trim(), "raw text")` to exact equality (cleanup_transcription trims internally). The pre-existing doc comment on with_ready_llm plus the in-module block comment at lines 255-268 already document the LOCAL_AI_TEST_MUTEX contract and the deliberate non-use in permissive tests — no new docs needed.
- webhooks/ops.rs: list_tunnels_hits_webhooks_core_endpoint_and_returns_payload now asserts the inbound Authorization header equals `Bearer test-session-token`; get_tunnel_encodes_id_in_path uses an id full of reserved URL chars so the test actually verifies percent-encoding rather than just trimming.
- workspace/ops.rs: introduce a WorkspaceEnvGuard RAII helper; replace all six unsafe set_var/remove_var pairs in the init_workspace tests so OPENHUMAN_WORKSPACE is cleared on panic too. Contract is documented inline: guard requires the caller to hold ENV_LOCK.

Refs #530.

* test(coverage): tighten sentry_dsn round-trip assertion to exact value

round_trip_preserves_all_fields previously only checked
`back.sentry_dsn.is_some()`, which would still pass if serde dropped or
corrupted the DSN string. Compare the full decoded value instead so any
regression in the Option<String> path is caught.

Refs #530.
2026-04-15 16:31:51 -07:00
19aa50a429 fix(agent): thread agent_id into build_session_agent_inner (#584)
build_session_agent_inner took an agent_id: &str parameter but
never threaded it into the Agent::builder() chain — a `let _ =
agent_id;` silenced the unused-variable warning. AgentBuilder::build()
fell back to the legacy "main" default at builder.rs:310-312, so
every session built via Agent::from_config_for_agent carried
agent_definition_name="main" regardless of the id the caller asked
for.

Only two ids reach this path in production: "orchestrator" (legacy
Agent::from_config wrapper, cron, local_ai, escalation) and
"welcome" (channels/providers/web.rs routing after #525/#526, and
welcome_proactive). Orchestrator is benign — "main" was already
an alias for orchestrator everywhere downstream (see debug_dump.rs:249).
Welcome is the visible bug — agent_definition_name feeds three
surfaces that are all silently wrong pre-fix:

  1. Transcript filename — welcome sessions land as main_*.md
     instead of welcome_*.md.
  2. Transcript metadata — the <!-- session_transcript --> block
     stamps `agent: main` instead of `agent: welcome`.
  3. Transcript resume cross-contamination —
     try_load_session_transcript calls
     find_latest_transcript(workspace_dir, "main") which scans all
     dated session dirs for the latest main_*.md. It finds the
     last orchestrator session and resumes from it, loading its
     system prompt + user messages + assistant tool-call history
     into the welcome agent's `history` as a resume prefix before
     run_single runs. The written transcript mixes yesterday's
     orchestrator email thread with today's welcome message under
     the same main_0.md filename. Reproduced live 2026-04-16: a
     welcome_proactive session loaded 5 messages from yesterday's
     15042026/main_0.md and wrote 6 messages back that included a
     spawn_subagent(skills_agent, toolkit=gmail) tool-call the
     welcome agent never made.

Prompt rendering is NOT affected. context/prompt.rs only reads
ctx.agent_id for the is_skill_executor branch at prompt.rs:655, so
welcome/main/orchestrator all fall through the same delegator path.
The welcome persona prompt is rendered by the stamped
SystemPromptBuilder::for_subagent(target_def.system_prompt) built
at session-build time, independent of agent_definition_name. The
pre-fix main_0.md on disk contains `# Welcome Agent\n\nYou are the
Welcome agent...` as its system prompt body — correct content,
wrong filename and metadata.

skills_agent and other typed sub-agents are unaffected — they go
through subagent_runner, which constructs its prompt directly and
writes transcripts under the explicit id it receives. The
pre-existing sessions/15042026/skills_agent_0.md on disk with
`agent: skills_agent` confirms this code path has always been
correct.

Changes:

  * src/openhuman/agent/harness/session/builder.rs:
    - Add .agent_definition_name(agent_id.to_string()) to the
      builder chain in build_session_agent_inner.
    - Delete the `let _ = agent_id;` suppression.
    - Replace the misleading 8-line comment block at the call site
      (which claimed event_channel was for transport identity and
      therefore stamping agent_id wasn't needed — conflating two
      unrelated fields) with an accurate description of the three
      load-bearing surfaces.
    - Expand the docstring on AgentBuilder::agent_definition_name
      to document the surfaces and the latent prompt-section
      foot-gun the fix closes for future code.
    - New log::debug! call at the stamping site for grep-friendly
      runtime traces ([agent::builder] stamping
      agent_definition_name=<id> onto session agent).

  * src/openhuman/agent/harness/session/runtime.rs:
    - Add pub fn agent_definition_name(&self) -> &str accessor
      on Agent so tests and runtime callers can introspect the
      stamped id without reaching into the pub(super) field.

  * src/openhuman/agent/harness/session/tests.rs:
    - Add build_minimal_agent_with_definition_name helper.
    - agent_builder_threads_agent_definition_name_when_set —
      parameterised over welcome/skills_agent/orchestrator/
      trigger_triage, asserts the setter threads the id through.
    - agent_builder_falls_back_to_main_when_definition_name_unset
      — pins the legacy fallback contract direct builder users rely
      on.

Tests: 2 passed; 0 failed; 3477 filtered out; finished in 0.21s.
cargo check --lib clean; cargo fmt clean.

Verified live against G:/projects/vezures/.openhuman on 2026-04-16:
  - Pre-fix welcome_proactive run wrote sessions/16042026/main_0.md
    with `agent: main` in the metadata header.
  - Post-fix welcome_proactive run wrote sessions/16042026/welcome_0.md
    with `agent: welcome` in the metadata header.
  - Post-fix web-chat dispatch to orchestrator wrote
    sessions/16042026/orchestrator_0.md (moved off the historical
    main_*.md alias — behaviorally unchanged for orchestrator).
  - The new [agent::builder] stamping debug line fires on both
    welcome and orchestrator paths.

Does not touch: subagent_runner, spawn_subagent / dispatch_subagent,
any agent/agents/*/agent.toml, any context::prompt code, or the
AgentBuilder::build() fallback itself.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 16:31:36 -07:00
d4f5b9a357 fix(overlay): fullscreen visibility, voice server reliability, and resize (#528) (#585)
* fix(overlay): shrink initial overlay window to match idle orb dimensions (#528)

The Tauri overlay window was 248×228 px while the idle orb renders at
50×50. The excess transparent area wasted compositing resources and
created an invisible click-absorbing region. Reduce to 60×60 to tightly
frame the idle orb with minimal padding.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(overlay): add native drag support with position persistence (#528)

- Mouse-down on the orb initiates Tauri startDragging() for native
  window drag
- Dragged position is saved to localStorage and survives mode changes
  (idle ↔ active) so the orb stays where the user placed it
- Double-click resets to the default bottom-right corner
- Cursor changes to grab/grabbing for affordance
- Skip default repositioning when a saved position exists

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(overlay): reclass NSWindow to NSPanel for fullscreen visibility (#528)

macOS fullscreen apps run in separate Spaces where standard NSWindow
cannot follow. Use object_setClass() to reclass the Tauri overlay
window from NSWindow to NSPanel at runtime, then configure it with
NonactivatingPanel style mask and Transient collection behavior —
matching the working Swift accessibility helper pattern.

Key configuration that makes this work:
- object_setClass(NSWindow → NSPanel) — in-place reclass, no reparenting
- NSWindowStyleMask::NonactivatingPanel — critical for panel behavior
- NSWindowCollectionBehavior::Transient (not Stationary) — follows Spaces
- Window level 25 (NSStatusWindowLevel) — floats above fullscreen apps
- setFloatingPanel(true), setHidesOnDeactivate(false)

Previous approaches that failed:
1. CGShieldingWindowLevel + CanJoinAllSpaces — hidden (NSWindow limitation)
2. Window level i32::MAX-17 + Stationary — hidden (Space membership issue)
3. CGS private API CGSSetWindowTags sticky bit — blocked on Sonoma
4. object_setClass WITHOUT NonactivatingPanel mask — hidden
5. Create new NSPanel + reparent webview — CRASH (Tao delegate panic)

Also removes unused objc2-core-graphics and objc2-foundation deps.

Ref: https://github.com/tauri-apps/tauri/issues/11488

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(sidecar): make dev signing non-fatal in stage script

codesign failures no longer call process.exit(), preventing
yarn tauri dev from hanging when the dev signing identity is
missing or the keychain rejects the request.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(voice): prevent race condition and fix restart after stop

- Atomically transition Stopped → Idle at start of run() to prevent
  duplicate run() calls during slow globe listener compilation
- Wrap CancellationToken in Mutex so run() creates a fresh token on
  each start — a cancelled token cannot be reused after stop()
- Reset state to Stopped if hotkey listener fails to start

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(voice): capture server errors from spawned run() task

Store errors from the background server.run() task via
set_last_error() so they surface in voice_server_status RPC
responses instead of being silently lost.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(overlay): enable programmatic resize and shrink idle dimensions

- Change overlay window from 60x60 to 50x50 to match idle orb size
- Remove minWidth/minHeight constraints that blocked dynamic resize
- Set resizable: true so setSize() calls work for bubble expansion

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(overlay): simplify window resize and bubble rendering

- Clear min/max constraints before resizing to avoid clamping
- Replace CSS transition-based bubble visibility with conditional
  mount for more reliable rendering when mode changes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: fix fmt and remove unused bubbles variable

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(voice): enforce lock ordering to prevent race between run() and stop()

Acquire cancel lock before state lock in run() — same order as stop() —
so stop() cannot cancel a stale token between setting Idle and swapping.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(overlay): restore saved position on resize and format with prettier

Parse and apply saved drag coordinates instead of just using their
presence as a sentinel. Also reformats for prettier compliance.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(sidecar): fail-fast on dev signing failure in CI environments

Add CI detection so signing failures abort the build in CI but remain
non-fatal for local development.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: fix cargo fmt on Tauri shell import

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 04:52:14 +05:30
f014058417 feat(local_ai): MVP model lockdown — lock selection to 2-4 GB tier (#573) (#588)
* feat(local_ai): add MVP tier ceiling and cap model recommendation (#573)

Introduce MVP_MAX_TIER constant (Ram2To4Gb), is_mvp_allowed() gate,
mvp_presets() filter, and cap recommend_tier() so auto-provisioning
never selects a model above the MVP ceiling regardless of device RAM.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(local_ai): enforce MVP model allowlists on resolved IDs (#573)

Add per-category allowlists (chat, vision, embedding) so that
effective_*_model_id() silently redirects any non-MVP model to the
default. Prevents config-file edits from bypassing the 2-4 GB tier
restriction.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(local_ai): reject non-MVP tiers in RPC and clamp at bootstrap (#573)

apply_preset handler now returns an error for tiers above the MVP
ceiling. Bootstrap clamps any existing out-of-range tier selection
down to the recommended (capped) preset on startup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(ui): lock model tier selection and show full roadmap (#573)

Replace clickable tier buttons with static cards. Active tier shows
"Active" badge; locked tiers show "Coming soon" with reduced opacity.
Add MVP info banner. Fix download size to 1 decimal place.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(local_ai): resolve CI failures — fmt, unused props, dead code (#573)

Apply cargo fmt to single-element array constants. Remove unused
isApplyingPreset/onApplyPreset props and applyPreset function from
the settings panel since tier switching is disabled for MVP.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style: apply Prettier formatting to settings panels (#573)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 04:50:28 +05:30
github-actions[bot] 098206cea7 chore(release): v0.52.15 v0.52.15 2026-04-15 20:45:19 +00:00
github-actions[bot] a2aee3a1f1 chore(release): v0.52.14 2026-04-15 19:42:53 +00:00
YellowSnnowmannandGitHub cca6c08ab0 Fix: discord (#580)
* feat(discord): implement managed link functionality and enhance DiscordConfig component

- Added support for managed Discord linking, including new API methods for initiating and checking link status.
- Enhanced the DiscordConfig component to manage link tokens and implement polling for link completion.
- Introduced new state management for link tokens and improved error handling during connection processes.
- Updated the definitions to include a new auth action for managed links, streamlining the onboarding experience for users.
- Added tests for the new Discord link functionality to ensure robust integration and error handling.

This commit significantly improves the user experience for connecting Discord accounts, providing clearer feedback and handling for managed links.

* feat(discord): implement managed link functionality and enhance DiscordConfig component

- Added support for managed Discord linking, including new API methods for initiating and checking link status.
- Enhanced the DiscordConfig component to manage link tokens and implement polling for link status, improving user experience during the linking process.
- Introduced new state management for link tokens and error handling, ensuring robust interaction with the Discord API.
- Updated the channel definitions to include a new auth action for managed links, streamlining the integration process.
- Added tests for the new API methods and updated existing tests to cover the new functionality.

This commit significantly improves the integration with Discord, allowing users to link their accounts more effectively and providing better feedback during the linking process.

* chore(release): update OpenHuman version to 0.52.9 in Cargo.lock

- Bumped the version of the OpenHuman package from 0.52.7 to 0.52.9 in both Cargo.lock files for the main application and the Tauri app.
- This update ensures that the latest features and fixes from the OpenHuman package are included in the project.

* fix(tests): update DiscordConfig test to reflect new Connect button count

- Adjusted the test for the DiscordConfig component to expect three Connect buttons instead of two, aligning with recent changes in the component's authentication modes.
- This update ensures that the test accurately reflects the current functionality and improves test reliability.

* refactor(discord): modularize channel permission checks and enhance logging

- Extracted the `check_channel_permissions_at_base` function to modularize the permission checking logic for better readability and maintainability.
- Updated the API calls to use a dynamic base URL instead of a hardcoded one, improving flexibility.
- Enhanced logging for non-success responses from Discord API calls, providing more context for debugging.
- Minor adjustment in the hallucination check logic to improve clarity in variable usage.

* fix(logging): enhance error handling in DiscordConfig and SocketProvider

- Updated error handling in DiscordConfig to log specific errors when link checks fail, improving debugging capabilities.
- Enhanced SocketProvider to log non-fatal RPC connection failures, providing clearer context for potential issues with the sidecar or backend connectivity.

* feat(discord): add validation functions for Discord link start and complete responses

- Introduced `expectDiscordLinkStart` and `expectDiscordLinkComplete` functions to validate the structure of responses from Discord link API calls.
- Updated `channelConnectionsApi` methods to utilize these new validation functions, improving error handling and response consistency.
- Added tests to ensure proper unwrapping and validation of Discord link responses, enhancing overall reliability of the API integration.

* fix(channelConnections): improve handling of lastError and capabilities in touchConnection function

- Updated the touchConnection function to conditionally assign lastError and capabilities based on their presence in the patch object, ensuring more accurate state management.
- This change enhances the reliability of connection updates by preventing unintended overwrites of existing values.

* test(channelConnections): add test to clear lastError when explicitly set to undefined

- Introduced a new test case to verify that the lastError state is cleared when an explicit patch sets it to undefined. This enhances the reliability of the state management in the channelConnectionsSlice reducer.

* test(tests): add tests for upstream_unhealthy detection and failure reason precedence

- Introduced multiple test cases to verify the detection of upstream unavailability and service unavailability errors.
- Ensured that the `failure_reason` function correctly prioritizes upstream_unhealthy over other error states, enhancing the reliability of error handling in the system.

* feat(discord): add OAuth success handling in DiscordConfig component

- Implemented a new useEffect to handle successful OAuth events for Discord, updating the channel connection status and capabilities accordingly.
- This enhancement improves the integration with Discord by ensuring that the application responds correctly to OAuth success events, providing a better user experience.

* feat(deepLink): enhance OAuth deep link handling to include skillId

- Updated the deep link handling logic to also retrieve the skillId from the URL parameters, improving flexibility in OAuth success scenarios.
- Added a new simulation example for skillId in the setupDesktopDeepLinkListener function to aid in testing and development.

* refactor(format): ran format

* fix(discord): update permissions mock and improve message dispatch test

- Revised the permissions mock to include additional endpoints for better accuracy in testing Discord API interactions.
- Adjusted the message dispatch test to utilize a deterministic stub for the agent's response, ensuring consistent timing and behavior during tests.
- Reduced the delay in the SlowProvider to enhance test performance while maintaining robustness.

* chore(dependencies): update OpenHuman package version to 0.52.13 in Cargo.lock
2026-04-15 22:59:28 +05:30
d545c193b9 feat(agent): fuzzy-filter skills_agent toolkit actions by task prompt (#579)
* feat(agent): fuzzy-filter skills_agent toolkit actions by task prompt

Narrow large Composio toolkits (e.g. github ~500 actions) down to the
handful relevant to a given delegation prompt before registering them
as native tools on a spawned skills_agent. Falls back to the full
catalogue when the filter yields fewer than MIN_CONFIDENT_HITS hits to
avoid starving the sub-agent on under-specified prompts.

Filter is only invoked when both `definition.id == "skills_agent"` and
a `toolkit=` argument is present, so orchestrator and other sub-agents
are unaffected.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test(composio): mock toolkits route in ops integration test

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 21:06:59 +05:30
github-actions[bot] d575cf2cca chore(release): v0.52.13 2026-04-15 14:33:51 +00:00
github-actions[bot] 70a2a6fcc3 chore(release): v0.52.12 v0.52.12 2026-04-15 06:30:33 +00:00
github-actions[bot] f179d2d3f9 chore(release): v0.52.11 2026-04-15 04:35:59 +00:00
github-actions[bot] fc103bf89f chore(release): v0.52.10 2026-04-15 03:51:46 +00:00
ea088d9f15 feat(prompt): unified delegation guide + dynamic per-toolkit tool registration (#447) (#570)
* feat(prompt): unified delegation guide + dynamic per-toolkit tool registration (#447)

Cuts main agent prompt token cost ~98% on accounts with connected
integrations and replaces the generic Composio dispatcher with native
per-action tool calling inside skills_agent.

## Why

Issue #447: prompt payloads were ballooning because main/orchestrator
re-shipped a full prose enumeration of every connected toolkit's
actions on every turn (~13.7k tokens for one gmail integration alone),
and skills_agent had no way to scope tools to a single toolkit so it
inherited a flat dispatcher (composio_execute) that the LLM couldn't
reliably call without first round-tripping composio_list_tools.

## What

### main / orchestrator prompt
- Replace `ConnectedIntegrationsSection`'s per-action prose dump with
  a unified Delegation Guide: one bullet per integration (toolkit +
  one-line description + spawn snippet). Tokens added per
  integration: ~30. Savings vs old per-action listing: ~5k+ tokens
  per connected toolkit.
- Drop every "Composio" reference from delegating-agent prose so the
  surface stays provider-neutral.

### skills_agent
- Add a `toolkit` argument to `spawn_subagent` with three pre-flight
  validation modes resolved against the parent's connected
  integrations list before any LLM call:
    * missing toolkit         -> ToolResult::error (model retries)
    * unknown toolkit         -> ToolResult::error (model retries)
    * in allowlist, not connected -> ToolResult::success (model
      surfaces "authorize in Settings -> Integrations" to user; not
      flagged as a tool failure in the agent loop or web channel)
- New `ComposioActionTool` (`src/openhuman/composio/action_tool.rs`)
  implements the `Tool` trait per Composio action. The sub-agent
  runner constructs ~N of these at spawn time from the cached
  integration overview and injects them via a new `extra_tools`
  parameter through `run_typed_mode` -> `run_inner_loop`.
- `filter_tool_indices` drops every skill-category parent tool when
  `is_skills_agent_with_toolkit` is true so the only skill-category
  entries the sub-agent sees are the freshly-built per-action tools.
  This eliminates apify_*, composio_list_*, composio_authorize, and
  composio_execute from the toolkit-scoped surface.
- Sub-agent renderer takes the parent's actual `tool_call_format`
  instead of hardcoding PFormat. Native dispatchers no longer carry a
  prose `## Tools` section at all (schemas already travel through the
  request body's `tools` field) — eliminates a ~30k-token duplication
  that was blowing past the model's context window.

### integration overview fetch
- `fetch_connected_integrations_uncached` now merges Composio's
  toolkit allowlist with the user's active connections and returns
  one `ConnectedIntegration` per allowlisted toolkit with a
  `connected: bool` flag. Unconnected entries carry no schemas, just
  the toolkit name + description, so the orchestrator can mention
  them without trying to invoke them.
- `ConnectedIntegrationTool` preserves the action's full JSON
  parameter schema so `ComposioActionTool` can advertise it through
  native function-calling.

### plumbing
- `ParentExecutionContext` carries a `ComposioClient` and the
  parent's resolved `tool_call_format`, populated alongside
  `connected_integrations` in `Session::fetch_connected_integrations`.
  Triage and test paths pass `None` / `PFormat` defaults.
- `dispatch_subagent` (the `SkillDelegationTool` path) plumbs its
  pre-bound skill_id through `toolkit_override` instead of the
  broken `skill_filter_override` (which used `{skill}__` prefix
  matching that never matched Composio's `TOOLKIT_*` naming).
- `web::run_chat_task` failures now log the underlying error at WARN
  so debugging an in-flight failure no longer requires turning on
  TRACE for socket events.
- `scripts/stage-core-sidecar.mjs` queries `cargo metadata` for the
  real target directory instead of assuming `<repo>/target` so the
  staging step works under workspace-level `target-dir` overrides
  (the vezures-workspace shared `.cargo-target` setup).

## Verified end-to-end

Two RPC tests against a freshly-rebuilt sidecar (gmail connected,
notion / slack / etc. allowlisted but not connected):

| Test | Behavior | Tokens | Iterations |
|---|---|---|---|
| Connected (gmail, "fetch 5 unread emails") | spawn_subagent -> 62 dynamic gmail tools registered -> 1 GMAIL_FETCH_EMAILS call with smart args -> markdown table response | 42,911 input | 1 |
| Not-connected (notion, "create a page") | main answers directly without spawning, tells user to authorize in Settings -> Integrations | 3,319 input | 1 |

Both flows complete cleanly. The connected path proves dynamic
per-action tool registration is working with full schema validation;
the unconnected path proves the unified delegation guide + ToolResult::success
return for not-connected toolkits keeps the model on a graceful path
without polluting the chat with error styling.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test(prompt): update test callers + assertions for new renderer signature

Follow-up to #447. The main patch changed three signatures that test
callers hadn't been updated for, and flipped one assertion that was
validating the now-removed prose schema duplication.

- `SubagentRunOptions` test constructors in subagent_runner.rs (2
  sites) now pass `toolkit_override: None`.
- `ConnectedIntegration` test constructor in orchestrator_tools.rs
  now passes `connected: true` (the default for test integrations —
  they're treated as authorized so delegation logic still runs).
- 12 `render_subagent_system_prompt` test callers in prompt.rs now
  pass `&[]` for the new `extra_tools` slice and
  `ToolCallFormat::PFormat` for the new `tool_call_format` argument.
- `render_subagent_system_prompt_honors_identity_safety_and_skills_flags`
  used to assert `rendered.contains("Parameters:")` on the Json
  dispatcher branch — that was valid in the old world where the
  prose `## Tools` section dumped full JSON schemas for Json/Native
  formats. The main patch deliberately removes that dump (it was the
  ~30k-token duplication of the native `tools` field), so the test
  now asserts the opposite: no `## Tools` header and no `Parameters:`
  line are emitted for Native/Json dispatchers. The schemas still
  travel through the provider request's `tools` field.

Also picks up `rustfmt` rewraps in action_tool.rs and ops.rs from a
background linter run — pure whitespace, no semantic change.

Verified green against the full `cargo test --lib` suite for every
test touched by this PR:
- openhuman::context::prompt::tests                         (26 passed)
- openhuman::agent::harness::subagent_runner::tests         (19 passed)
- openhuman::composio::ops::tests                           (2 passed)
- openhuman::tools::impl::agent::tests                      (0 scoped)

The 7 remaining failures in `cargo test --lib` are pre-existing
Windows-path/filesystem flakes in subsystems this PR doesn't touch
(self_healing polyfill path separator, cron scheduler shell spawning,
local_ai::paths absolute-path detection, security::policy sandbox
path handling, composio::trigger_history jsonl archive, and a real
pre-existing `Option::unwrap()` panic in browser::screenshot).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test(harness): add composio_client + tool_call_format to integration test stub

Follow-up to #447. The `tests/agent_harness_public.rs` integration
test constructs a `ParentExecutionContext` in `stub_parent_context`
that was missing the two fields added in the main patch
(`composio_client` and `tool_call_format`). `cargo test --lib`
doesn't compile integration tests, which is why this slipped past
local verification — it only surfaced on Linux CI.

- `composio_client: None` — the stub parent has no composio client
  because these tests don't exercise the integration-overview path.
- `tool_call_format: ToolCallFormat::PFormat` — default legacy
  format; none of the tests in this file exercise the sub-agent
  renderer's format branching, so PFormat is the safe pick.

Verified locally that `cargo test --no-run` compiles all integration
test targets including `agent_harness_public`. (The
`agent_memory_loader_public` link error in the local run is a
pre-existing Windows `libucrt`/`fgets` C-runtime issue unrelated to
this PR — won't reproduce on Linux CI.)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(prompt,composio): address CodeRabbit review on #570 (#447)

Four fixes from the CodeRabbit review of PR #570, ranked by impact:

## `context/prompt.rs` — Json dispatcher needs prose tool catalog

The initial patch gated the prose `## Tools` section behind
`matches!(tool_call_format, ToolCallFormat::PFormat)`, which
conflated Json with Native. Only `ToolCallFormat::Native` uses the
provider's native function-calling channel where schemas travel via
the request body's `tools` field. `ToolCallFormat::Json` is a
prompt-driven format (the model wraps JSON tool calls in
`<tool_call>` tags) and relies on the prose catalog the same way
PFormat does — without it the model sees protocol instructions but
no visible tool names or schemas.

Inverted the guard to `!matches!(tool_call_format, Native)` so
PFormat and Json both render the `## Tools` section with their
respective per-entry shapes (compact `Call as:` signature for
PFormat, inline `Parameters:` JSON schema for Json). Updated the
`render_subagent_system_prompt_honors_identity_safety_and_skills_flags`
test: the Json assertion now expects `Parameters:` to be present
again (reverting commit 2's flip), and a new Native-branch assertion
guards against the ~54k-token schema duplication the original PR
fixed.

## `composio/ops.rs` — don't cache degraded snapshots

All three backend calls in `fetch_connected_integrations_uncached`
(`list_toolkits`, `list_connections`, `list_tools`) previously
returned `Some(Vec::new())` or fell through to an empty inner vec
on transient errors. The outer `fetch_connected_integrations`
caches whatever the uncached path returns, so a single transient
5xx would silently hide every integration, mark connected toolkits
as disconnected, or register dynamic Composio tools with zero
callable actions — until the cache is invalidated or the process
restarts. Changed all three branches to return `None`, which
signals the caller to NOT cache the result and retry on the next
call.

## `composio/ops.rs` — prefix match needs a delimiter

`starts_with(&slug.to_uppercase())` false-matches when two toolkit
slugs share a text prefix (e.g. `git` vs `github`). The current
allowlist doesn't trigger this, but adding a new toolkit could
silently leak actions between integrations. Anchored the prefix
with an underscore so `GMAIL_SEND_EMAIL` matches `gmail_`, not
just `gmail`.

## `scripts/stage-core-sidecar.mjs` — resolve CARGO_TARGET_DIR vs repo root

`resolve(process.env.CARGO_TARGET_DIR)` uses the process's current
working directory, but the `cargo build` spawn below runs with
`cwd: root`. For an absolute env var this is identical, but a
relative `CARGO_TARGET_DIR` and a cwd outside the repo would make
the two paths disagree and the binary lookup would miss. Defensive
fix: `resolve(root, process.env.CARGO_TARGET_DIR)` so both paths
anchor to the same base.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 13:07:26 -07:00
1836c7691f Feat: smart routing (#569)
* chore: update OpenHuman version to 0.52.9 and add intelligent routing functionality

- Bumped OpenHuman version from 0.52.7 to 0.52.9 in Cargo.lock files.
- Introduced a new routing module that implements intelligent model routing based on task complexity and local model health.
- Added a health checker for the local Ollama model server to improve routing decisions.
- Enhanced the provider to classify tasks and determine the appropriate backend (local or remote) for processing requests.
- Updated related files to support the new routing logic and ensure seamless integration with existing functionalities.

* refactor(routing): streamline provider and enhance routing logic

- Removed the `build_tool_instructions` function from the public API, simplifying the routing module.
- Updated the `IntelligentRoutingProvider` to utilize a more efficient model resolution process, improving routing decisions based on task complexity and local model health.
- Introduced a new `quality` module to assess response quality, enabling better fallback decisions when local responses are deemed low quality.
- Enhanced the `RoutingHints` struct to provide more granular control over routing behavior, including privacy requirements and cost sensitivity.
- Added tests to validate the new routing logic and quality assessment, ensuring robust functionality across various scenarios.

* feat(tests): add live end-to-end routing tests for real backend integration

- Introduced a new test file `live_routing_e2e.rs` containing end-to-end tests for routing against a live backend.
- Tests require a valid backend URL, user session JWT, and real network interactions, hence marked as `#[ignore]`.
- Implemented functionality to set up environment variables, write configuration files, and perform JSON-RPC calls to validate routing behavior.
- Added assertions to ensure correct handling of various routing cases, enhancing test coverage for the routing module.

* refactor(format): ran format command

* feat(routing): add IntelligentRoutingProvider and enhance LocalHealthChecker

- Introduced a new `factory.rs` file containing the `new_provider` function to construct an `IntelligentRoutingProvider` that integrates local AI capabilities with remote backend providers.
- Enhanced the `LocalHealthChecker` in `health.rs` by adding a `reqwest::Client` for improved health probing, including better logging for cache hits and misses, and streamlined cache updates.
- Updated health check logic to utilize the new client, ensuring more reliable health status checks for local AI services.

* refactor(routing): move new_provider function to factory module

- Moved the `new_provider` function from `mod.rs` to a new `factory.rs` module to improve code organization and maintainability.
- Updated public exports to include the new location of `new_provider`, ensuring continued accessibility for constructing `IntelligentRoutingProvider` instances.
- Removed the old implementation from `mod.rs`, streamlining the routing module's structure.

* refactor(routing): simplify local task routing logic

- Removed redundant conditions for routing medium tasks locally, streamlining the decision-making process in the `decide` function.
- Updated comments to reflect the simplified logic, enhancing code clarity and maintainability.

* docs(tests): clarify comments in json_rpc_e2e.rs regarding hint overrides
logic.

* refactor(tests): enhance live routing end-to-end tests with timeout handling

- Introduced a timeout mechanism for reading SSE events to prevent indefinite blocking.
- Updated environment variable management in tests to ensure safe access and cleanup.
- Improved comments for clarity regarding the safety of environment variable mutations during tests.

* refactor(tests): update SSE event reading in live routing tests.

* refactor(routing): enhance medium task routing logic and update comments

- Updated the routing logic for medium tasks to utilize hints for local bias, ensuring more accurate routing decisions.
- Revised comments throughout the code to clarify the behavior of task categories and routing preferences.
- Adjusted test cases to reflect the new routing logic, ensuring they accurately validate the expected behavior for medium tasks.

* refactor(tests): implement timeout handling for dictation event reception

- Added a timeout mechanism to the dictation event test to prevent indefinite blocking while waiting for the "pressed" event.
- Enhanced the test logic to consume events until the expected event type is received, improving reliability and clarity in the test flow.

---------

Co-authored-by: Steven Enamakel <31011319+senamakel@users.noreply.github.com>
2026-04-14 13:06:48 -07:00
b2c74458d3 fix(voice): enable GPU detection and Metal acceleration for whisper (#558) (#571)
* fix(device): expand GPU detection with Intel Mac and NVIDIA probes (#558)

Add Intel Mac detection (no Metal GPU for whisper), nvidia-smi probe
for Windows/Linux NVIDIA GPUs, and diagnostic tracing at each
decision point in detect_gpu().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* build(cargo): enable whisper-rs Metal feature on macOS (#558)

Add target-specific dependency for macOS that enables the `metal`
feature on whisper-rs, compiling whisper.cpp with Metal GPU support.
Cargo merges features from both declarations so non-macOS builds
are unaffected.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(whisper): configure GPU params from device profile (#558)

Accept has_gpu and gpu_description in load_engine() and explicitly
set use_gpu and flash_attn on WhisperContextParameters instead of
relying on the compile-time default. Log the selected acceleration
backend at startup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(local_ai): pass GPU info to whisper engine load paths (#558)

Thread DeviceProfile has_gpu and gpu_description through both the
bootstrap (startup) and speech (lazy) whisper engine load calls so
the engine can configure Metal or CUDA acceleration at runtime.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-14 13:06:08 -07:00
CodeGhost21andGitHub 13ce2cdcbf test(coverage): raise Rust unit test coverage to ≥80% on 10 critical modules (issue #530) (#567)
* test(coverage): raise 9 critical modules to ≥80% line coverage

Pushes unit test coverage to meet issue #530's 80% target on 9 modules:
  socket (17% → 80%), credentials (46% → 81%), composio (55% → 81%),
  memory (75% → 80%), tools (73% → 83%), plus previously-passing
  cron, context, learning, embeddings.

Additions span ~400+ new tests across 44 files:
  - Pure-function tests for types, schemas, parsers, URL builders,
    and error-classification helpers.
  - Mock-backend integration tests using axum for HTTP-dependent
    code (composio client + ops, socket ws_loop against a local
    WebSocket server).
  - End-to-end RPC handler tests using tempdir + OPENHUMAN_WORKSPACE
    env override (credentials, memory, config, cron).

Also fixes 4 pre-existing flaky tests on macOS dev machines by
pinning absolute paths (/bin/ls, /bin/sleep, /bin/sh, /usr/bin/touch)
in cron scheduler tests so sh -lc does not pick up homebrew-shadowed
binaries that macOS SIP refuses to execute, and relaxing a
screen_intelligence capture-permission hang assertion.

Remaining work: voice, config, screen_intelligence, channels,
local_ai still below 80% (62%, 61%, 54%, 68%, 35% respectively).

* test(coverage): add config module tests (62% → ~71%)

Adds tests for:
- config/ops.rs: env_flag_enabled, core_rpc_url_from_env, snapshot_config_json,
  workspace_onboarding_flag_exists/_set, set_browser_allow_all, agent_server_status
- config/schemas.rs: catalog parity, all 21 registered schema keys, helpers
- config/schema/proxy.rs: normalize_*, parse_proxy_scope, parse_proxy_enabled,
  validate_proxy_url, service_selector_matches, ProxyConfig defaults
- config/schema/load.rs: apply_env_overrides for api_key, model, temperature,
  reasoning_enabled, web_search.*, storage.*; resolve_config_dir_for_workspace
- config/settings_cli.rs: settings_section_json all sections (model/memory/runtime/browser/unknown)

* test(coverage): config reaches 80.49%, socket back to 80.10%

- config/ops.rs: apply_model/memory/runtime/browser/analytics/screen_intelligence_settings
  roundtrips via in-memory Config; load_and_apply_dictation/voice_server_settings
  activation-mode validation; get_dictation/voice_server/onboarding readers;
  workspace_onboarding_flag_set/resolve error and happy paths.
- config/schema/load.rs: apply_env_overrides coverage for OPENHUMAN_MODEL,
  OPENHUMAN_TEMPERATURE (range clamp), OPENHUMAN_REASONING_ENABLED,
  OPENHUMAN_WEB_SEARCH_*, OPENHUMAN_STORAGE_* env branches.
- config/schema/proxy.rs: normalize_* helpers, parse_proxy_scope/enabled,
  ProxyConfig defaults, validate_proxy_url schemes + hosts,
  service_selector_matches wildcards.
- config/schemas.rs: every registered controller key, namespace parity.
- config/settings_cli.rs: all section projections (model/memory/runtime/browser/unknown).
- Shared `TEST_ENV_LOCK` at config::mod level so config::ops and config::schema::load
  test modules serialize OPENHUMAN_WORKSPACE mutations.
- socket: a couple more schema assertions to keep module at 80%+.

* test(coverage): channels partial push (68.12% → 68.91%)

- channels/controllers/schemas.rs: every registered key resolves, required
  input coverage for describe/send_message/telegram_login_check.
- channels/providers/web.rs: catalog parity, chat/cancel schema required
  inputs, unknown fallback, key_for, event_session_id_for stability,
  normalize_model_override trimming/empty, broadcast channel subscribe,
  field-builder helpers.
- channels/providers/discord/api.rs: auth_header prefix, BotPermissionCheck
  serde with empty/full missing_permissions lists, permission bit flags
  are single-bit and distinct.

Channels remains below the 80% issue target — the bulk of remaining
uncovered code lives in telegram/channel.rs (574 lines), ops.rs (462),
lark.rs (433) and runtime/startup.rs (350), which depend on live HTTP /
socket / runtime bootstrap state that would require extensive mock
infrastructure to exercise from unit tests.

* test(coverage): partial push on local_ai (34→45%), screen_intelligence (54→62%), voice (62→63%)

- local_ai/schemas.rs: catalog parity, every registered key resolves,
  field-builder helpers, deserialize_params happy/error, download_force optional.
- local_ai/ops.rs: local-ai-disabled error paths for prompt/vision_prompt/
  embed/summarize/transcribe/tts/chat; empty-messages rejection; suggestions
  return empty when disabled (graceful degradation).
- local_ai/install.rs: find_system_ollama_binary env-override happy/missing/
  empty cases; PATH-based lookup stub.
- screen_intelligence/schemas.rs: catalog parity, all 15 registered keys
  resolve to non-unknown, unknown fallback.
- screen_intelligence/ops.rs: accessibility_status/doctor_cli_json/
  capture_image_ref/stop_session/vision_recent error-free behaviour.
- voice/server.rs: truncate_for_log ellipsis + multibyte; try_global_server
  after init; additional hallucination-pattern coverage.

Remaining gap on these modules lives almost entirely in:
- local_ai/service/ollama_admin.rs (625 lines) — real HTTP + Ollama subprocess
- local_ai/service/{assets,public_infer,vision_embed}.rs — same
- voice/{server,audio_capture}.rs deep paths — audio hardware
- screen_intelligence/{engine,processing_worker}.rs — active capture session

* test(coverage): channels providers (+0.9%) — qq ensure_https, lark parsers

- qq: ensure_https accept/reject, QQ_API_BASE/AUTH_URL constants, constructor.
- lark: parse_post_content zh_cn/en_us fallback + links/mentions; invalid
  JSON returns None; strip_at_placeholders for @_user_N tokens; group
  should_respond_in_group mention gating.

* test(coverage): push all 4 remaining modules

- discord/api.rs: list_bot_guilds_at_base/list_guild_channels_at_base test
  seams with mock axum server; parse happy-path, error status, channel
  filter+sort, empty list.
- channels/controllers/ops.rs: parse_allowed_users for string CSV/array/
  newline/@-prefix/case-insensitive dedup/non-string; credential_provider;
  list_channels/describe_channel; connect_channel unknown-channel and
  non-object credentials.
- local_ai/ollama_api.rs: `ollama_base_url()` honours OPENHUMAN_OLLAMA_BASE_URL
  env var so tests can point at mock servers; DEFAULT_OLLAMA_BASE_URL
  preserved.
- local_ai/service/public_infer.rs: mock-backend tests for inference/prompt
  happy path, non-success status, suggest_questions parsing, disabled-
  local-ai short-circuits for summarize/prompt/suggest_questions/
  inline_complete.
- voice/schemas.rs: overlay_notify cancelled→released, unknown state
  errors, missing state errors, server_start handler, TranscribeParams +
  TtsParams deserialize happy/error paths, server_start all-optional
  invariant, description completeness.

* test(coverage): local_ai HTTP mock tests (49→53%)

- ollama_api: ollama_base_url() helper now used by ollama_admin/has_model
  + ollama_healthy (in addition to public_infer + vision_embed).
- public_infer: inference against mock /api/generate happy/error/empty;
  suggest_questions parses line-separated output; disabled short-circuits
  for summarize/prompt/suggest/inline_complete.
- vision_embed: mock /api/embed with /api/tags preflight; empty-input
  rejection; disabled short-circuits for embed and vision_prompt.
- ollama_admin: has_model matches exact + prefixed tags; errors on 5xx
  /api/tags; ollama_healthy true on 200 and false on unreachable URL.

* test(coverage): more local_ai + voice gains

- local_ai/ollama_admin: diagnostics against mock Ollama (unreachable +
  missing models + all models present), list_models happy/error paths.
- voice/dictation_listener: start_if_enabled early-returns for disabled/
  empty-hotkey/unparseable-hotkey; normalize_hotkey_for_rdev coverage
  for Shift+Alt, lowercase, function keys, whitespace trimming.

* test(coverage): local_ai schemas handlers + voice flakiness fix

- local_ai/schemas: handle_device_profile; handle_presets tier+device
  shape; handle_apply_preset invalid/custom/valid paths; handle_set_ollama_path
  nonexistent/empty-to-clear paths.
- voice/schemas: relax server_status/stop assertion to tolerate other
  tests in the same binary having initialised the global voice server
  (it's a OnceLock, so state is shared across the whole test process).

* test(coverage): channels incremental push (71→73%)

- presentation.rs: split_sentences, group_sentences, merge_short,
  segment_delay monotonic/bounded, is_structured_content detection,
  segment_for_delivery edge cases.
- runtime/dispatch.rs: contains_any, starts_with_any, full coverage of
  select_acknowledgment_reaction across all 7 categories + deterministic
  + empty/single-char inputs.
- commands.rs: doctor_channels with telegram/discord/slack/imessage/
  multiple-config branches.
- discord/api: check_channel_permissions mock-server tests — admin bypass,
  all-missing, everyone-allow, channel overwrite deny, member lookup
  failure. Added check_channel_permissions_at_base seam.
- lark: should_refresh_last_recv, LarkChannel::new, is_user_allowed
  wildcard/empty allowlist, parse_event_payload edge cases (unsupported
  type / empty sender / missing event / post type).
- email_channel: is_sender_allowed full matrix (empty/wildcard/exact/
  @-prefix/bare-domain/subdomain-confusion), strip_html empty/tags-only/
  unclosed/whitespace collapse.
- voice/schemas: tolerate event interleaving in broadcast-channel test
  (schema bus is process-global).

* test(coverage): address review findings — assertions, determinism, observability

Tighten assertions so regressions surface:
- credentials/ops: assert decrypt migrate path returns Ok
- credentials/session_support: assert trimmed profile name directly
- computer/mouse: check Err branch in single-axis scroll tests
- filesystem/git_operations: assert exact error substrings and verify
  `git init` success before each test
- channels/controllers/ops: exact credential_provider key + concrete
  parse_allowed_users expectation with accurate normalisation note;
  merged the three duplicate list/describe tests into existing coverage
- tools/impl/browser/screenshot: cfg-branched support-matrix assertions
- tools/impl/agent: replace misleading stub test with one that actually
  exercises dispatch_subagent's graceful-failure paths
- local_ai/install: cfg-branched build_install_command expectations
- local_ai/service/public_infer: exercise empty-response reject path via
  inference() (allow_empty=false) and assert the error
- screen_intelligence: only take the macOS slow-path skip on macOS

Test determinism:
- local_ai/install: serialise env mutations via a module Mutex and RAII
  EnvGuard that restores prior values on drop
- composio/ops: poll TCP readiness with backoff before returning the
  mock-backend URL
- memory/global: bind TempDir at test scope so its workspace outlives
  any lazy-init reference

Consistency:
- local_ai/service/ollama_admin: route every Ollama HTTP call and the
  diagnostics URL through ollama_base_url(); drop the stale
  OLLAMA_BASE_URL import so /api/pull, /api/tags (runner check),
  /api/show, and the health message all honour the env override

Observability:
- local_ai/service/vision_embed: tracing::debug at entry/response and
  tracing::error on send/non-success
- local_ai/service/ollama_admin::list_models: entry/response/parse
  logging including raw body on parse failure
- channels/providers/discord/api: tracing::debug with endpoint, status,
  body, and context before every non-success bail!

New coverage:
- channels/providers/lark: anchor href-only fallback in parse_post_content
- local_ai/ollama_api: five-case env-override suite for ollama_base_url
  (unset / normal / trimmed / trailing slashes / empty|whitespace)

Miscellaneous:
- voice/server: OnceCell-backed comment (was OnceLock); drop duplicate
  initial-status test; supply the HallucinationMode argument to two
  stale hallucination tests so the crate type-checks
2026-04-14 12:52:59 -07:00
Steven EnamakelandGitHub 7ae2bf83b2 feat: disable permission auto-prompts + typing indicator on webhook channels (#552)
* feat(config): default screen intelligence, dictation, and vision model off

Flip defaults so no macOS TCC permission prompt fires on first run:

- `dictation.enabled`: `true` → `false` (was auto-starting rdev::listen,
  which requests Accessibility/Input Monitoring on macOS)
- `screen_intelligence.use_vision_model`: `true` → `false` (fewer
  surprise vision-model calls; Pass 1 Apple Vision OCR still runs)

Aligns all permission-gated auto-starts on a consistent opt-in posture:
`screen_intelligence.enabled`, `autocomplete.enabled`, and
`voice_server.auto_start` already default to `false`. Users must now
explicitly flip each toggle (config or JSON-RPC) before the core triggers
any OS permission dialog.

* feat(channels): fire typing indicator on webhook-inbound path

Two inbound flows exist today and only one fires typing:

- Local bot (`channels_config.telegram.bot_token` set) → dispatch.rs
  already calls `channel.start_typing` + `spawn_scoped_typing_task`
- Backend webhook (Telegram → backend → socket.io → core) →
  `ChannelInboundSubscriber` had **no typing call** — replies route via
  backend REST, so the local `Channel` trait isn't reachable.

Close the gap by going through the backend:

- `api/rest.rs`: add `send_channel_typing(channel, jwt, body)` hitting
  the new `POST /channels/:id/typing` backend route.
- `channels/bus.rs`: extract the agent-wait loop into `run_agent_loop`
  and wrap it with a typing task that fires immediately on `start_chat`
  success, refreshes every 4s (beats Telegram's ~5s and Discord's ~10s
  typing TTLs), and cancels on every exit path (done / error / empty /
  bus-closed / lagged / timeout). Backend failures log at debug — a
  flaky typing call must never block the reply flow.

Generalises to every channel with a backend adapter; adapters without a
native typing API no-op gracefully.

* Enhance test stability by introducing a Mutex guard for TRIAGE_DISABLED_ENV in tests

- Added a static Mutex guard to ensure safe concurrent access to the `TRIAGE_DISABLED_ENV` variable during tests, preventing interleaved set_var/remove_var calls that could lead to spurious failures.
- Updated relevant test cases to acquire the Mutex lock when accessing the environment variable, ensuring consistent behavior across concurrent test executions.
2026-04-14 08:19:57 -07:00
933c233704 fix(voice): add hallucination filter to chat voice path (#553) (#556)
* fix(voice): add hallucination filter to chat voice path and improve detector (#553)

The chat voice transcription pipeline (ops.rs voice_transcribe_bytes) had
no hallucination filtering, unlike the desktop dictation server which has
had it since inception. This caused Whisper to inject repetitive garbage
text ("it... it... it...", "Thank you. Thank you. Thank you.") into chat
voice input, especially on short/silent recordings.

Changes:
- Extract hallucination detection into shared voice/hallucination.rs module
- Add hallucination filter to voice_transcribe_bytes (chat voice path)
- Improve detector: strip punctuation before word comparison, add
  dominant-word ratio check (>40%), catch "it... it... it..." patterns
- Add 14 unit tests covering exact match, repetition, ratio, and
  legitimate speech cases

Closes #553

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(voice): introduce HallucinationMode enum with split pattern lists

Split monolithic HALLUCINATION_PATTERNS into ALWAYS_HALLUCINATION
(blank-audio, YouTube phrases — filtered in all modes) and
DICTATION_ONLY_PATTERNS (single-word noise like "yes", "okay" —
filtered only in desktop dictation). Add repeating n-gram detection
for looping phrases ("Thank you. Thank you. Thank you.") and raise
dominant-word ratio from >40%/3 to >60%/5 to prevent false positives
on emphatic speech like "no no no don't do that".

Addresses CodeRabbit review on PR #556.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(voice): use Conversation mode for chat, Dictation mode for desktop

Wire HallucinationMode into both voice paths:
- ops.rs (chat voice): HallucinationMode::Conversation — conservative
  filtering allows short replies like "yes", "okay", "thank you"
- server.rs (desktop dictation): HallucinationMode::Dictation —
  aggressive filtering drops single-word noise artifacts
- Update server.rs hallucination_detection test for new signature

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(voice): redact user transcript content from debug logs

Remove raw text interpolation (normalized, first, word, pattern) from
hallucination detection debug logs to prevent leaking sensitive speech
content. Retain non-PII metadata (repeat counts, ratios, n-gram length)
for diagnostics.

Addresses CodeRabbit review on PR #556.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-14 07:33:33 -07:00
YellowSnnowmannandGitHub 219a2ff9af Fix: GitHub composeio (#543)
* feat(composio): add GitHub repository management and trigger creation

- Enhanced the ComposioConnectModal component to support loading and selecting GitHub repositories for connected users.
- Introduced new API functions for listing GitHub repositories and creating triggers, improving integration capabilities with GitHub.
- Added state management for repository loading, selection, and trigger action feedback, enhancing user experience during GitHub integration.
- Updated types to include GitHub repository structures and responses, ensuring type safety and clarity in the codebase.

* feat(composio): add GitHub repository listing and trigger creation APIs

- Implemented new API endpoints for listing GitHub repositories and creating triggers, enhancing integration capabilities with GitHub.
- Updated types to include responses for GitHub repositories and trigger creation, ensuring type safety and clarity.
- Added corresponding handler functions and schemas to support the new operations, improving overall functionality and user experience in the Composio integration.

* refactor(composio): remove GitHub repository and trigger management from ComposioConnectModal

- Eliminated GitHub repository loading and trigger creation logic from the ComposioConnectModal component, streamlining its functionality.
- Removed associated state management and API calls for GitHub repositories and triggers, enhancing code clarity and maintainability.
- Updated types to reflect the removal of GitHub-related structures, ensuring consistency across the codebase.

* format: ran format
2026-04-14 15:58:12 +05:30
Cyrus GrayandGitHub 21e4a8b307 fix(onboarding): remove Screen & Accessibility Permissions step (#557)
* fix(onboarding): remove Screen & Accessibility Permissions step

The permissions step added friction to onboarding without being essential —
users can still configure screen intelligence permissions later via Settings.

Steps are now: Welcome → Referral → Skills → Context Gathering.

* fix(onboarding): remove Screen & Accessibility Permissions step

The permissions step added friction to onboarding without being essential —
users can still configure screen intelligence permissions later via Settings.

Steps are now: Welcome → Referral → Skills → Context Gathering.

* fix(onboarding): skip context gathering when no sources connected

When the user clicks "Skip for Now" on the Gmail step, complete
onboarding immediately instead of showing the context gathering step.

* fix(onboarding): address CodeRabbit review feedback

- Preserve accessibilityPermissionGranted from existing state instead
  of hardcoding false (matches ToolsPanel defensive pattern)
- Update E2E test step comments and detection logic to match the real
  onboarding flow (WelcomeStep → ReferralApplyStep → SkillsStep →
  ContextGatheringStep) and use actual UI text
2026-04-14 13:44:02 +05:30
github-actions[bot] d5be45d42d chore(release): v0.52.9 v0.52.9 2026-04-14 07:12:04 +00:00
github-actions[bot] 95ccf86069 chore(release): v0.52.8 2026-04-14 06:01:16 +00:00
Steven EnamakelandGitHub 2824850708 feat(streaming): thinking tokens, tool call deltas, and progressive channel edits (#549)
* feat(conversations): implement live streaming for assistant responses

- Added support for live streaming of assistant responses in the Conversations component, enhancing user experience during interactions.
- Introduced new interfaces for handling streaming state, including `StreamingAssistantState` and related event handlers for text, thinking, and tool argument deltas.
- Updated the state management to accommodate streaming data, ensuring smooth updates to the UI as new content arrives.
- Enhanced the rendering logic to display provisional assistant bubbles and thinking indicators while responses are being composed.
- Refactored existing event handling to integrate with the new streaming functionality, improving overall responsiveness and interactivity.

This commit significantly improves the real-time interaction capabilities of the assistant, providing users with immediate feedback during conversations.

* feat(channels): progressive-edit streaming for inbound channels

ChannelInboundSubscriber now buffers text/tool delta events on a 1s
timer and edits the outbound channel message in place, so Telegram/
Slack-style conduits can show a single evolving reply instead of a
single atomic bubble at the end. Renders a "🔧 tool …" status line
above the partial text and rewrites with the final canonical reply
on chat_done.

Falls back to atomic-final delivery if the backend's PATCH
/channels/:channel/messages/:id endpoint is unavailable or repeatedly
fails, so existing channels keep working while the edit endpoint is
rolled out.

New rest.rs helper: BackendOAuthClient::send_channel_edit.

* style: cargo fmt on streaming-related files

Auto-applied by cargo fmt after the streaming plumbing changes touched
provider traits, the session turn loop, the channel inbound subscriber,
and the compatible-provider SSE path.

* feat(streaming): compact live preview + Telegram typing indicator

UI: while streaming, show only the trailing ~120 chars of assistant
text in a monospace ticker-tape bubble (cursor + ellipsis prefix) so
the scroll position doesn't jump as tokens arrive. The full response
still replaces the preview via addInferenceResponse on chat_done.

Backend bridge: ChannelInboundSubscriber now fires Telegram's typing
indicator as soon as an inbound message is received and refreshes it
every 4 s while the turn is in flight (Telegram's sendChatAction lasts
~5 s). New BackendOAuthClient::send_channel_typing hits
POST /channels/:channel/typing; the subscriber latches
typing_disabled after two failures so channels without the endpoint
stop getting hit.

* fix(providers): fall back to JSON parse when upstream ignores stream=true

Some OpenAI-compatible backends (including our e2e mock) accept
`stream: true` in the request but reply with a regular
`application/json` body rather than an SSE stream. The previous
implementation blindly pushed the body through the SSE line parser,
which produced an empty aggregated response because the body never
contained `\n\n` event separators.

Detect the non-SSE content-type and fall through to the existing
`parse_native_response` path so the caller still gets the aggregated
response. No deltas are emitted in this case — there's nothing to
stream — but correctness is preserved.

Unblocks the json_rpc_protocol_auth_and_agent_hello E2E test.

* Enhance event handling for progressive-edit streaming in ChannelInboundSubscriber

- Introduced a new `StreamingState` struct to manage the state of progressive-edit streaming, allowing for buffered text and tool deltas.
- Implemented a timer-based flushing mechanism for edits, ensuring timely updates to the channel.
- Refactored event handling logic to accommodate new event types (`text_delta`, `tool_call`, `tool_result`) and improved error handling for chat events.
- Updated the logic for finalizing channel replies to handle both streaming and atomic delivery scenarios, enhancing overall responsiveness and user experience.

This commit significantly improves the handling of real-time updates during user interactions, providing a smoother and more interactive experience.

* Enhance tool call tracking in AgentProgress and parsing logic

- Added a `call_id` field to `ToolCallStarted` and `ToolCallCompleted` variants in the `AgentProgress` enum to uniquely identify tool calls and link them to their respective events.
- Updated the `ParsedToolCall` struct to include an optional `id` field for tool calls originating from native responses, ensuring consistent tracking across different call types.
- Modified the `parse_tool_call_value` function to initialize the `id` field appropriately, enhancing the parsing logic for tool calls.
- Adjusted the `run_tool_call_loop` function to utilize the new `call_id` for progress events, ensuring accurate tracking of tool execution across iterations.
- Enhanced the `spawn_progress_bridge` function to handle the new `call_id` field in progress events, improving the overall event handling mechanism.

These changes improve the robustness of tool call tracking and enhance the clarity of event relationships within the agent's progress reporting.

* Enhance tool call tracking and event handling in Conversations component

- Added a `tool_call_id` field to `ChatToolCallEvent` and `ChatToolResultEvent` interfaces for improved tracking of tool calls across events.
- Updated event key generation in the Conversations component to include `tool_call_id`, ensuring unique identification of tool events.
- Enhanced the logic for managing tool timelines to prevent duplication of entries by reconciling existing tool calls based on `tool_call_id`.
- Improved handling of tool argument deltas and results, allowing for more accurate updates to the UI during tool execution.

These changes significantly improve the robustness of tool call tracking and enhance the clarity of event relationships within the Conversations component.

* Enhance tool call event handling and progress reporting in Agent

- Updated the `run_tool_call_loop` function to include stable IDs for tool calls, ensuring unique identification across iterations.
- Introduced early completion events for failed tool calls, improving client-side error handling and user experience.
- Refactored the `emit_progress` function to use asynchronous sending, ensuring lifecycle events are not dropped due to backpressure.
- Enhanced the `turn` method to await progress emissions, improving synchronization during user message processing.

These changes significantly improve the robustness of tool call tracking and enhance the clarity of event relationships within the agent's progress reporting.

* fix(channels): close stuck drafts, prevent duplicate post, add bridge logs

Findings #3, #5, #6 from the follow-up review:

channels/bus.rs
- chat_done handler no longer returns early on empty reply — it now
  finalizes with a "(No response from agent.)" fallback so any draft
  we posted during streaming gets closed off instead of being left
  showing "_working…_" forever.
- StreamingState gained `draft_sent: bool`, set whenever the initial
  send_channel_message succeeds (even when the backend's response
  didn't include an id). finalize_channel_reply now checks this flag
  and silently skips the "no draft → send atomic" fallback when a
  draft was posted but id was lost — fixes a duplicate-bubble bug
  where an id-less draft plus a chat_done finalize produced two
  messages in the user's channel.

channels/providers/web.rs
- spawn_progress_bridge now logs a scoped entry message on startup
  (client_id/thread_id/request_id), a per-variant trace/debug line on
  each AgentProgress event (with call_id/iteration correlation), and
  an exit message with final round + events_seen count. SubagentFailed
  is logged at warn level for visibility.
2026-04-13 20:38:00 -07:00
Steven EnamakelandGitHub 3db06e8e92 feat(prompt): per-agent PROFILE.md + MEMORY.md injection, 2K-char cap (#550)
* Enhance debug-agent-prompts script and prompt rendering logic

- Updated the `debug-agent-prompts.sh` script to always run `cargo build`, ensuring the latest binary is used and preventing stale binaries from affecting agent behavior.
- Modified the output directory handling to wipe and recreate it at the start of each run, ensuring a clean snapshot of the current agent set.
- Enhanced the `render_subagent_system_prompt` function to unconditionally inject `PROFILE.md`, ensuring it is included even when identity information is omitted, thus improving personalization for agents like `welcome`.
- Added tests to verify the correct injection of `PROFILE.md` under various conditions, ensuring robust functionality and preventing regressions in prompt rendering.

* Enhance debug-agent-prompts script to utilize the currently-logged-in user's workspace

- Updated the `debug-agent-prompts.sh` script to point to the real user's workspace, ensuring onboarding-generated files like `PROFILE.md` are included in the dump.
- Improved workspace resolution logic to prioritize the active user's workspace, falling back to default paths as necessary.
- Added error handling for cases where the workspace is not found, providing clear guidance for users to complete onboarding or specify a different workspace.
- Enhanced output to include the presence state of `PROFILE.md`, improving visibility into the onboarding process.

* Add profile inclusion for user-facing agents to enhance personalization

- Updated agent TOML configurations for orchestrator, trigger reactor, trigger triage, and welcome agents to include user profile data by setting `omit_profile = false`. This allows agents to personalize interactions based on user context derived from `PROFILE.md`.
- Refactored the `AgentDefinition` struct to include a new `omit_profile` field, ensuring that agents can opt-in to utilize user profile information.
- Enhanced prompt rendering logic to conditionally inject `PROFILE.md` based on the `omit_profile` flag, improving the relevance and personalization of agent responses.
- Updated tests to verify the correct behavior of profile inclusion across various agents, ensuring robust functionality and preventing regressions.

* Implement `omit_profile` flag in `AgentBuilder` and `Agent` for profile management

- Added a new `omit_profile` field to the `AgentBuilder` struct, allowing agents to specify whether to include user profile data in their responses.
- Updated the `Agent` struct to mirror the `omit_profile` flag, ensuring that the profile inclusion logic is consistent across agent instances.
- Enhanced the `omit_profile` method in `AgentBuilder` to facilitate the configuration of this flag during agent construction.
- Adjusted the default behavior to omit profiles for legacy agents while allowing opt-in for specific agents that require user context.
- Updated documentation to clarify the purpose and usage of the `omit_profile` flag in agent definitions.

* Implement profile management enhancements in Agent and prompt rendering

- Introduced an `omit_profile` flag in the `AgentBuilder` and `Agent` to control the inclusion of user profile data in responses, defaulting to true for legacy paths.
- Updated the `Agent` struct to utilize the `omit_profile` flag, ensuring consistent profile inclusion logic across agent instances.
- Enhanced prompt rendering logic to conditionally include or exclude `PROFILE.md` based on the `omit_profile` setting, improving personalization for user-facing agents.
- Added tests to verify the correct behavior of profile inclusion and omission across various scenarios, ensuring robust functionality and preventing regressions.

* feat(prompt): per-agent MEMORY.md injection with 2000-char cap

Add `omit_memory_md` to `AgentDefinition` (mirror of `omit_profile`)
and inject `MEMORY.md` alongside `PROFILE.md` in both the main and
sub-agent render paths. Both user-specific files are capped at
`USER_FILE_MAX_CHARS = 2_000` (~1000 tokens each) via a new
`inject_workspace_file_capped` helper so growing on-disk files can't
balloon the system prompt.

Opt-in on the same four user-facing agents (welcome, orchestrator,
trigger_triage, trigger_reactor). Narrow specialists leave it at the
`true` default.

KV-cache contract is documented on the flag, the injection sites, and
the capped helper: rendered bytes are frozen per session, and
mid-session writes only surface on the next session. Pinned with a new
`rendered_subagent_system_prompt_is_byte_stable_across_repeat_calls`
test plus coverage for injection / opt-out / 2000-char cap.

* refactor(tests): clean up test formatting and improve readability

- Removed unnecessary line breaks and adjusted indentation in test files for better consistency and clarity.
- Reformatted the `RewardsCouponSection` test to enhance readability and maintain a uniform style across test cases.
- Ensured that all test cases align with the updated formatting standards, improving overall maintainability.

* feat(debug-agent-prompts): enhance output directory validation and canonicalization

- Improved the `debug-agent-prompts.sh` script to validate and canonicalize the output directory (`OUT_DIR`) before performing any file operations.
- Added checks to reject relative paths and ensure the output directory is an absolute path, preventing potential catastrophic deletions.
- Implemented a `canonicalize` function that uses `realpath` or `readlink` to resolve paths, with a fallback to Python for compatibility on barebones systems.
- Enhanced error handling to provide clear feedback when the output directory cannot be validated or canonicalized.
- Ensured that the script operates on the canonicalized path for all subsequent commands, maintaining consistency and safety.

* style(turn): single-line rustfmt for redacted log branch
2026-04-13 20:06:00 -07:00
Steven EnamakelandGitHub 8cbb425cea feat(onboarding): fire welcome agent immediately on completion (#548)
* Implement proactive welcome agent for onboarding experience

- Added a new module `welcome_proactive` to handle immediate welcome messages after user onboarding completion, enhancing user engagement.
- Updated `set_onboarding_completed` to trigger the proactive welcome agent upon onboarding status change, ensuring timely delivery of welcome messages.
- Refactored the onboarding process to streamline the integration of proactive messaging, improving overall user experience.
- Enhanced documentation and logging for better observability of the onboarding flow and proactive message handling.
- Introduced tests to validate the functionality of the new welcome agent and its integration into the onboarding process.

* fix(socketio): auto-join "system" room so proactive messages reach clients

The proactive message subscriber emits with `client_id="system"`, but
connected Socket.IO clients only auto-joined a room named after their
own sid — so the welcome agent's message was published into an empty
room and never reached any frontend.

Adds `socket.join("system")` at connect time alongside the existing
per-sid join, so every client now receives broadcast proactive events
(welcome agent, morning briefing, cron announcements).

Also adds scripts/test-proactive-welcome.sh — end-to-end smoke test
that resets onboarding flags, spawns a fresh core on port 7789,
connects a Socket.IO listener, fires the RPC, and reports pass/miss
for every checkpoint in the pipeline including client-side delivery.

* fix(welcome): PR review — tighter gating, legacy-cron prune, safer harness

- Gate proactive welcome spawn on `!was_chat_completed` so a user
  whose chat flow already completed (legacy tool path or manual flip)
  doesn't get a second welcome.
- Prune legacy `welcome` cron jobs in `seed_proactive_agents`: one-
  shot entries left behind by an interrupted earlier run would
  otherwise double-deliver once the scheduler picks them up. Added
  a unit test covering the prune + morning-briefing seed in the same
  call.
- Add a post-publish debug log in `welcome_proactive::run_proactive_welcome`
  so "reached end successfully" is distinguishable from "silently
  bailed above" by reading the log alone.
- Replace ignored `socket.join(...)` results with a helper that logs
  success + failure per room + client id. Silent join failures on the
  "system" room make proactive delivery vanish without a trace.
- Harden the test harness:
  * Back up CONFIG_PATH before mutating and restore on exit so the
    developer's staging profile is never permanently changed (unless
    `--keep-flags` is passed).
  * Tolerate inline comments + trailing whitespace in the TOML
    rewrite; append-at-end instead of prepend when a key is missing,
    so the rewrite can't accidentally land inside a section header.

Not applied:
- Unit tests for `run_proactive_welcome` empty-response / serialization-
  failure paths: empty response is already `anyhow::bail!`-guarded, and
  the serde_json failure branch is unreachable given a `json!`-built
  value. Testing either requires mocking `Agent::from_config_for_agent`
  which has heavy registry/provider dependencies — cost > value.
2026-04-13 19:43:24 -07:00
Steven EnamakelandGitHub 5a8a7edb91 Refine billing, settings, rewards, and usage UI (#547)
* feat: add react-icons support and refactor skill category handling

- Added `react-icons` dependency to the project for enhanced icon usage.
- Introduced new skill icons in the `toolkitMeta.tsx` component, replacing SVG icons with `react-icons` for improved maintainability and consistency.
- Created a new `skillCategories.ts` file to define skill categories and their order, streamlining the management of skill categories across the application.
- Refactored the `SkillCategoryFilter` component to utilize the new skill categories structure, enhancing clarity and reducing redundancy in the codebase.
- Updated the `Skills` page to leverage the new icon rendering method and skill categories, improving the overall user experience.
- Added tests to ensure the correct functionality of the new skill category and icon implementations.

* feat: update environment configuration and enhance settings UI

- Updated `.env.example` and `app/.env.example` to reflect new backend URL and added optional environment selector for staging.
- Enhanced `SettingsHome` component by separating account and billing sections for improved clarity.
- Introduced a new billing section in the settings menu to streamline user navigation.
- Updated `useSettingsNavigation` hook to accommodate changes in settings structure.
- Improved `BillingPanel` to handle session token checks and ensure accurate billing state retrieval.
- Refactored `Rewards` page to enhance user experience with clearer progress indicators and improved layout.
- Added tests to validate changes in the rewards and settings components.

* feat(config): enhance API base URL handling and environment configuration

- Introduced a new staging API base URL constant for better environment management.
- Added app environment variable constants to streamline environment detection.
- Refactored effective API URL resolution to accommodate environment-specific defaults.
- Implemented functions to resolve app environment from process environment variables.
- Added tests to validate the correct behavior of staging and production API URL handling.

* feat(rewards): add community and referrals tabs for rewards management

- Introduced `RewardsCommunityTab` and `RewardsReferralsTab` components to enhance the rewards management interface.
- The `RewardsCommunityTab` displays user progress, Discord role statuses, and connection options, improving user engagement with rewards.
- The `RewardsReferralsTab` allows users to manage their referral program, track progress, and access coupon rewards in a streamlined layout.
- Updated the `Rewards` page to integrate these new tabs, enhancing overall user experience and navigation.

* feat(rewards): introduce ReferralRewardsSection and RewardsRedeemTab components

- Added `ReferralRewardsSection` to manage referral statistics and code application, enhancing user engagement with the referral program.
- Created `RewardsRedeemTab` to streamline the process of applying reward codes, improving the overall rewards management experience.
- Updated `Rewards` page to include the new redeem tab, allowing users to easily switch between referral and redeem functionalities.
- Refactored `RewardsCommunityTab` to adjust the referral selection handler, ensuring consistent navigation across the rewards interface.
- Removed redundant UI elements in `RewardsCouponSection` for a cleaner layout.
- Enhanced tests to cover new functionalities and ensure robust performance across the rewards system.

* feat(ui): introduce PillTabBar component and refactor SkillCategoryFilter and Rewards pages

- Added a new `PillTabBar` component to enhance tab navigation with customizable styles and item rendering.
- Refactored `SkillCategoryFilter` to utilize `PillTabBar`, improving code clarity and reducing redundancy in button rendering.
- Updated the `Rewards` page to replace the existing tab navigation with `PillTabBar`, streamlining the user interface for switching between referral, rewards, and redeem tabs.
- Enhanced the `ReferralRewardsSection` layout for better user experience and consistency across the rewards management interface.
- Improved tests to cover the new `PillTabBar` functionality and ensure robust performance across the updated components.

* fix(rewards): update placeholder and button text in RewardsCouponSection

- Changed the input placeholder from "Promo code" to "Coupon code" for clarity.
- Updated button text from "Apply code" to "Redeem Code" to better reflect the action being performed.
- Adjusted loading state text from "Applying…" to "Redeeming..." for consistency in user feedback.

* feat(composio): enhance toolkit handling and improve user messaging

- Updated the `ComposioConnectModal` to simplify the connection message, removing unnecessary wording for clarity.
- Introduced a `TOOLKIT_ALIASES` mapping in `toolkitMeta.tsx` to standardize toolkit slugs, improving consistency across the application.
- Refactored the `composioToolkitMeta` function to utilize the new slug mapping, enhancing toolkit metadata retrieval.
- Improved the `useComposioIntegrations` hook to leverage the canonicalized toolkit slugs for better integration handling.
- Adjusted the `Skills` page to normalize toolkit slugs during rendering, ensuring a consistent user experience.
- Updated tests to reflect changes in messaging and toolkit handling, ensuring robust functionality across the application.

* feat(intelligence): add new tabs for Dreams, Memory, and Subconscious features

- Introduced `IntelligenceDreamsTab`, which displays generated dreams based on daily life events, enhancing user engagement with a visually appealing layout.
- Added `IntelligenceMemoryTab` to manage actionable items, featuring search and filter capabilities for improved user interaction with memory data.
- Created `IntelligenceSubconsciousTab` to handle subconscious tasks and logs, providing users with insights and management options for their subconscious activities.
- Refactored the `RewardsCommunityTab` to remove unused Discord role status logic, streamlining the component for better performance.
- Implemented a new `PageBackButton` component for consistent navigation across settings pages.
- Enhanced the `BillingPanel` and its subcomponents to improve user experience in managing billing and subscription details, including transaction history and payment methods.
- Added new billing-related tabs for better organization and access to billing features, including `BillingOverviewTab`, `BillingPaymentsTab`, and `BillingPlansTab`.
- Updated tests to ensure functionality across new and refactored components, maintaining robust application performance.

* refactor(billing): update BillingPanel and BillingPlansTab for improved user experience

- Changed the default selected tab in `BillingPanel` from 'overview' to 'plans' to prioritize subscription management.
- Removed the `BillingOverviewTab` from the `BillingPanel`, streamlining the billing interface.
- Enhanced the `BillingPlansTab` header to clarify the purpose, changing "Explore tiers" to "Choose a Subscription Plan".
- Updated the description in `BillingPlansTab` for better clarity on payment options.
- Improved the layout of the `SubscriptionPlans` component to better highlight crypto payment options and their availability.
- Cleaned up unused code and comments for better maintainability.

* refactor(billing): streamline BillingPanel and BillingPlansTab components

- Removed unused `teamUsage` state and related API call from `BillingPanel` to simplify data management.
- Adjusted layout in `BillingPlansTab` for improved visual hierarchy and user experience.
- Cleaned up code by eliminating unnecessary comments and enhancing maintainability.

* refactor(billing): enhance SubscriptionPlans layout for improved responsiveness

- Updated the layout of the `SubscriptionPlans` component to ensure better responsiveness and visual consistency.
- Adjusted class names to include minimum height and width properties for better alignment across different screen sizes.
- Enhanced the layout of the pricing display section for improved clarity and user experience.

* refactor(billing): update subscription plans and billing components for improved clarity and user experience

- Adjusted pricing for BASIC and PRO plans to reflect new monthly and annual rates.
- Enhanced feature descriptions for subscription plans to better communicate value.
- Removed redundant UI elements in the BillingPaymentsTab and PayAsYouGoCard for a cleaner layout.
- Added loading and confirmation messages in SubscriptionPlans to improve user feedback during payment processes.
- Updated class names for better responsiveness and visual consistency across billing components.

* refactor(billing): improve error messaging and UI consistency in billing components

- Updated error message in BillingPanel to specify adding a payment card on Stripe for clarity.
- Changed header text in AutoRechargeSection to "Enable Auto-Recharge" for better user understanding.
- Modified button text in AutoRechargeSection to "Add card on Stripe" for consistency.
- Enhanced styling in PayAsYouGoCard for improved visual appeal and user interaction.
- Removed redundant UI elements in PayAsYouGoCard for a cleaner layout.

* refactor(billing): remove unused error handling and improve UI consistency across billing components

- Eliminated `setArError` prop from BillingPanel, AutoRechargeSection, and BillingPaymentsTab to streamline error handling.
- Enhanced layout and styling in BillingHistoryTab and SubscriptionPlans for better visual consistency and user experience.
- Removed the BillingOverviewTab component to simplify the billing interface and improve maintainability.

* refactor(billing): update feature descriptions and enhance SubscriptionPlans display

- Revised feature descriptions in the PLANS array for clarity and conciseness.
- Increased the number of displayed features in the SubscriptionPlans component to provide users with more information at a glance.

* refactor(billing): update billing components for improved clarity and functionality

- Revised feature descriptions in the PLANS array to reflect increased usage limits.
- Renamed header in BillingHistoryTab to "Transaction History" and updated description for clarity.
- Adjusted transaction amount formatting in BillingHistoryTab to display five decimal places.
- Removed redundant UI elements in BillingPaymentsTab to streamline the layout.
- Enhanced PayAsYouGoCard with improved credit balance display and top-up options, including validation for custom amounts.

* feat(usage): add budget completion message logic and update tests

- Introduced `shouldShowBudgetCompletedMessage` to the `UsageState` interface to indicate when the budget completion message should be displayed.
- Updated the `useUsageState` hook to calculate the budget completion message based on user credits and budget status.
- Enhanced tests for `useUsageState` to verify the correct behavior of the budget completion message under various scenarios.
- Modified the `Conversations` component to display the budget completion message appropriately based on the new logic.

* style: apply formatter fixes from pre-push hook

* refactor(rewards): update coupon section and test cases for clarity and consistency

- Renamed placeholder text and button labels in the RewardsCouponSection test to reflect updated terminology.
- Adjusted test assertions to ensure they align with the new button and placeholder names.
- Updated pricing details in billingHelpers tests to reflect new monthly and annual rates for BASIC and PRO plans.
- Enhanced the ContextGatheringStep labels for better clarity regarding email and LinkedIn processing stages.

* style(tests): format coupon code input changes for consistency

- Reformatted the coupon code input changes in the RewardsCouponSection test for improved readability and consistency.
- Ensured that the test cases maintain a uniform style for better maintainability.

* refactor: enhance accessibility and improve code structure in various components

- Added ARIA roles and attributes to the PillTabBar for better accessibility.
- Refactored the canonicalization logic for toolkit slugs into a new file for improved organization.
- Updated the IntelligenceDreamsTab and IntelligenceMemoryTab components for better readability and accessibility.
- Enhanced error handling and logging in the IntelligenceSubconsciousTab for improved debugging.
- Streamlined the ReferralRewardsSection and RewardsCommunityTab components by normalizing referral code input handling.
- Removed unused props and improved layout consistency in BillingPanel and related components.
- Updated the Skills page to handle subconscious escalation dismissals more effectively.

* feat(release): configure staging environment for deployment

- Added steps to configure the staging app environment in the release workflow.
- Set environment variables for staging, including OPENHUMAN_APP_ENV and VITE_OPENHUMAN_APP_ENV.
- Updated build and deployment steps to conditionally use staging settings based on the build target.
- Ensured proper handling of workspace paths for staging deployments.

* chore(env): add optional staging environment variable to .env.example

- Introduced OPENHUMAN_APP_ENV variable to specify the app environment as 'staging'.
- Updated .env.example to include new environment configuration for clarity.

* refactor(intelligence): streamline navigation and improve text formatting

- Simplified the navigation logic in the IntelligenceSubconsciousTab for better readability.
- Improved text formatting in the IntelligenceDreamsTab for enhanced clarity and consistency.
- Refactored import statements in toolkitMeta.tsx for better organization.
2026-04-13 18:10:07 -07:00
Steven EnamakelandGitHub f26a0c50b0 feat(referral): switch to link-based claims with flat $5 rewards (#546)
* refactor(referral): update referral system to a one-time flat reward structure

- Transitioned to a link-based referral system offering a one-time $5 credit to both the referrer and the referred user upon the first subscription payment.
- Removed the previous reward rate in basis points and eliminated recurring rewards, ensuring clarity in the referral process.
- Updated API endpoints and service methods to reflect the new `claimReferral` functionality, enhancing user experience and eligibility checks.
- Revised documentation and tests to align with the new referral structure, ensuring comprehensive coverage and understanding of the changes.

* refactor(referral): simplify JSON response handling in referral claim function

- Streamlined the `handle_referral_claim` function by removing unnecessary `as_deref()` and `filter()` calls, enhancing code clarity and performance.
- Updated the JSON response construction to eliminate redundant line breaks, improving readability without altering functionality.
2026-04-13 14:58:58 -07:00
7685e877ee feat(agent): welcome->orchestrator routing + per-agent tool scoping (#525, #526) (#544)
* feat(agent): add subagents + delegate_name fields to AgentDefinition (#525, #526)

Introduces the schema change needed to make agent definitions the single source
of truth for both direct tools and delegation targets:

- `subagents: Vec<SubagentEntry>` — declarative list of agents this agent can
  spawn, expanding at build time into synthesised delegate_* tools on the LLM's
  function-calling surface. Supports two TOML shapes via `#[serde(untagged)]`:
    * Bare string (`"researcher"`) → `SubagentEntry::AgentId`
    * Inline table (`{ skills = "*" }`) → `SubagentEntry::Skills(SkillsWildcard)`
  The `Skills` variant expands dynamically to one delegate_{toolkit} tool per
  connected Composio toolkit at runtime.

- `delegate_name: Option<String>` — optional override for the tool name this
  agent is exposed as when another agent lists it in `subagents`. Defaults to
  `delegate_{id}` when absent, lets the researcher agent be exposed as
  `research`, code_executor as `run_code`, etc.

Schema only — no runtime behavior change yet. Follow-up commits wire the field
into `collect_orchestrator_tools`, the dispatch path, and the debug dump.

TOML placement note: `subagents = [...]` must appear before the `[tools]` table
header in agent TOMLs. Once a table section opens, every subsequent top-level
key is consumed by that table, so placing `subagents` after `[tools]` parses it
as `tools.subagents` and fails deserializing ToolScope. The test doc-comment
records this constraint.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(agent): drive orchestrator delegation tools from TOML subagents (#525, #526)

Completes the half-built migration from a hardcoded ARCHETYPE_TOOLS table
+ orphan skill_delegation.rs to a TOML-driven delegation surface where
each agent declares its subagents and the tool list is synthesised from
the registry at agent-build time.

Rewrites `collect_orchestrator_tools` to take the orchestrator's
AgentDefinition, the global AgentDefinitionRegistry, and the slice of
connected Composio integrations, then iterates the definition's
`subagents` field:

  * `SubagentEntry::AgentId` → one ArchetypeDelegationTool. The tool's
    `name()` comes from the target agent's `delegate_name` override (or
    `delegate_{id}` fallback) and its `description()` is the target's
    `when_to_use`. Editing an agent's TOML when_to_use now immediately
    updates the tool schema the orchestrator LLM sees — zero drift, no
    hardcoded description strings to keep in sync.

  * `SubagentEntry::Skills { skills = "*" }` → one SkillDelegationTool
    per connected Composio toolkit. Each routes to the generic
    skills_agent with `skill_filter` pre-populated. Empty integrations
    list (CLI path, or user not yet connected) produces zero tools
    rather than phantom `delegate_*` entries for unconnected toolkits.

Also in this commit:

- Wire the orphan `skill_delegation.rs` into `impl/agent/mod.rs` via
  `mod skill_delegation;` + `pub use SkillDelegationTool;`. The file has
  existed since earlier work but was never declared in the module tree,
  so it compiled as dead code.

- Delete the legacy `MAIN_AGENT_TOOL_ALLOWLIST` constant and the
  `main_agent_tools` filter in `tools/ops.rs`. They were documented as
  "no longer the primary source of truth" since the from_config builder
  switched to `collect_orchestrator_tools`, and grep confirms no
  external callers remain. Clean deletion.

- Delete the hardcoded `ARCHETYPE_TOOLS` const in
  `tools/impl/agent/mod.rs`. The 4-entry table has been replaced by the
  orchestrator TOML's `subagents` list (which covers those 4 plus
  archivist plus the skills wildcard), and the re-export in
  `tools/mod.rs` is removed accordingly.

- Update `agents/orchestrator/agent.toml`: add the `subagents` field
  listing researcher / planner / code_executor / critic / archivist /
  { skills = "*" }. Keep `spawn_subagent` in `[tools] named` as an
  advanced fallback so power users can still spawn custom workspace-
  override agent ids that aren't in the declarative subagents list.

- Add `delegate_name = "..."` to the 5 archetype TOMLs so the
  orchestrator LLM sees natural tool names (`research`, `plan`,
  `run_code`, `review_code`, `archive_session`) rather than the
  `delegate_<agent_id>` fallback.

- Update `agent/harness/session/builder.rs` (line ~461) to call the new
  `collect_orchestrator_tools` signature. Looks up the orchestrator
  definition from the global registry; passes an empty integrations
  slice because the builder is synchronous and cannot await Composio's
  async fetch. The channel-dispatch path will populate integrations in
  a later commit — the CLI/REPL path ships without per-toolkit delegation
  tools, which is acceptable regression since CLI users still reach
  Composio via `composio_execute` and the retained `spawn_subagent`
  fallback.

Tests:

  * 5 new unit tests in `orchestrator_tools.rs` cover the baseline
    AgentId + Skills wildcard expansion, empty-integrations edge case,
    unknown-id graceful skip, non-delegating agent with empty subagents,
    and the slug sanitiser for tool-name-safe Composio toolkit names.
  * Runs clean alongside all existing agent-module tests (323 pass;
    one pre-existing Windows-path failure in `self_healing::tests::
    tool_maker_prompt_includes_command` is unrelated to this PR and
    fails identically on the upstream baseline).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(agent): plumb per-agent tool scoping through bus + tool loop (#525, #526)

Adds the parameter plumbing for agent-aware tool filtering without
changing any runtime behaviour. Every existing call site continues to
pass `None` / empty extras, so the LLM still sees the full unfiltered
registry — the actual routing logic that populates these fields lands
in commit 4b (dispatch.rs onboarding-flag → target_agent_id).

Why two parameters and not one filter:

  Tools in this codebase are `Box<dyn Tool>` — owned trait objects with
  no Clone impl, stored in a shared `Arc<Vec<Box<dyn Tool>>>`. We can't
  cheaply build a per-turn filtered subset of the global registry, and
  we can't mutate the Arc to remove entries. Two new parameters work
  around this without touching the global registry's lifetime model:

  * `visible_tool_names: Option<&HashSet<String>>` — whitelist filter
    applied at the iteration site inside `run_tool_call_loop`. When
    `Some(set)`, only tools whose `name()` is in the set contribute to
    the function-calling schema and are eligible for execution; every
    other tool in the combined registry is hidden from the model and
    rejected if the model emits a call for it. `None` preserves the
    legacy "everything visible" behaviour.

  * `extra_tools: &[Box<dyn Tool>]` — per-turn synthesised tools spliced
    alongside `tools_registry`. The dispatch path will use this to
    surface delegation tools (`research`, `delegate_gmail`, …) that are
    built fresh each turn from the active agent's `subagents` field and
    the current Composio integration list — tools that don't exist in
    the global startup-time registry because they depend on per-user
    runtime state. Empty slice for agents that don't delegate.

  Inside the loop, `tool_specs` is built from
  `tools_registry.iter().chain(extra_tools.iter()).filter(is_visible)`,
  and the tool-execution lookup uses the same chain + filter so the
  function-calling schema and the execution surface stay in sync.

Files touched in this commit:

  src/openhuman/agent/harness/tool_loop.rs
    - Add `visible_tool_names` and `extra_tools` parameters to
      `run_tool_call_loop`. Build `tool_specs` from chained iteration
      with the visibility filter applied. Replace the `find_tool` call
      at the execution site with an inline chain+filter lookup so
      hallucinated calls to filtered-out tools surface as "unknown
      tool" errors. Drop the now-unused `find_tool` import.
    - Update the legacy `agent_turn` wrapper to pass `None, &[]`,
      preserving its existing unfiltered behaviour.
    - Update all 9 in-file test sites to pass `None, &[]`.

  src/openhuman/agent/harness/tests.rs
    - Update all 3 `run_tool_call_loop` test sites to pass `None, &[]`.

  src/openhuman/agent/bus.rs
    - Add `target_agent_id: Option<String>`, `visible_tool_names:
      Option<HashSet<String>>`, and `extra_tools: Vec<Box<dyn Tool>>`
      fields to `AgentTurnRequest`, with rustdoc explaining each.
    - Destructure the new fields in the `agent.run_turn` handler;
      thread `visible_tool_names.as_ref()` and `&extra_tools` through
      to `run_tool_call_loop`. Augment the dispatch trace with
      target_agent / extra_tool_count / visible_tool_count /
      filter_active so production logs show whether scoping is active.
    - Update the in-test `test_request()` helper to populate the new
      fields with safe defaults.

  src/openhuman/agent/triage/evaluator.rs
    - Update the triage `AgentTurnRequest` initializer to set
      `target_agent_id = Some("trigger_triage")` (for tracing) with
      `visible_tool_names: None` + `extra_tools: Vec::new()` because
      the classifier intentionally runs against an empty registry and
      emits a structured JSON decision rather than calling tools.

  src/openhuman/channels/runtime/dispatch.rs
    - Update the channel-message `AgentTurnRequest` initializer to set
      the three new fields to safe defaults (`None` / `None` / empty
      vec). Commit 4b will replace these with the real onboarding-flag
      based routing.

  src/openhuman/tools/impl/agent/mod.rs
    - Bug fix: `dispatch_subagent` previously took `_skill_filter:
      Option<&str>` but discarded the value, hardcoding
      `SubagentRunOptions::skill_filter_override = None`. That meant
      `SkillDelegationTool::execute()` synthesising
      `dispatch_subagent("skills_agent", ..., Some("gmail"))` never
      actually narrowed `skills_agent`'s tool list — so even with the
      orchestrator's view scoped, the spawned `skills_agent` subagent
      would still see the full Composio catalog. Drop the underscore,
      propagate `skill_filter` into `skill_filter_override`, and add a
      tracing log line to make this path observable. This is the
      downstream half of the #526 leak that commit 3's orchestrator-
      side scoping alone wouldn't have caught.

Tests: 8/8 `tool_loop` tests pass, 3/3 harness `tests.rs` cases pass,
323/324 agent module tests pass overall (the one failure is the same
pre-existing Windows-path bug in `self_healing::tool_maker_prompt_
includes_command` that fails identically on the upstream baseline).
No existing test expectations were changed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(dispatch): route channel messages to welcome/orchestrator by onboarding flag (#525)

Wires the per-agent tool scoping plumbing from commit 4a into the
channel-message dispatch path. Each incoming channel message now picks
the active agent — `welcome` pre-onboarding, `orchestrator` post — based
on `Config::onboarding_completed`, loads the matching definition from
the global `AgentDefinitionRegistry`, synthesises any `delegate_*` tools
the agent declares in its `subagents` field, and passes everything
through to `agent.run_turn` on the bus.

This is the half of #525 that makes the welcome agent actually run for
new users — the welcome definition has existed since upstream PR #522
but had no caller; nothing in dispatch consulted the onboarding flag,
so every channel message ran through the same generic tool loop with
the full registry exposed.

Changes:

  src/openhuman/channels/runtime/dispatch.rs
    - New `AgentScoping` struct carrying the three new `AgentTurnRequest`
      fields (`target_agent_id`, `visible_tool_names`, `extra_tools`)
      plus an `unscoped()` constructor for safe-fallback paths.
    - New async `resolve_target_agent(channel)` helper:
      * fresh `Config::load_or_init().await` per turn (no cache — the
        loader reads from disk every call, verified at
        `config/schema/load.rs:409`, so the welcome→orchestrator
        handoff is observed on the next message after
        `complete_onboarding(complete)` flips the flag, with no need
        for an explicit handoff event);
      * picks `"welcome"` or `"orchestrator"` based on the flag and
        emits a structured `[dispatch::routing] selected target agent`
        info trace recording the choice + the flag value, satisfying
        the #525 acceptance criterion `"agent-selection logs clearly
        record why each agent was selected at onboarding boundaries"`;
      * looks up the definition in `AgentDefinitionRegistry::global()`,
        gracefully falling back to `AgentScoping::unscoped()` (= legacy
        behaviour, no filter, no extras) if the registry isn't
        initialised or the definition isn't found, so a routing miss
        never fails the user message;
      * for agents with a non-empty `subagents` field, awaits
        `composio::fetch_connected_integrations(&config)` and runs
        `orchestrator_tools::collect_orchestrator_tools` to materialise
        per-turn delegation tools (`research`, `plan`, `delegate_gmail`,
        …). Agents with empty `subagents` get an empty extras vec.
    - New `build_visible_tool_set(definition, &extra_tools)` helper that
      returns `Some(union)` for `ToolScope::Named` agents (their named
      list ∪ the names of the synthesised delegation tools) and `None`
      for `ToolScope::Wildcard` agents to preserve the unfiltered
      semantics — so agents like `skills_agent` and `morning_briefing`
      that already work via `wildcard + category_filter` keep their
      existing behaviour without this layer interfering.
    - `process_channel_message` calls `resolve_target_agent` once per
      turn, drops the placeholder defaults from commit 4a, and feeds
      the real `target_agent_id`/`visible_tool_names`/`extra_tools`
      into `AgentTurnRequest`.
    - New imports: `AgentDefinition`, `AgentDefinitionRegistry`,
      `ToolScope`, `Config`, `fetch_connected_integrations`,
      `orchestrator_tools`, `Tool`, `HashSet`.

End-to-end behaviour after this commit:

  1. New user, `onboarding_completed=false`: dispatch picks `welcome`,
     loads its 2-tool TOML scope, builds `visible_tool_names =
     {complete_onboarding, memory_recall}`, no extras, hands off to
     the bus. Bus handler applies the filter → welcome's LLM sees
     exactly 2 tools.
  2. Welcome agent guides the user through setup, eventually calls
     `complete_onboarding(action="complete")` → flag persists to disk
     via `config.save()`.
  3. Next user message: dispatch reads the flag fresh, picks
     `orchestrator`, fetches connected Composio integrations, expands
     `subagents = ["researcher", "planner", "code_executor", "critic",
     "archivist", { skills = "*" }]` into delegate_research /
     delegate_plan / delegate_run_code / delegate_review_code /
     delegate_archive_session + one delegate_<toolkit> per connected
     integration. visible_tool_names is the union with the 4 direct
     tools from orchestrator's `[tools] named` list. LLM sees the
     scoped delegation surface, not the full 1000+ Composio catalog.

#526's runtime leak is now fixed end-to-end: the orchestrator's LLM
prompt only contains the tools its TOML allows, and the
SkillDelegationTool path narrows skills_agent to a single toolkit via
the `skill_filter` propagation fix from commit 4a. No agent at any
layer sees more than its definition declares.

Tests: 599/599 channel module tests pass — including
`runtime_dispatch::dispatch_routes_through_agent_run_turn_bus_handler`
and the telegram integration variant, which exercise the full bus
roundtrip with the new fields populated. No existing assertions were
modified.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(debug_dump): apply orchestrator definition filter to main dump (#526)

Replaces the explicit `empty_filter: HashSet::new()` in
`render_main_agent_dump` with a real visibility whitelist derived from
the orchestrator's `AgentDefinition`. The "main" dump path now mirrors
the runtime dispatch path from commits 4a/4b — same definition, same
delegation tool synthesis, same filter — so `openhuman agent dump-prompt
main` shows what the LLM actually sees in production instead of the
unfiltered global registry.

Before this commit, `dump-prompt main` always rendered every tool in
the registry regardless of which agent was supposed to run, which is
exactly the symptom #526 reports: "agent prompt tool scopes leak full
GitHub tool catalog". The runtime fix in commit 4b stops the leak in
production but the dump path was the user's primary observability tool
for inspecting prompts, so leaving it unscoped would mask future
regressions and confuse debug sessions.

Changes in `src/openhuman/context/debug_dump.rs`:

  * `dump_agent_prompt` now loads `AgentDefinitionRegistry` once at the
    top (previously only loaded inside the sub-agent branch). When the
    request is for "main" or "orchestrator_main", it looks up the
    "orchestrator" definition from the registry and passes it into
    `render_main_agent_dump` along with the registry itself. Missing
    orchestrator entry → structured error listing known agents
    instead of silently rendering an unfiltered prompt.

  * `render_main_agent_dump` signature gains `registry:
    &AgentDefinitionRegistry` and `orchestrator_def: &AgentDefinition`.
    Inside, it:
      - calls `collect_orchestrator_tools(orchestrator_def, registry,
        connected_integrations)` to synthesise the same per-turn
        delegation tools (`research`, `plan`, `delegate_<toolkit>`,
        …) that dispatch generates;
      - extends `prompt_tools` with the synthesised extras so they
        contribute to the rendered tool catalogue;
      - builds `visible_filter: HashSet<String>` from
        `orchestrator_def.tools` (the `[tools] named` list) ∪ the
        names of the synthesised extras, falling back to an empty
        HashSet when the orchestrator definition uses
        `ToolScope::Wildcard` (which the prompt builder treats as "no
        filter, every tool visible") so dump consumers that supply a
        wildcard orchestrator (custom workspace overrides, tests)
        retain the legacy unscoped behaviour;
      - replaces `visible_tool_names: &empty_filter` in the
        `PromptContext` with `&visible_filter`;
      - filters the returned `tool_names` and `skill_tool_count` by
        the same predicate so the `DumpedPrompt` summary fields
        match what the prompt text actually contains.

Tests:

  * Replaces the previous `render_main_agent_dump_includes_tool_
    instructions_and_skill_count` test with two more focused cases:

    1. `render_main_agent_dump_wildcard_scope_shows_full_tool_set` —
       regression guard for the legacy wildcard path. Builds a
       wildcard-scoped orchestrator definition, asserts every tool
       from `tools_vec` survives, and checks the standard system-
       prompt skeleton (Tools section, Tool Use Protocol, cache
       boundary) still renders.

    2. `render_main_agent_dump_named_scope_filters_to_whitelist` —
       the #526 regression guard. Builds an orchestrator with
       `ToolScope::Named(["query_memory", "ask_user_clarification"])`
       and a `tools_vec` containing `shell`, `query_memory`, and
       `GMAIL_SEND_EMAIL`. Asserts the dump's `tool_names` is exactly
       `["query_memory"]` — `shell` and `GMAIL_SEND_EMAIL` are in the
       global registry but NOT in the whitelist, so they MUST be
       excluded. If a future change reintroduces the unfiltered
       behaviour this test fails immediately.

  * Adds two test helpers: `wildcard_orchestrator_def()` builds a
    minimal orchestrator definition with all `omit_*` flags set and
    `ToolScope::Wildcard`, and `registry_with_orchestrator(orch)`
    wraps it in an `AgentDefinitionRegistry` so the tests can call
    `render_main_agent_dump` without going through the full TOML
    loader.

11/11 debug_dump tests pass. The two new guards plus the 9 existing
sub-agent / filter / composio-stub tests all run clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test(dispatch): unit tests for build_visible_tool_set + cargo fmt cleanup (#525, #526)

Adds focused unit tests for the per-agent scoping helper landed in
commit 4b and runs `cargo fmt` across the files touched by this PR.

Why scoping unit tests, not full integration tests:

  `resolve_target_agent` is async and reads `Config::load_or_init().await`
  which does a real disk read every call (no cache, verified at
  `config/schema/load.rs:409`). Mocking that requires either spinning up
  a full workspace under a temp dir with a config.toml containing the
  right `onboarding_completed` value, or adding a test-only injection
  point on the public Config API. Both are tractable but invasive
  enough to belong in their own follow-up PR. The end-to-end dispatch
  path is already covered by the existing channel integration tests
  (`dispatch_routes_through_agent_run_turn_bus_handler` etc.) which
  exercise the full bus roundtrip with the new fields populated, and
  which still pass after the new resolver landed (it gracefully falls
  back to `AgentScoping::unscoped()` when no orchestrator definition
  is registered in the test environment).

  Pure-function unit tests for `build_visible_tool_set` cover the
  branching logic that does the actual scoping work: how the named
  whitelist + extras union is built, how Wildcard scope is preserved,
  how duplicates are de-duplicated, etc. That's the part most likely
  to drift in future changes, so it's the part most worth fencing
  with focused tests.

Tests added (all in `src/openhuman/channels/runtime/dispatch.rs` under
the new `scoping_tests` module):

  * `wildcard_scope_yields_none_filter` — `ToolScope::Wildcard` must
    produce `None` regardless of whether extras are present, so
    skills_agent / morning_briefing keep their full skill-category
    catalogue.

  * `named_scope_without_extras_returns_named_only` — the welcome
    agent's path: 2 named tools, no delegation, exactly 2 entries in
    the visibility whitelist.

  * `named_scope_with_extras_returns_union` — the orchestrator's path:
    3 direct named tools + 3 synthesised extras (research,
    delegate_gmail, delegate_github) → 6 entries.

  * `empty_named_with_extras_returns_extras_only` — guards a future
    "delegation-only" agent layout where the agent has no direct tools
    of its own, just spawns subagents.

  * `empty_named_with_no_extras_returns_empty_set` — guards the
    distinction between `None` (no filter, all visible) and
    `Some(empty)` (filter active, nothing matches). Important because
    the prompt loop's `is_visible` check treats them differently.

  * `duplicate_names_across_named_and_extras_are_deduplicated` — the
    HashSet handles collisions automatically, but the test pins that
    behaviour so a future migration to `Vec<String>` (which would
    silently double-count) gets caught.

  * `agent_scoping_unscoped_has_no_filter_or_extras` — pins the
    safe-fallback constructor's contract. Used when the registry is
    uninitialised or the target agent is missing — every field must
    default to "no scoping" so the channel turn falls back to legacy
    unfiltered behaviour rather than crashing.

Plus `cargo fmt` run across the 6 files modified by this PR. No
behavioural changes.

Final test status across all commits 2-6 in this PR:

  * agent::harness::definition: 10/10  (4 new for Subagents schema)
  * agent::harness::tool_loop: 8/8 
  * agent::harness::tests: 3/3 
  * tools::orchestrator_tools: 5/5  (5 new)
  * channels::*: 599/599  (incl. dispatch integration)
  * channels::runtime::dispatch::scoping_tests: 7/7  (7 new)
  * context::debug_dump: 11/11  (1 replaced + 1 new)
  * Total agent module: 323/324 (one pre-existing Windows path
    failure in `self_healing::tool_maker_prompt_includes_command`
    confirmed identical against upstream/main baseline)

Pre-existing Windows-environment test failures NOT caused by this PR
and out of scope (all confirmed identical on upstream baseline; CI on
Linux is unaffected):

  * self_healing::tool_maker_prompt_includes_command (PathBuf separator)
  * cron::scheduler::run_job_command_success / _failure (Unix shell)
  * composio::trigger_history::archives_triggers_in_daily_jsonl... (path)
  * local_ai::paths::target_paths_preserve_absolute_overrides (path)
  * security::policy::checklist_root_path_blocked (POSIX absolute)
  * security::policy::checklist_workspace_only_blocks_all_absolute (POSIX)
  * tools::implementations::browser::screenshot::screenshot_command_
    contains_output_path (browser binary lookup)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(welcome): upgrade bare-install nudge with concrete integration pitches (#525)

Follow-up to #522 that addresses a rough UX edge in the welcome agent
prompt: users who arrive with only an API key configured (no
channels, no Composio integrations, no web search / browser / local
AI) were getting a "gentle suggestion" to connect something without
any concrete picture of what they'd actually unlock. The welcome
agent would finish onboarding, hand off to orchestrator, and leave
the user staring at a functional-but-empty assistant with no
roadmap for how to make it useful.

This commit rewrites the prompt to handle sparse setups explicitly.
No Rust changes — this is prompt.md only, picked up at build time
via the existing `include_str!` loader in `agents/mod.rs`.

Changes to `src/openhuman/agent/agents/welcome/prompt.md`:

  * Step 2's "point out what's missing" sub-point is rewritten from
    a single "gently suggest" line into a four-case decision tree
    keyed off `check_status` state:
      - No API key → critical, block completion.
      - Integrations yes, channels no → note the Tauri-only reach
        limitation, suggest a messaging platform.
      - Channels yes, integrations no → degraded assistant, nudge
        toward Composio.
      - Nothing beyond the API key → the "bare install" case, gets
        the new Step 2.5 treatment.

  * New **Step 2.5: Handling a bare install** section added after
    Step 2. Spells out what the user DOES have (sandboxed reasoning
    + coding assistant with memory), what they're MISSING (any
    external action), and how to structure the message: state the
    current capability honestly, pitch 2-3 specific integrations
    with concrete example prompts, point to Settings → Integrations
    / Channels, and leave room for the user to opt into the
    coding-only experience if that's what they actually want. For
    bare-install users the word budget stretches to 250-400 words
    (up from 200-350) so the concrete pitches and example prompts
    actually fit without cramming.

  * New **Integration capability reference** section giving the LLM
    a menu it can draw from when pitching integrations. Each entry
    is a one-line "connect X → I can Y" with a concrete example
    prompt the user could send next:
      - Gmail: "Summarise the most important emails that came in
        overnight and flag anything that needs a reply today."
      - Google Calendar: "What's on my calendar tomorrow, and do I
        have a 30-minute gap before 2pm?"
      - GitHub: "List open issues on my main project tagged 'bug'
        and summarise which ones look newest or most urgent."
      - Notion: "Pull up my 'Ideas' Notion database and show me
        the three newest entries."
      - Slack / Discord / Linear / Jira / etc. with similar shapes.

    Plus a sub-section for messaging platforms (Telegram / Discord /
    Slack / iMessage / WhatsApp / Signal / web-fallback) that
    clarifies which each is best for, and a sub-section for the
    other capabilities (web search, browser automation, HTTP
    requests, local AI) that explains what breaks without them.

    The LLM is told NOT to list everything — just pick 2-3 most
    likely to matter, defaulting to Gmail + GitHub + one of
    {Calendar, Notion} as the top-3 pitch when no profile context
    is available.

  * Tone guidelines updated to document the stretched word budget
    for bare installs (200-350 for configured users, 250-400 for
    bare installs).

  * "What NOT to do" list updated:
      - Explicitly allows product-tour-style listing ONLY in the
        bare-install case (Step 2.5), forbids it elsewhere.
      - Clarifies that describing what WOULD unlock with integration
        X is fine and encouraged; claiming a capability the user
        doesn't have is still forbidden.
      - Adds a new "Don't gloss over a bare install" entry that
        pins the rule: API-key-only users get concrete pitches and
        example prompts, not vague suggestions.

Scope note: this commit does NOT change the completion logic.
`complete_onboarding(complete)` still accepts API-key-only as the
minimum bar — that's a separate design question for the maintainer
about whether zero-integration users should be gatekept. This
change improves what the welcome agent SAYS to those users, not
whether they're allowed to proceed.

Tests: all 14 `agent::agents::tests` pass (including
`welcome_has_onboarding_and_memory_tools` which validates the
welcome agent's declarative shape is unchanged). The prompt.md
edit is pure content — no schema changes, no tool additions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(web-channel): route Tauri in-app chat to welcome/orchestrator + Windows fsync fix (#525)

Closes the web-channel-side half of the #525 welcome agent routing.
Also bundles a small pre-existing Windows compatibility fix for
`app_state::ops::sync_parent_dir` that was blocking end-to-end testing
of this branch on Windows.

## Web channel routing (primary change)

Commit 4b (274b50ed) wired the welcome-vs-orchestrator routing into
`channels/runtime/dispatch.rs::process_channel_message` — the handler
for inbound messages on external channels (Telegram, Discord, Slack,
iMessage, etc.). That path reads `config.onboarding_completed` and
selects the right agent via the bus.

End-to-end testing on the Tauri desktop app revealed a gap: the
in-app chat window does NOT route through `process_channel_message`.
It uses its own JSON-RPC method (`openhuman.channel_web_chat` →
`start_chat` → `run_chat_task` → `build_session_agent` →
`Agent::from_config`) which was hardcoded to build a generic
main-agent every turn. So a new user typing in the Tauri app saw the
orchestrator immediately, never the welcome agent.

This commit closes that gap without adding a third code path:

### `src/openhuman/agent/harness/session/builder.rs`

* Split the existing `Agent::from_config` into a thin wrapper + a
  new `Agent::from_config_for_agent(config, agent_id)`. The wrapper
  calls the new function with `agent_id = "orchestrator"`, preserving
  the legacy behaviour byte-identically for all existing callers
  (CLI, REPL, tests, every call site that doesn't care which agent
  they get).
* `from_config_for_agent` looks up `agent_id` in
  `AgentDefinitionRegistry::global()` up front; unknown ids fail
  fast with a clear error. Missing orchestrator is tolerated as a
  legacy / pre-startup fallback so existing tests that run without
  a populated registry continue to work.
* When a target definition is resolved:
    - `prompt_builder` uses `SystemPromptBuilder::for_subagent` with
      the agent's `system_prompt` body (read from the registry,
      which already has it inlined via `include_str!` from
      `agents/*/prompt.md` at crate-build time) and the three
      `omit_*` flags from its TOML.
    - `visible_tool_names` is built from the agent's
      `ToolScope::Named` list, unioned with the names of any
      synthesised delegation tools produced by
      `collect_orchestrator_tools` (for agents that declare
      `subagents = [...]`).
    - `ToolScope::Wildcard` yields an empty filter, matching the
      legacy "no filter, all visible" semantics.
    - `temperature` comes from the agent's TOML (e.g. welcome is
      0.7, orchestrator is 0.4) instead of
      `config.default_temperature`.
* Legacy behaviour for plain `from_config(config)` — which passes
  `agent_id = "orchestrator"` — is preserved: the prompt builder
  continues to use `SystemPromptBuilder::with_defaults()`, temperature
  comes from `config.default_temperature`, and the orchestrator's
  `subagents` list drives delegation tool synthesis exactly as
  before. No test expectations changed.

### `src/openhuman/channels/providers/web.rs`

* `build_session_agent` now reads `config.onboarding_completed` and
  picks `"welcome"` or `"orchestrator"` as the target agent id, then
  calls `Agent::from_config_for_agent`. Structured info log records
  the routing decision + the flag state for observability, matching
  the `[dispatch::routing]` trace pattern from Commit 4b.
* `config.onboarding_completed` is read fresh every turn because
  `run_chat_task` loads the config via
  `config_rpc::load_config_with_timeout`, which reads from disk on
  every call (no in-process cache). Welcome → orchestrator handoff
  therefore happens automatically on the next chat message after
  `complete_onboarding(complete)` flips the flag, with no explicit
  event or notification plumbing.
* `set_event_context` call is unchanged — the event context is a
  (session_id, channel_name) pair for telemetry, not the agent id.

## Windows fsync fix (bundled)

### `src/openhuman/app_state/ops.rs`

`sync_parent_dir` was calling `File::open(parent).and_then(|dir|
dir.sync_all())` on the save path for `app_state.json`. On Windows,
opening a directory as a regular file requires
`FILE_FLAG_BACKUP_SEMANTICS` which `std::fs::File::open` does not
set, so the call fails with "Access is denied. (os error 5)".

Mirrored the existing `#[cfg(unix)]` guard already in
`config/schema/load.rs::sync_directory`. On non-Unix the function is
a no-op and returns `Ok(())`. Durability on Windows is provided by
`NamedTempFile::persist`'s atomic `MoveFileEx`, which is sufficient
for config files.

This is a pre-existing upstream bug, not caused by this PR, but it
blocks end-to-end testing of any code path that touches
`app_state::save_stored_app_state_unlocked` on Windows — which
includes the web channel's agent_server_status RPC. Fixing it here
so the #525 routing can actually be tested on the developer's
machine, and the fix is tiny and self-contained. The `File` import
on line 1 is also gated behind `#[cfg(unix)]` to avoid an
unused-import warning on Windows.

## Tests

All 35 `agent::harness::session` tests pass, including the
transcript round-trip + session queue + runtime tests that exercise
the builder path most heavily. No test expectations were modified;
the refactor is a pure delegation chain (`from_config` → new inner
method → existing builder).

CLI dump-prompt testing from the earlier session still validates:
  * `openhuman agent dump-prompt welcome` → 2 tools, Step 2.5
    bare-install prompt embedded
  * `openhuman agent dump-prompt main` → 6 tools, zero skill leakage
  * `openhuman agent dump-prompt main --stub-composio` → still 6
    tools, composio meta-tools correctly filtered out

End-to-end Tauri testing (next step after this commit) will exercise
the new `build_session_agent` branch live — user types `hi`, web
channel routes to welcome, welcome replies with the updated
bare-install prompt from commit 990a7264.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(config): split chat_onboarding_completed from React UI onboarding flag (#525)

End-to-end testing on the Tauri desktop app revealed that the welcome
agent could never run from the in-app chat pane — even with all of
Commits 4b/8 in place — because the React layer's
`OnboardingOverlay.tsx` renders a full-screen wizard whenever
`onboarding_completed = false` and gates the chat pane behind it. By
the time a user can type a chat message the React wizard has already
flipped `onboarding_completed = true` (via `OnboardingOverlay::handleDone`
or the `Onboarding.tsx` wizard's completion handler), so the welcome
agent's routing condition is never satisfied on the Tauri surface.

This commit fixes the architectural conflict by splitting the single
`onboarding_completed` flag into two orthogonal flags:

* **`onboarding_completed`** — unchanged semantics. Tracks whether the
  React UI wizard has been completed/dismissed. Continues to be set
  by `OnboardingOverlay.tsx::handleDone` and `Onboarding.tsx` via the
  existing `config.set_onboarding_completed` JSON-RPC method. Used
  exclusively to gate whether the React layer renders the wizard.

* **`chat_onboarding_completed`** — NEW. Tracks whether the welcome
  agent's chat-based greeting flow has run. Set exclusively by the
  welcome agent itself via `complete_onboarding(action="complete")`.
  Used by both the Tauri web channel
  (`channels::providers::web::build_session_agent`) and the external
  channel dispatch path (`channels::runtime::dispatch::resolve_target_agent`)
  to decide whether to route to welcome or orchestrator.

The two flags are intentionally orthogonal:

  * A Tauri desktop user completes the React wizard → only
    `onboarding_completed` flips → wizard disappears → user types `hi`
    → welcome agent runs (because `chat_onboarding_completed` is still
    false) → welcome calls `complete_onboarding(complete)` →
    `chat_onboarding_completed` flips → next chat turn routes to
    orchestrator.

  * A Telegram/Discord user (no React wizard exists) sends a message
    → external channel dispatch checks `chat_onboarding_completed` →
    routes to welcome → welcome runs → flips the flag → next inbound
    message routes to orchestrator.

Both paths give every user the chat welcome experience, regardless of
which surface they came in through, and without requiring the React
wizard to be removed or restructured.

## Files changed

`src/openhuman/config/schema/types.rs`
* Add `chat_onboarding_completed: bool` field with `#[serde(default)]`
  for backward compat — existing `config.toml` files that don't have
  the field default to `false`, which means existing users will see
  the welcome agent on their next chat turn (correct behaviour, the
  welcome agent is idempotent).
* Default impl initializes the new field to `false`.
* Extensive rustdoc on both flags explaining the orthogonal split,
  why two flags exist, and which code paths gate on which.

`src/openhuman/tools/impl/agent/complete_onboarding.rs`
* `check_status` now reports BOTH flags side-by-side so the welcome
  agent's LLM can see whether the React wizard has run AND whether
  the chat welcome itself has run. Old single "Onboarding completed"
  line replaced with two lines: "UI onboarding wizard completed" and
  "Chat welcome flow completed".
* `complete()` now flips `chat_onboarding_completed`, NOT
  `onboarding_completed`. The React UI's flag is left untouched —
  that's owned by the React layer. Idempotency guard updated to
  check the chat flag.
* Rustdoc on `complete()` explains the orthogonal-flags rationale
  for future readers.

`src/openhuman/channels/providers/web.rs::build_session_agent`
* Reads `effective.chat_onboarding_completed` instead of
  `effective.onboarding_completed` for the welcome-vs-orchestrator
  decision.
* Log line now includes BOTH flags so observability captures the
  full picture (e.g. `chat_onboarding_completed=false,
  ui_onboarding_completed=true` is the expected steady state for a
  Tauri user who completed the React wizard but hasn't typed yet).

`src/openhuman/channels/runtime/dispatch.rs::resolve_target_agent`
* Same flag swap for the external-channel path.
* `[dispatch::routing] selected target agent` info trace also
  reports both flags.
* Function-level rustdoc updated with the new semantics.

## Backward compatibility

* Existing `config.toml` files: `chat_onboarding_completed` defaults
  to `false` via `#[serde(default)]`. Means existing users get a
  welcome message on their next chat turn — this is the desired
  behaviour, not a regression.
* React layer: untouched. The `config.set_onboarding_completed`
  JSON-RPC method continues to write the same field; the new flag is
  not exposed to React at all.
* External callers of `complete_onboarding(complete)`: they now flip
  a different flag. This may matter for callers who were depending
  on the flag flip side effect; a quick grep shows
  `tools/impl/agent/complete_onboarding.rs` is the only caller and
  the rest of the codebase reads `onboarding_completed` for UI
  purposes (snapshots, app state) and `chat_onboarding_completed`
  for routing — the split is clean.

## Tests

399/400 tools tests pass. The single failure
(`tools::impl::browser::screenshot::screenshot_command_contains_output_path`)
is a pre-existing Windows-environment bug that fails identically on
`upstream/main` baseline and is unrelated to this PR. No test
expectations were modified.

## Next step

Restage sidecar, relaunch Tauri, click through the React wizard once
to dismiss it, then type `hi` in the chat pane. This time
`build_session_agent` should read `chat_onboarding_completed=false`
and route to welcome, even though `onboarding_completed=true` was set
by the React layer.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(welcome): force tool-call-first iteration to stop greeting fallback (#525)

End-to-end testing on the Tauri desktop app revealed that the welcome
agent — even with all routing/scoping commits in place and the
correct prompt embedded — was producing 1-line greetings like
"Hey! What's up?" (15 chars, single iteration, zero tool calls)
instead of running its workflow. The user typed `hihi`, the LLM saw
the welcome agent's full ~14KB persona prompt, and chose to ignore
every workflow step in favour of the chatbot fallback behaviour.

Diagnosis: the previous prompt was descriptive ("Call check_status to
get a snapshot...") rather than imperative. Combined with a "Concise"
tone guideline and a low-information user input ("hihi"), the model
under tension between "be warm and concise" and "follow the workflow"
collapsed to "tiny greeting" — which is the chatbot training-data
default for short user messages.

This commit makes the workflow non-negotiable by:

## 1. New "MANDATORY FIRST ACTION" preamble at the top of the prompt

Inserted as a blockquote between the role description and "Your
workflow" so the model encounters it before any workflow steps. The
preamble:

* States explicitly that the **first thing emitted on every turn must
  be a tool call** to `complete_onboarding(check_status)` — before
  any user-facing text, before any greeting, before any thinking out
  loud.
* States the user's input is **irrelevant** to this rule: "hi",
  "hello", emoji, nothing — the first iteration always emits the
  same thing, a check_status tool call.
* Explains *why* the rule exists (without a status snapshot the
  agent has nothing to personalise on, and any blind greeting
  defeats the welcome agent's one job).
* Shows three concrete  wrong examples (generic chitchat reply,
  greeting-then-tool, refusing the tool because the user said hi)
  and one  correct example (first iteration emits ONLY the tool
  call, message comes in iteration 2).
* Closes with a stop-and-correct rule: "If you find yourself about
  to write any text in your first iteration, STOP. Emit the tool
  call instead."

## 2. Strengthened "Your workflow / Step 1" heading

Renamed to "Step 1: Check setup status (ALWAYS — see Mandatory First
Action above)" so the cross-reference is unmissable. Body explicitly
says "In your **first iteration**, ... No text. No greeting. Just
the tool call. ... You will use this report to write the actual
welcome message in your second iteration."

## 3. Length is non-negotiable (rewritten "Concise" tone guideline)

The previous "Concise — but scale with the situation" framing was the
exact phrase the model latched onto when producing 15 chars. Rewritten
to "Length is non-negotiable" and adds:

* "A 1-2 sentence greeting is a failure, not a 'concise' success."
* Explicit "if you ever produce a message under 100 words, stop and
  try again — you've almost certainly skipped a required element."
* A "concise vs. terse" callout distinguishing "no wasted words in a
  message that does its full job" from "skip the job entirely."

## 4. New "What NOT to do" entries

Three new bullets target the exact failure modes observed:

* The existing "Don't skip check_status" entry is upgraded with
  "**This is the single most common failure mode**" and a pointer
  back to the mandatory-first-action preamble.
* New "Don't reply with a 1-line greeting" entry forbids the chatbot
  fallback explicitly and sets a 100-word floor.
* New "Don't treat 'hi' / 'hello' / short greetings as a signal to
  be brief" entry explicitly disconnects user input length from
  agent output length, since "hi" is the most common opening and
  the user typing it needs the FULL welcome experience.

## What this does NOT change

* No Rust code changes. `prompt.md` is `include_str!`'d into the
  binary at crate-build time via `agents/mod.rs`, so the next sidecar
  rebuild picks up the new content automatically.
* No agent definition changes (`agent.toml` untouched). The welcome
  agent still has 2 tools (`complete_onboarding` + `memory_recall`),
  `temperature = 0.7`, `max_iterations = 6`, `omit_*` flags
  unchanged.
* No model hint change. Still `"agentic"`. If the new prompt language
  alone isn't enough to push the model over the workflow-following
  threshold, a follow-up commit can bump to `"reasoning"` for
  stronger instruction-following.
* The integration capability reference, Step 2.5 bare-install
  handling, subscription/referral flow, and handoff sections are
  unchanged from commit 990a7264. Those were never the problem —
  the problem was the model never reaching them because it skipped
  Step 1 entirely.

## Tests

14/14 `agent::agents::tests` pass, including
`welcome_has_onboarding_and_memory_tools` and `every_builtin_has_a_
prompt_body`. The structural shape of the welcome agent definition
is byte-identical; only the embedded prompt body changed.

## Verification plan

After restaging the sidecar and relaunching Tauri, type `hi` in the
chat pane. Expected behaviour:

1. `[web-channel] routing chat turn to 'welcome'` (already validated
   in commit 56d95e2c).
2. `[agent::builder] building session agent id=welcome` (already
   validated).
3. **`[agent_loop] iteration start i=1`** with a `check_status` tool
   call as the first emission — this is the new behaviour we're
   verifying.
4. `[agent_loop] iteration start i=2` after the tool returns, with
   the actual welcome message — should be 100-400 words covering all
   required elements.
5. If everything's in place, `complete_onboarding(complete)` call in
   the same turn or a later iteration → `chat_onboarding_completed`
   flips to `true` → next chat turn routes to orchestrator.

If iteration 1 still produces text instead of a tool call, the next
escalation is bumping the model hint from `"agentic"` to
`"reasoning"` in `agent.toml`.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(complete_onboarding): check session JWT in addition to legacy api_key (#525)

Discovered during end-to-end Tauri testing that the welcome agent
refused to call `complete_onboarding(complete)` for a fully
authenticated user because `check_status` reported "API key:
**missing**" — even though the user had completed the desktop OAuth
flow and a valid encrypted JWT was sitting in `auth-profiles.json`.

## Root cause

`check_status` was reading only `config.api_key` (an `Option<String>`
on the Config struct), which is a **legacy free-form provider key
field**. It is not where the desktop OAuth flow stores its
credentials. The actual openhuman backend session JWT lives in
`<openhuman_dir>/auth-profiles.json` under the `app-session:default`
profile, encrypted via the SecretStore using `<openhuman_dir>/.secret_key`,
and is read at every inference RPC via
`crate::api::jwt::get_session_token(config)` (which delegates to
`AuthService::from_config(config).get_profile(APP_SESSION_PROVIDER, None)`).

So a user who:
1. Completed the desktop deep-link OAuth flow (the only supported
   way to get an account), and
2. Has a fully populated `auth-profiles.json` with a valid encrypted
   `app-session:default` token,

was reported by `check_status` as "API key missing" because their
JWT lives in the auth profile store, not in `config.api_key`. The
welcome agent then dutifully refused to call `complete_onboarding(
complete)` per its own prompt rule ("If critical setup is missing,
do not complete onboarding"), and re-ran on every chat turn forever
even though there was nothing to fix.

This is a **pre-existing upstream bug** dating from PR #522 (the
welcome agent's introduction). It was written before, or in parallel
with, the auth-profile refactor that moved session credentials out of
`config.api_key` into the dedicated profile store. The check was
never updated to reflect the new auth model.

## Fix

`check_status` now checks BOTH sources:

```rust
let has_legacy_api_key = config.api_key.as_ref().map_or(false, |k| !k.is_empty());
let has_session_jwt = crate::api::jwt::get_session_token(&config)
    .ok()
    .flatten()
    .is_some_and(|t| !t.is_empty());
let is_authenticated = has_legacy_api_key || has_session_jwt;
```

The status report now shows:
* `Authentication: configured ✓ (session token from desktop login)` —
  when the user has gone through the OAuth flow (the typical case);
* `Authentication: configured ✓ (legacy api_key)` — when the user
  has set the legacy `config.api_key` field directly (CI / dev
  setups);
* `Authentication: **missing** — log in via the desktop app or set
  `api_key` in config to enable inference` — when neither source has
  a credential.

The line label changed from "API key" to "Authentication" because
"API key" was misleading for both states (it isn't really the user's
API key; it's the openhuman backend session JWT).

## What this unblocks

After this fix, a Tauri user who:
1. Logs in once via the desktop OAuth flow → JWT lands in
   `auth-profiles.json`,
2. Types `hi` in the chat pane → welcome agent runs,
3. Welcome calls `check_status` → reports "Authentication: configured ✓",
4. Welcome calls `complete_onboarding(complete)` → flips
   `chat_onboarding_completed` from false to true,
5. Next chat turn → routes to orchestrator.

Without this fix, step 4 never happens because welcome's prompt
explicitly forbids completing without an API key, so the user gets
the welcome message on every single turn forever.

## Files touched

`src/openhuman/tools/impl/agent/complete_onboarding.rs`
* `check_status` reads both `config.api_key` and
  `crate::api::jwt::get_session_token(&config)`.
* Status line renamed from "API key" to "Authentication" with a
  more informative message.
* Long inline comment documenting the two auth sources, why the
  check needs to consult both, and what bug was being fixed.

## Tests

360/361 tools tests pass. Only failure is the pre-existing Windows
browser-screenshot bug (`tools::impl::browser::screenshot::
screenshot_command_contains_output_path`) which fails identically on
upstream/main and is unrelated to this PR. The
`complete_onboarding::tool_metadata` test still passes — it only
checks the tool schema, not the runtime behaviour.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(welcome): force complete_onboarding(complete) in iteration 2 (#525)

Live Tauri test after Commit 11 (auth-aware check_status) showed the
welcome agent completing iteration 1 with the correct check_status
tool call AND iteration 2 with a beautiful 1586-char welcome message
— but NOT calling complete_onboarding(action="complete") to flip
chat_onboarding_completed. So the user gets the welcome message and
then routes to welcome AGAIN on every subsequent chat turn forever,
because the prompt's Step 3 is descriptive rather than imperative.

Same failure pattern as the iteration-1 chitchat fallback that
Commit 10 fixed — the LLM follows the path of least resistance. If
the prompt says "call X to finalize" without making it mandatory,
the model will write the welcome text, see that it's done its job,
and stop without emitting the second tool call.

Fix: rewrite Step 3 with the same "MANDATORY" preamble pattern that
worked for the iteration-1 fix. Specifically:

* Renamed Step 3 to "Complete onboarding — MANDATORY in iteration 2
  (when authenticated)" so the cross-reference is unmissable.
* Added a blockquoted "MANDATORY SECOND TOOL CALL" preamble at the
  top of Step 3 that:
  - States the rule: when check_status reports "Authentication:
    configured ✓", iteration 2 MUST contain BOTH the welcome message
    text AND a complete_onboarding(action="complete") tool call.
  - Explains the consequence: without the tool call, the user is
    routed to welcome forever, which is a hard failure.
  - Defines the iteration 2 output structure as a 2-element list:
    welcome text + tool call.
  - Shows / examples (welcome message without tool call vs. with
    tool call) so the model has a concrete pattern to match.
  - Documents the single exception: only when authentication is
    missing should complete() be skipped, and explicitly says
    "missing channels, missing Composio integrations, missing local
    AI — none of those block completion." Auth is the only blocker.
* Replaced the descriptive bullet list with a stricter "Decision
  rule for iteration 2" that pairs each check_status outcome with
  exactly what the agent must emit.

## What stays the same

* No code changes — the existing complete_onboarding tool already
  handles the action="complete" call correctly (Commit 11 made it
  flip the right flag and check the right auth source).
* No agent.toml changes — welcome still has 2 tools, max_iterations=6,
  temperature=0.7. Plenty of headroom for the 2-iteration flow.
* The decision logic itself is unchanged — auth → complete, no auth
  → no complete. Just the framing.

## Test plan

Restage sidecar, relaunch Tauri, type `hi`. Expected:

```
i=1: complete_onboarding(check_status) → "Authentication: configured ✓"
i=2: 1500-2000 char welcome message + complete_onboarding(action="complete")
       ↓
     [complete_onboarding] chat welcome flow marked complete, proactive agents seeded
       ↓
     chat_onboarding_completed flips false → true
```

Then type a follow-up message → expect:

```
[web-channel] route → orchestrator (chat_onboarding_completed=true)
```

That's the welcome → orchestrator handoff — the actual goal of #525.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(web-channel): include target_agent_id in THREAD_SESSIONS cache key (#525)

Final bug discovered during E2E testing: after Commit 12 made the
welcome agent successfully call complete_onboarding(complete) and
flip chat_onboarding_completed to true, the user typed a follow-up
message — but the next turn STILL routed through the welcome agent
instead of orchestrator.

## Root cause

`run_chat_task` caches the built `Agent` in a `THREAD_SESSIONS`
HashMap keyed by `(client_id, thread_id)`. The cache hit predicate
checked `model_override` and `temperature` for invalidation but not
the routing target — so when the routing decision flipped
(chat_onboarding_completed: false → true) between turns, the cache
happily returned the stale welcome agent and `build_session_agent`
was never invoked.

This was a real bug introduced by Commit 8 — when I added
agent-aware routing to `build_session_agent`, I should have
extended the cache key (or hit predicate) with the target agent id.
Without that, the routing fix only worked on the FIRST turn of any
thread; every subsequent turn served the cached agent regardless of
flag changes.

Symptoms in the live test:
* Turn 1: routes to welcome ✓, welcome runs 3 iterations, calls
  complete(), flag flips on disk → cached as welcome agent
* Turn 2: cache hits on (client_id, thread_id), returns cached
  welcome agent. NO `[web-channel]` routing trace, NO
  `[agent::builder]` trace. Welcome runs again with history_len=10.
* Result: orchestrator handoff never happens. User stuck in welcome
  forever.

## Fix

Three small changes in `web.rs`:

### 1. `SessionEntry` gains a `target_agent_id` field

```rust
struct SessionEntry {
    agent: Agent,
    model_override: Option<String>,
    temperature: Option<f64>,
    target_agent_id: String,  // NEW
}
```

Documents which agent definition was used to build the cached
`Agent`, so the next turn's cache lookup can compare against the
current routing decision.

### 2. New `pick_target_agent_id(config)` helper

```rust
fn pick_target_agent_id(config: &Config) -> &'static str {
    if config.chat_onboarding_completed {
        "orchestrator"
    } else {
        "welcome"
    }
}
```

Mirrors the routing decision inside `build_session_agent` so
`run_chat_task` can compute it once up front and use it as a cache
key component. Since `Config::load_or_init` reads from disk every
call, the value reflects the freshly persisted state — meaning the
moment the welcome agent flips the flag, the next turn's
`pick_target_agent_id` returns the new value, the cache hit
predicate falls through, and `build_session_agent` is invoked.

### 3. Cache hit predicate extended

```rust
let mut agent = match prior {
    Some(entry)
        if entry.model_override == model_override
            && entry.temperature == temperature
            && entry.target_agent_id == target_agent_id =>
    {
        log::info!("[web-channel] reusing cached session agent id={} ...");
        entry.agent
    }
    Some(prior_entry) => {
        log::info!(
            "[web-channel] cache miss — rebuilding session agent \
             (was id={}, now id={}) ...",
            prior_entry.target_agent_id, target_agent_id
        );
        build_session_agent(...)?
    }
    None => build_session_agent(...)?,
};
```

The `Some(prior_entry)` arm now distinguishes stale-cache hits
(rebuild + log the transition) from cold misses (rebuild silently).
The transition log line gives observability for the welcome →
orchestrator handoff: whenever you see "cache miss — rebuilding
session agent (was id=welcome, now id=orchestrator)", that's the
moment of the handoff in production.

## Tests

Library compiles. The cache-key change is small and the logic is
straightforward; no test changes needed (existing tests don't
exercise the `THREAD_SESSIONS` cache flow, and adding a unit test
would require mocking the global config + memory backend which is
significantly more setup than the change itself warrants).

Live verification: type `hi` (welcome), wait for the "all set"
message + flag flip, type a follow-up. Expected logs:

```
[web-channel] routing chat turn to 'welcome'  (turn 1)
[agent::builder] building session agent id=welcome
... welcome runs, calls complete(), flag flips ...

[web-channel] routing chat turn to 'orchestrator'  (turn 2)
[web-channel] cache miss — rebuilding session agent (was id=welcome, now id=orchestrator)
[agent::builder] building session agent id=orchestrator
```

That's the complete welcome → orchestrator handoff that has been
the goal of #525 since day one.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(complete_onboarding): document tool semantics in schema, terse success result (#525)

Stopping the prompt-bloat cycle on the welcome agent. Previous
commits (10, 12, and a half-applied iteration of 14) progressively
added MANDATORY blockquotes to welcome's prompt.md to fix three
distinct failure modes: chitchat-fallback in iter 1, missing
complete() in iter 2, and prose-leak in iter 3. Each fix worked but
the welcome prompt is now ~250 lines of imperatives encoding what
should be tool semantics, not agent persona.

The right home for tool semantics is the tool's own schema —
specifically `Tool::description()` and `parameters_schema()`. Those
fields are what the LLM sees when deciding when and how to call the
tool, and they apply to every agent that uses the tool, not just
welcome. Encoding the contract there lets the welcome prompt stay
about persona / workflow / tone, not implementation details.

## Changes

### `tools/impl/agent/complete_onboarding.rs`

#### `Tool::description()` — full rewrite

Replaces the previous 5-line description with a structured ~30-line
contract documenting BOTH actions:

* **`check_status`** — explicit list of what the report contains
  (auth, default model, channels, integrations, memory, both
  onboarding flags), explicit "side effects: NONE (read-only)"
  declaration, and explicit framing as "intended for an LLM agent
  to read and use as the basis for a personalized welcome message"
  so consumers know how to use the result.

* **`complete`** — explicit semantics: flips
  `chat_onboarding_completed`, triggers welcome→orchestrator
  handoff, seeds proactive cron jobs, idempotent, writes config.toml,
  has a pre-condition (must be authenticated). And critically:

  > The complete action returns the literal token "ok" on success.
  > **This return value is a machine-readable success marker, not
  > user-facing prose.** Do not paraphrase it, summarize it, or
  > acknowledge it back to the user — the actual user-facing welcome
  > text should have been emitted alongside the tool call in the
  > same iteration. The chat layer extracts the LAST iteration's
  > text as the user-visible reply, so any prose written after this
  > tool returns will overwrite the welcome message in the chat pane.

  This is the contract that prevents the iter-3 paraphrase leak,
  documented at the source of the tool result instead of in every
  consumer's prompt.

#### `parameters_schema()` — extended action description

The `action` enum's description now mirrors the contract:
> "check_status" → read-only inspection ... "complete" → finalize
> the chat welcome flow, flips chat_onboarding_completed to true,
> returns the literal token "ok" (NOT a user-facing message — do
> not paraphrase the result back to the user).

JSON-schema descriptions are seen by the LLM at function-calling
decision time, so the warning lands in the same place the LLM is
deciding whether to call the tool.

#### `complete()` return value

Changed from a 118-char chatty success string ("Chat welcome flow
marked as complete. Morning briefing and proactive agent jobs have
been set up. The user is all set!") to the literal 2-char `"ok"`.

The chatty string was the source of the iter-3 paraphrase leak —
the LLM kept treating it as something to summarize back to the user
in iteration 3, which overwrote the iter-2 welcome message in the
chat pane (because the chat layer extracts the last iteration's
text). With "ok" there's nothing to paraphrase.

The inline comment block on the return statement records the bug
history so a future maintainer who sees `Ok(ToolResult::success("ok"))`
and is tempted to make it more "informative" understands why it's
deliberately terse.

### `agents/welcome/prompt.md`

Reverts the half-applied "Step 6: STOP after iteration 2" blockquote
that I started adding in the previous commit attempt. Replaces it
with a single one-line pointer at the end of Step 5:

> "(See the `complete_onboarding` tool's own description for what
> its `"ok"` return value means and why you should not paraphrase it
> back to the user.)"

That's enough context for the welcome agent to understand the
return-value contract without duplicating the contract text. The
contract lives in the tool, not in the agent that consumes it.

## What this DOESN'T touch

* Commits 10 (mandatory first action) and 12 (mandatory second tool
  call) stay as-is. They're still arguably too forceful, but they
  encode workflow steps specific to the welcome agent's persona,
  not general tool semantics. Trimming them is a separate prompt
  audit follow-up.

* No code changes outside `complete_onboarding.rs`. No agent.toml
  changes, no schema changes, no other tool changes. Pure
  documentation + return-value tweak.

## Tests

`tool_metadata` still passes (it pins name, scope, permission_level,
and schema shape — not description text, which is intentionally
allowed to drift). 1/1 complete_onboarding tests pass. Library
compiles clean.

## Test plan

Restage sidecar, relaunch Tauri, type `hi`. Expected:
* iter 1: check_status → 600-char status report
* iter 2: 1500-char welcome message + complete_onboarding(complete)
* iter 3 (if it fires): no prose, or trivial prose ≪ iter 2 chars
* Chat pane shows the iter-2 welcome message, NOT a confirmation
  paraphrase.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(complete_onboarding): merge check + finalize, return JSON, halve welcome prompt (#525)

End-to-end testing surfaced the cleanest version of this design,
collapsing what had grown into a multi-iteration imperative dance
back into a single tool call with a JSON return value. Three
problems addressed in one pass:

## Problem 1 — two tool calls were a state-machine race condition

Previous design required the welcome agent to call two tools in
order: `complete_onboarding(check_status)` in iteration 1, then
`complete_onboarding(complete)` in iteration 2 alongside the welcome
text. This created a fragile state machine: if the model wrote the
welcome message but forgot the second tool call, the chat_onboarding
flag never flipped and the user got stuck in welcome forever. The
prompt grew a 50-line "MANDATORY SECOND TOOL CALL" blockquote with
multiple / examples to compensate for what was really an API
shape problem.

## Problem 2 — Markdown report fed paraphrase loops

`check_status` returned a 600-char human-readable Markdown report
with section headers, bullet points, and check marks. The LLM kept
treating it as a draft and paraphrasing fragments back into the
welcome message instead of using the field values as ground-truth
facts. Worse, the structured-looking output gave the model
permission to "wrap up" the tool result in iteration 3, producing
the (parenthetical) leak text the user kept seeing.

## Problem 3 — prompt was bloated past the point of usefulness

After three rounds of "the LLM did the wrong thing, add a more
forceful blockquote", the welcome prompt was ~250 lines, with
overlapping MANDATORY blocks, repeated / examples, and
imperatives that contradicted the tone guidelines. A model trying
to serve a coherent welcome persona could not emerge from that.

## Fix — single call, JSON return, auto-finalize as a side effect

`check_status` now does all of:

1. Reads the user's config.
2. Checks JWT via `crate::api::jwt::get_session_token` (the auth
   source the rest of the codebase uses).
3. **If authenticated AND chat_onboarding_completed is false, flips
   the flag + seeds proactive cron jobs as a side effect**. No
   second tool call, no parameter, no opt-in. Authentication is the
   only gate.
4. Returns a structured JSON snapshot:
   ```
   {
     "authenticated": true,
     "auth_source": "session_token",
     "default_model": "reasoning-v1",
     "channels_connected": ["telegram"],
     "active_channel": "web",
     "integrations": {
       "composio": false,
       "browser": false,
       "web_search": true,
       "http_request": false,
       "local_ai": true
     },
     "memory": { "backend": "sqlite", "auto_save": true },
     "delegate_agents": [],
     "ui_onboarding_completed": true,
     "chat_onboarding_completed": true,
     "finalize_action": "flipped"
   }
   ```

The `finalize_action` field discriminates three cases the welcome
agent needs to handle differently:

* `"flipped"` — auth ok, first welcome, flag was just flipped by
  this call. Write the full welcome and hand off.
* `"already_complete"` — auth ok, chat flow already done from a
  prior call. Friendly re-entry; still write a welcome but
  acknowledge they've been here before.
* `"skipped_no_auth"` — not authenticated. Don't write a celebratory
  welcome; explain the auth problem and let them retry on the next
  chat turn.

The `complete` action stays as a legacy manual finalize-only escape
hatch for admin tools / tests. Returns "ok". Welcome agent doesn't
use it.

## Welcome prompt rewrite

Down from ~250 lines to ~140. Specifically removed:

* "MANDATORY FIRST ACTION" blockquote (40 lines) — replaced with
  one tight paragraph in the new "Iteration 1: call
  complete_onboarding" section.
* "MANDATORY SECOND TOOL CALL" blockquote (50 lines) — entirely
  gone. There is no second tool call.
* "STOP after iteration 2" Step 6 from a previous failed iteration
  (never landed in this branch but the structure that motivated it
  is gone too).
* "Decision rule for iteration 2" (5 lines) — replaced with a
  three-bullet list keyed off `finalize_action`.
* Three duplicate "Don't reply with a 1-line greeting" / "Don't
  treat 'hi' as a signal to be brief" rules — collapsed into one
  shorter rule.

Added:

* "You have exactly two iterations. There is no third." — single
  declarative sentence that does the work the previous 90 lines of
  iteration imperatives were doing.
* Explicit framing that JSON is a fact source, not a draft. "Don't
  quote, paraphrase, or summarise the JSON snapshot back to the
  user."
* `finalize_action` decision tree (three bullets) so the agent
  knows what message to write for each case without the prompt
  needing to enumerate side effects.
* Handling for `skipped_no_auth` — short, helpful "you need to log
  in first" message instead of the celebratory welcome.

## Why this works without any iteration-2 imperatives

With a single tool call, the agent loop converges naturally:

* iter 1: tool call (the only thing the prompt allows)
* tool returns: JSON snapshot, flag already flipped server-side
* iter 2: prose response (no remaining tool calls to make)
* iter 3: doesn't fire because there's no incomplete work — the
  loop terminator at `turn.rs:351` returns when `calls.is_empty()`,
  which is iter 2's state.

No race, no forgotten flip, no paraphrased tool result, no
parenthetical leak.

## Files

`src/openhuman/tools/impl/agent/complete_onboarding.rs` — full
rewrite. New `check_status` builds the JSON, performs the auto-
finalize side effect, returns serialised JSON via `ToolResult::
success`. Old Markdown-report code deleted (~150 lines). Tool
description and `parameters_schema` rewritten to document the new
contract; the `finalize` parameter from the previous attempt is
removed entirely (auth presence IS the gate).

`src/openhuman/agent/agents/welcome/prompt.md` — full rewrite.
~250 → ~140 lines. New "two iterations, no third" workflow.
JSON-aware drafting instructions. `finalize_action` decision tree.

## Tests

* `tool_metadata` (1/1): pins schema shape (action enum, required
  fields). Still passes — schema unchanged at the level the test
  asserts.
* `agent::agents::*` (14/14): all welcome-definition shape tests
  pass. The TOML hasn't changed, only the embedded `prompt.md`.

## Verification

Restage sidecar, relaunch Tauri, type `hi`. Expected:

* iter 1: `complete_onboarding(check_status)` → JSON returned with
  `finalize_action: "flipped"`. Side effect: chat_onboarding
  flipped to true on disk.
* iter 2: ~250-word welcome message based on the JSON.
* iter 3: not reached. Loop terminates after iter 2.
* Chat pane shows the iter-2 welcome. No paraphrase leak.

Then type a follow-up:

* `[web-channel] cache miss — rebuilding session agent (was
  id=welcome, now id=orchestrator)` — the welcome → orchestrator
  handoff fires via Commit 13's cache invalidation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(welcome): hide the welcome→orchestrator handoff from the user (#525)

The welcome agent's prompt previously instructed it to close every
welcome message with an explicit handoff line:

> "From here, you're in the hands of the full OpenHuman assistant.
>  Just start a new conversation and ask it anything — it knows how
>  to delegate to specialists, run tools, search the web..."

That line is a UX leak. From the user's perspective they are
talking to "OpenHuman" — one assistant, one conversation. They do
not know there are multiple agents under the hood, and they
shouldn't have to. The welcome → orchestrator transition is a
routing-layer implementation detail. Surfacing it in the chat
makes openhuman feel like a chatbot framework instead of a
product.

## Changes (welcome prompt only — no code)

### Step 5 of the message structure

Renamed from "Hand off" to "Close naturally". New instruction:

> "End the message with something inviting like 'anything you'd
>  like to try first?' or 'what should we dig into?' — a normal
>  conversational close that lets the user pick up the next turn.
>  Do NOT mention handing off, transferring, or any change in who
>  they're talking to. The user does not know there are multiple
>  agents under the hood; from their perspective they're talking
>  to OpenHuman as one entity, and the next message they send
>  will just continue the conversation."

### "What NOT to do" list

Replaced the old "Don't forget the handoff" rule with its inverse:

> "**Don't reveal that the conversation is being routed to a
> different agent.** From the user's perspective they are talking
> to 'OpenHuman' — one assistant, one conversation. Do NOT say
> 'I'll hand you off to the main assistant', 'the orchestrator
> will take over', 'you're now in the hands of the full assistant',
> 'from here on out you'll be talking to a different agent', or
> any variation that exposes the welcome → orchestrator handoff.
> The handoff happens transparently in the routing layer; the user
> just sends another message and the conversation continues.
> Phrases like 'what should we dig into?' or 'anything you'd like
> to try first?' are correct conversational closes — they invite
> the next turn without leaking the architecture."

The rule lists four specific forbidden phrasings the previous
welcome agent runs produced (variations of "you're in the hands
of the full assistant" and "the orchestrator will take over") so
the model has explicit anti-patterns to match against, plus two
acceptable conversational closes.

## Why this matters for #525

The welcome → orchestrator handoff is the central architectural
contribution of this PR. From a developer's perspective it is a
big deal — different agent definition, different tool scope,
different prompt, different temperature, different model hint.
From the user's perspective it should be invisible. They type a
message, OpenHuman replies. They type another, OpenHuman replies.
The fact that one of those replies came from `welcome` and the
next from `orchestrator` is purely an implementation detail they
should never have to think about.

## What does NOT change

* No code changes. Pure prompt edit.
* The handoff still happens — `chat_onboarding_completed` still
  flips to true via the `check_status` auto-finalize, the
  `THREAD_SESSIONS` cache still invalidates between turns (Commit
  13), the next chat turn still routes to orchestrator (Commit 8).
  The mechanism is unchanged; only the user-facing framing of it
  is.
* No test changes. `agent::agents::welcome_has_onboarding_and_
  memory_tools` and `every_builtin_has_a_prompt_body` only
  validate the agent definition shape, not the prompt body text,
  so this commit does not affect them.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(complete_onboarding): delegate auto-finalize side effect to complete() (#525)

Small refactor on top of Commit 15. The auto-finalize block inside
`check_status` previously inlined the entire flag-flip + cron-seed
sequence:

```rust
config.chat_onboarding_completed = true;
config.save().await?;
let seed_config = config.clone();
tokio::spawn(async move {
    if let Err(e) = crate::openhuman::cron::seed::seed_proactive_agents(&seed_config) {
        tracing::warn!("...");
    }
});
```

But the legacy `complete()` action does literally the same work.
Duplicating it meant two places to update if the finalize semantics
ever changed (e.g. add another side effect, change the cron seed
strategy). Refactor `check_status` to delegate to `complete()`:

```rust
let _ = complete().await?;
config.chat_onboarding_completed = true;  // mirror disk into local
```

The `let _` discards `complete()`'s `ToolResult::success("ok")`
return value — `check_status` is producing its own JSON snapshot,
the caller just wants the side effect.

The `config.chat_onboarding_completed = true` after the call is an
in-memory mirror of the flip that `complete()` just performed on
disk. Without it, the JSON snapshot built later in `check_status`
would still show the pre-flip value, because the local `config` was
loaded BEFORE `complete()` ran and `complete()` operates on its own
fresh disk-loaded copy. The mirror is one line, no extra disk read,
no behavior change.

## Why not extract a `perform_finalize(&mut Config)` helper instead

Considered. Would eliminate the in-memory mirror and the redundant
disk read inside `complete()`. Ruled out because:

* `complete()` is already documented and used as a public legacy
  action; changing its signature would ripple to admin tools and
  tests that call it through the action dispatcher.
* The mirror cost is one line, zero I/O, zero correctness risk.
* The literal "call complete() inside check_status" framing matches
  the request more directly.

If a future maintainer wants to refactor further, the obvious move
is to make `complete()` take `&mut Config`, drop the redundant
`Config::load_or_init` inside `complete()`, and have both
`check_status` and the public action handler load the config once
at their top level. That's a separate refactor.

## Files

`src/openhuman/tools/impl/agent/complete_onboarding.rs` — single
function body change in `check_status`. The "flipped" arm of the
`finalize_action` match now calls `complete()` and mirrors the
flip locally instead of inlining the flip + save + cron seed.
About -10 / +5 lines.

## Tests

Library compiles. The 1 `tool_metadata` test still passes (it
pins the schema shape, not function bodies). No behavior change
for any caller.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style(builder): cargo fmt cleanup of from_config_for_agent body (#525)

Pure indentation cleanup applied by `cargo fmt` against the
`target_def` resolution block I added in Commit 8 (`feat(web-channel):
route Tauri in-app chat to welcome/orchestrator`). The original
indentation had an awkward type annotation on the `let target_def:
Option<...> = ` line that wrapped before the `match` keyword;
rustfmt prefers the `=` on the same line as the type and the
`match` body indented under it.

Zero behavior change. Same control flow, same logic, same
diagnostics. Detected by the husky pre-push hook running
`yarn rust:check` (which runs `cargo fmt --check`).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test(json_rpc_e2e): set chat_onboarding_completed=true in seed config (#525)

The e2e config seeded by `write_min_config` now sets
`chat_onboarding_completed = true` so `channel_web_chat` routes straight
to the orchestrator. Without this the first chat turn goes through the
new welcome agent whose tool contract is not modelled by the in-process
mock upstream, which tore down the SSE stream mid-response and caused
`json_rpc_protocol_auth_and_agent_hello` to fail with
`sse stream read failed: error decoding response body`.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Steven Enamakel <31011319+senamakel@users.noreply.github.com>
2026-04-13 14:17:11 -07:00
Steven EnamakelandGitHub 8057f2283c feat: onboarding Gmail integration + LinkedIn profile enrichment (#524)
* refactor(composio): restructure toolkit metadata handling and onboarding steps

- Removed the old `toolkitMeta.ts` file and replaced it with a new `toolkitMeta.tsx` file that includes updated metadata handling for Composio toolkits, enhancing the integration with React components.
- Updated the `ComposioConnectModal` to directly render icons without additional markup, streamlining the component structure.
- Modified the `Skills` page to utilize the new icon rendering method, improving consistency across the application.
- Enhanced the onboarding process by introducing a new `ContextGatheringStep` component, which gathers user context from connected integrations, improving the onboarding experience.
- Updated the `SkillsStep` to reflect changes in toolkit connection handling and display, ensuring a smoother user interaction during onboarding.

* feat(apify): introduce Apify integration tools for actor execution and status retrieval

- Added new tools for running Apify actors and fetching their run statuses, enhancing automation capabilities.
- Updated the integration schema to include an `apify` toggle for user configuration, allowing for flexible integration management.
- Enhanced the onboarding experience by modifying the SkillsStep to focus on Gmail integration, streamlining user interactions.
- Improved documentation and comments for clarity on the new Apify functionalities and their usage.

* feat(learning): add LinkedIn enrichment module and schemas

- Introduced a new `linkedin_enrichment` module for enriching user profiles by scraping LinkedIn data from Gmail.
- Implemented the `run_linkedin_enrichment` function to handle the enrichment pipeline, including Gmail search, scraping via Apify, and data persistence.
- Added controller schemas for the learning domain, enabling integration with the existing controller framework.
- Updated the `all.rs` file to register the new learning controllers and schemas, enhancing the overall functionality of the learning system.

* feat(linkedin): implement PROFILE.md generation for LinkedIn enrichment

- Added functionality to generate a PROFILE.md file from scraped LinkedIn data, summarizing user profiles for agent context.
- Updated the `run_linkedin_enrichment` function to write PROFILE.md to the workspace, enhancing data persistence.
- Introduced helper functions for rendering and summarizing LinkedIn profiles, improving the overall enrichment process.
- Ensured minimal PROFILE.md creation even when scraping fails, maintaining essential user context.

* feat(onboarding): enhance ContextGatheringStep for LinkedIn enrichment pipeline

- Updated the ContextGatheringStep to integrate a new pipeline for LinkedIn enrichment, replacing the previous Gmail profile fetching stages.
- Implemented a progress animation and logging for the enrichment process, improving user feedback during data retrieval.
- Refactored stage definitions to align with the new pipeline structure, enhancing clarity and maintainability.
- Introduced error handling and status updates for each stage of the enrichment process, ensuring robust user experience.

* feat(instructions): refactor tool instruction generation for clarity and flexibility

- Introduced helper functions `tool_instructions_preamble` and `append_tool_entry` to streamline the construction of tool instructions.
- Updated `build_tool_instructions` to utilize the new helper functions, improving code readability and maintainability.
- Added `build_tool_instructions_filtered` to allow for generating instructions from a filtered list of tools, enhancing flexibility in tool usage.
- Adjusted the startup process to use the filtered instructions, ensuring only relevant tools are included in the system prompt.

* refactor(toolkitMeta): simplify component structure and improve readability

- Refactored the `BrandIcon` component to streamline its props definition, enhancing code clarity.
- Consolidated SVG path definitions in various icons for better readability and maintainability.
- Updated the `ContextGatheringStep` to simplify the RPC call syntax, improving code conciseness.
- Enhanced logging in the LinkedIn enrichment process for clearer tracking of Gmail searches and scraping stages.

* fix: address PR review — USER.md→PROFILE.md consistency, Composio tool filtering, and quality fixes

- Replace USER.md with PROFILE.md across all prompt paths: channels_prompt.rs,
  subconscious/prompt.rs, workspace/ops.rs bootstrap, and channel tests
- Remove Composio tool description from main agent system prompt (tool_descs)
  so skills_agent is the only agent that sees Skill-category tools
- Add "learning" namespace description for CLI help discovery
- Fix react-hooks/set-state-in-effect: wrap synchronous setState in
  queueMicrotask in ContextGatheringStep
- Derive SkillsStep displayToolkits from backend allowlist with error/retry UI
- Add KNOWN_COMPOSIO_TOOLKITS alternate slug variants (google_calendar, etc.)
- Add namespaced debug logging to onboarding handlers and pipeline
- Deduplicate MemoryClient creation in linkedin_enrichment persist functions

* fix: use setTimeout instead of queueMicrotask for ESLint compatibility

* refactor(onboarding): streamline debug logging in handleContextNext function

- Consolidated debug logging in the handleContextNext function to improve clarity and reduce verbosity.
- Removed unnecessary line breaks for a more concise code structure.

* refactor(linkedin): enhance enrichment pipeline with structured stage results

- Introduced a new `EnrichmentStage` struct to capture detailed results for each stage of the LinkedIn enrichment process.
- Updated the `LinkedInEnrichmentResult` to include a vector of stages, allowing for structured reporting of success, failure, and skipped stages.
- Improved error handling and logging throughout the enrichment pipeline, ensuring better traceability of issues during execution.
- Adjusted the API response to include stage results, enhancing the frontend's ability to display detailed enrichment outcomes.

* chore(workflows): update macOS E2E test configuration and comment out Linux E2E job

- Modified the description and default values in the macOS E2E test input options for clarity.
- Commented out the entire Linux E2E job configuration to prevent execution while maintaining the setup for future use.
2026-04-13 14:16:53 -07:00
08d9fd2d4d fix(voice): recover buffered hotkey events after select! race (#527) (#545)
* fix(voice): return receiver count from publish_transcription

publish_transcription now returns the number of active TRANSCRIPTION_BUS
subscribers that received the message. When no receivers are connected
(e.g. Socket.IO bridge not yet subscribed), the function logs a warning
instead of silently discarding the broadcast.

This makes it possible to diagnose "transcription produced but never
delivered" scenarios from logs alone.

Closes #527 (partial)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(voice): recover buffered hotkey events after select! race

On warm CPAL init (second+ recording), audio_capture::start_recording()
completes fast enough that the pending_ready branch and the hotkey_rx
branch are both ready simultaneously inside tokio::select!. select!
picks one pseudo-randomly — when it picks pending_ready, the Released
event sits unprocessed in hotkey_rx. The recording is stored as "live"
with no deferred stop, then the next loop iteration processes the
buffered Released and stops the recording almost immediately, producing
a near-zero-length clip that the duration gate silently drops.

Fix: after pending_ready resolves, call hotkey_rx.try_recv() to check
for a buffered stop event that lost the race. If found, apply the
deferred stop mechanism (MIN_RECORDING_AFTER_SETUP = 1500ms) instead
of treating the recording as live.

Also adds pipeline_id (UUID prefix) to all process_recording_bg log
lines for end-to-end correlation, and labels each pipeline stage
(stop_recording, gate_duration, gate_silence, transcribe, deliver)
so dropped recordings are diagnosable from logs.

Closes #527

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 14:07:28 -07:00
github-actions[bot] 57a8bedddf chore(release): v0.52.7 v0.52.7 2026-04-13 13:27:38 +00:00
github-actions[bot] af454afbb3 chore(release): v0.52.6 v0.52.6 2026-04-13 07:03:29 +00:00
Steven EnamakelandGitHub ec138c8491 feat(composio): provider folder modules + user profile persistence (#523)
* feat(composio): implement profile persistence for user data

- Introduced a new `profile` module to handle the persistence of user profile data from various providers into the local `user_profile` facet table.
- Enhanced the `composio_get_user_profile` and `fetch_user_profile` functions to call `persist_provider_profile`, ensuring that profile fields like display name, email, and avatar are stored locally for quick access.
- Added debug logging to track the number of facets written during the persistence process, improving observability of profile updates.
- This change aims to enhance user experience by reducing the need for repeated upstream API calls for frequently accessed profile information.

* style: apply cargo fmt to profile.rs and client.rs

* fix: PR review — cursor whitespace, stale profile values, test precision, log level

- Trim whitespace in cursor_to_gmail_after_filter before parsing so
  leading/trailing spaces don't silently bypass the date filter.
- Change profile_upsert condition from `>` to `>=` so equal-confidence
  provider refreshes replace stale values instead of only bumping
  evidence_count.
- Tighten epoch millis test to assert exact date "2026/03/31" instead
  of loose contains('/') check.
- Lower profile persistence log from info to debug to reduce noise in
  normal connect/sync flows.
2026-04-12 19:18:07 -07:00
Steven EnamakelandGitHub ae9ac30db6 feat: add proactive welcome onboarding flow (#522)
* feat: add proactive agents for onboarding experience

- Introduced two new proactive agents: "Morning Briefing" and "Welcome Message".
- The Morning Briefing agent provides a daily summary of the user's calendar, tasks, and relevant context, scheduled to run automatically at 7 AM.
- The Welcome Message agent delivers a personalized greeting after onboarding, utilizing user information for a tailored experience.
- Updated the cron job system to seed these agents upon onboarding completion, enhancing user engagement from the start.
- Added comprehensive tests to ensure the correct functionality of the new agents and their integration into the system.

* feat: implement proactive message handling for user engagement

- Added a new `ProactiveMessageSubscriber` to route proactive messages (e.g., morning briefings, welcome messages) to the user's active channel, enhancing user engagement.
- Introduced `ProactiveMessageRequested` event in the event bus to facilitate proactive message delivery.
- Updated the configuration schema to include a preferred channel for proactive messages, allowing for tailored user experiences.
- Seeded default proactive agent cron jobs to ensure timely delivery of messages post-onboarding.
- Enhanced the startup process to register the proactive message subscriber, ensuring proactive messages are consistently routed.
- Comprehensive tests added to validate the functionality of proactive message handling and delivery mechanisms.

* feat: enhance welcome agent functionality and onboarding process

- Updated the welcome agent's description to clarify its role as the first point of contact for new users, focusing on guiding them through remaining onboarding steps.
- Increased the maximum iterations for the agent from 4 to 6 to allow for more complex interactions.
- Introduced a new `complete_onboarding` tool to check the user's setup status and mark onboarding as complete, improving the onboarding experience.
- Enhanced the prompt structure to provide clearer guidance on the agent's workflow, including steps for checking setup status, greeting users, and completing onboarding.
- Updated the tools list to include `complete_onboarding`, ensuring the welcome agent can effectively manage user onboarding.
- Comprehensive documentation added to clarify the agent's functionality and improve user engagement.

* feat: enhance welcome agent functionality and onboarding process

- Updated the welcome agent's description to clarify its role as the first point of contact for new users, focusing on guiding them through setup and onboarding.
- Increased the maximum iterations for the agent from 4 to 6 to allow for more comprehensive interactions.
- Introduced a new `complete_onboarding` tool to check the user's setup status and mark onboarding as complete, improving the onboarding experience.
- Enhanced the prompt structure to provide clearer guidance on the agent's workflow, including steps for checking setup status, greeting users, and completing onboarding.
- Updated the tools list to include the new `complete_onboarding` tool, ensuring it is available for the welcome agent's operations.
- Improved documentation for the welcome agent's prompt, emphasizing tone guidelines and what not to do during user interactions.

* style: apply rustfmt to onboarding flow changes

* refactor: streamline proactive message subscriber registration and enhance onboarding agent tests

- Replaced the direct registration of the proactive message subscriber with a new `register_web_only_proactive_subscriber` function, ensuring it is only registered once during domain startup.
- Updated the welcome agent test to reflect the addition of the `complete_onboarding` tool, verifying its presence and adjusting the expected number of tools.
- Enhanced the onboarding process by improving logging for proactive agent seeding transitions, providing clearer insights during onboarding state changes.
- Refactored the cron job system to support agent definitions, allowing for more flexible job scheduling and execution based on defined agent parameters.
- Improved documentation and comments across the affected modules to clarify the purpose and functionality of changes made.

* refactor: improve proactive subscriber registration and logging

- Streamlined the registration process for the web-only proactive message subscriber, enhancing clarity and maintainability.
- Consolidated logging statements in the onboarding completion function for improved readability and consistency.
- These changes aim to enhance the overall structure and logging clarity within the proactive messaging and onboarding processes.
2026-04-12 19:15:42 -07:00
Steven EnamakelandGitHub 4cf608c2be feat: native embeddings module with Ollama and vector store (#521)
* feat: replace fastembed with candle for local embeddings

- Introduced a new `CandleEmbedding` provider using the `candle` ML framework, eliminating C++ dependencies and enhancing performance.
- Updated configuration to default to `candle` for embedding provider settings, replacing the previous `fastembed` references.
- Added new modules for embedding providers, including `candle_embed`, `noop`, and `openai`, to support various embedding strategies.
- Enhanced the `EmbeddingProvider` interface to accommodate the new `CandleEmbedding` implementation.
- Refactored related tests to ensure comprehensive coverage of the new embedding functionalities and maintain backward compatibility with existing configurations.

* feat: update embedding provider to Ollama

- Replaced the default embedding provider from Candle to Ollama, enhancing local embedding capabilities with improved model management and GPU acceleration.
- Updated configuration defaults for embedding model and dimensions to align with Ollama specifications.
- Introduced a new Ollama embedding module, including necessary constants and functionality for embedding requests.
- Refactored related code and tests to ensure compatibility with the new provider, maintaining backward compatibility with existing configurations.

* refactor: remove Candle embedding provider and related dependencies

- Deleted the `CandleEmbedding` module and its associated files, streamlining the embedding provider architecture.
- Updated `Cargo.toml` and `Cargo.lock` to remove references to Candle-related packages, ensuring a cleaner dependency tree.
- Refactored the `EmbeddingProvider` interface to eliminate support for the Candle provider, maintaining compatibility with existing providers.
- Adjusted tests and documentation to reflect the removal of the Candle embedding functionality, ensuring clarity and consistency across the codebase.

* feat: add SQLite-backed vector store for embeddings

- Introduced a new `store` module to implement a local vector store backed by SQLite, enabling efficient storage and retrieval of text embeddings.
- Added functionality for inserting, updating, and searching embeddings using cosine similarity, enhancing the embedding management capabilities.
- Updated the `mod.rs` file to include the new `store` module and expose relevant functions for cosine similarity and vector operations.
- Enhanced documentation to provide usage examples and clarify the integration of the vector store with existing embedding providers.

* Add fs2 dependency for improved file locking in ComposeIO trigger history

* test: boost embeddings module coverage to 97%+

Add tests for batch insert mismatch error path, invalid metadata
JSON handling, disk store parent directory creation, and fake
embedding dimensions accessor. 95 tests total across the module,
all files at 97-100% line coverage.

* style: apply cargo fmt to embeddings module

* feat: enhance embedding provider creation with error handling

- Updated the `create_embedding_provider` function to return an `anyhow::Result`, allowing for immediate error reporting on unrecognized provider names.
- Added support for a "none" provider that returns a no-op embedding.
- Enhanced tests to cover new error handling paths and validate behavior for known and unknown providers, improving overall test coverage.

* feat: enhance Ollama and OpenAI embedding providers with improved handling for blank inputs and response validation

- Updated the `embed` method in both `OllamaEmbedding` and `OpenAiEmbedding` to skip blank inputs while preserving their positions in the output as zero-vectors.
- Added validation to ensure the response count matches the input count, with appropriate error handling for dimension mismatches.
- Enhanced tests to cover new behavior for blank inputs and response validation, ensuring robustness in embedding functionality.

* fix: address PR review findings for embeddings module

- Factory returns Result for unknown providers instead of silent noop
- Ollama: preserve positional alignment when blank texts are filtered
- Ollama: validate response count and dimensions before returning
- OpenAI: strict parsing errors on non-numeric embedding values
- OpenAI: skip Authorization header when api_key is empty
- OpenAI: validate response count and dimensions
- OpenAI/Ollama: add tracing at entry, success, and error paths
- Store: add store_meta table to persist and validate embedding
  provider/dimensions on open — errors on dimension mismatch
- Store: propagate row decode errors instead of filter_map(ok)
- Store: add tracing to search, insert, delete, and count paths
- Update factories.rs caller for Result-returning factory

101 tests pass, all files at 97%+ coverage.
2026-04-12 18:54:38 -07:00
Steven EnamakelandGitHub 3a20599f45 feat: dynamic connected integrations in agent system prompts (#520)
* feat(agent): add support for connected integrations in system prompts

- Introduced a new `fetch_connected_integrations` method to retrieve and populate active Composio integrations for the agent.
- Updated the `Agent` struct to include a `connected_integrations` field, allowing the system prompt to display available external services.
- Enhanced the `build_system_prompt` method to incorporate connected integrations, improving the context provided to users during interactions.
- Added a `ConnectedIntegrationsSection` to the prompt rendering, ensuring visibility of active integrations in the system prompt output.
- Overall, these changes enhance the agent's ability to leverage connected services, improving user experience and interaction capabilities.

* feat(debug_dump): integrate connected integrations into agent prompt dumps

- Added a new `fetch_connected_integrations_for_dump` function to retrieve active integrations for the agent during prompt dumps.
- Updated the `render_main_agent_dump` function to include connected integrations, enhancing the context provided in the debug output.
- Improved the overall structure and clarity of the debug dump process, ensuring that connected integrations are accurately represented in the agent's prompt context.

* refactor(agent): streamline integration fetching for system prompts

- Refactored the `fetch_connected_integrations` method in the `Agent` struct to delegate integration fetching to a new centralized function in the `composio` module, enhancing code clarity and maintainability.
- Updated the `fetch_connected_integrations_for_dump` function to utilize the new centralized fetching logic, ensuring consistent integration retrieval across different contexts.
- Improved the overall structure of integration handling, allowing for better error management and logging during the fetching process.

* feat(agent): add connected integrations support to parent execution context

- Introduced a new field `connected_integrations` in the `ParentExecutionContext` struct to store active Composio integrations.
- Updated relevant functions to utilize the new `connected_integrations` field, ensuring that system prompts and agent dumps reflect the current integrations.
- Enhanced the integration cache management by implementing cache invalidation logic when connections are created or deleted, improving the accuracy of integration data across sessions.
- Overall, these changes enhance the agent's ability to leverage connected services, providing users with better context during interactions.

* feat(agent): initialize connected integrations in subagent context

- Added a `connected_integrations` field to the `ParentExecutionContext` and `Agent` struct, allowing for the storage and retrieval of active Composio integrations.
- Updated the `dispatch_target_agent` function to populate the `connected_integrations` field when creating a new sub-agent context.
- Enhanced the `fetch_connected_integrations` method to return an `Option<Vec<ConnectedIntegration>>`, improving error handling and caching logic.
- These changes improve the agent's ability to manage and utilize connected integrations, enhancing user interactions and context awareness.

* refactor(agent): improve caching logic in fetch_connected_integrations

- Updated the `fetch_connected_integrations` function to handle caching more effectively by using a match statement.
- The function now caches results only when the backend is reachable, preventing unnecessary caching when the client is unavailable.
- This change enhances error handling and ensures that subsequent calls with different configurations can retry without stale data.

* style: apply cargo fmt formatting

* feat(agent): enhance connected integrations handling and caching

- Updated the `dispatch_target_agent` function to initialize connected integrations for sub-agents, ensuring they have access to the latest integrations.
- Improved the caching mechanism for connected integrations by using a `HashMap` keyed by configuration identity, allowing for user-specific caching and better isolation of integration data.
- Refactored the `invalidate_connected_integrations_cache` function to clear the entire cache instead of setting it to `None`, enhancing cache management.
- Added a new method `load_from_default_paths` in the `Config` struct to reliably load user configurations without being affected by environment variable overrides, improving the debug dump process.
- Enhanced the rendering of connected integrations in system prompts to provide clearer instructions based on available tools, improving user interaction clarity.

* feat(agent): add method to set connected integrations

- Introduced a new `set_connected_integrations` method in the `Agent` struct to allow for replacing the agent's connected integrations from external sources, enhancing flexibility in integration management.
- Updated the caching mechanism for connected integrations to utilize `LazyLock`, improving initialization efficiency and thread safety.

* style: apply cargo fmt
2026-04-12 18:37:01 -07:00