Skip to content

refactor(skills): unify auth/oauth handshake on start({validate}) and drop enabled flag#484

Merged
graycyrus merged 4 commits intomainfrom
fix/skill-states
Apr 10, 2026
Merged

refactor(skills): unify auth/oauth handshake on start({validate}) and drop enabled flag#484
graycyrus merged 4 commits intomainfrom
fix/skill-states

Conversation

@senamakel
Copy link
Copy Markdown
Member

@senamakel senamakel commented Apr 10, 2026

Summary

  • Collapses skill state from enabled + setup_complete down to a single setup_complete flag — resolve_should_start is now setup_complete OR manifest.auto_start.
  • Rewrites handle_oauth_complete and handle_auth_complete around the same contract: temp-inject the new credentials, call start({oauth, auth, validate:true}), persist to disk only when start returns {status:'complete'}, otherwise roll back.
  • Removes the onOAuthComplete / onAuthComplete JS hooks. start() is now the single entry point a skill implements for "I am now connected — go activate."
  • Threads a {oauth, auth} credential bag into the start lifecycle on instance spawn (build_start_credentials_arg reads oauth_credential.json / auth_credential.json from disk).

Problem

The skill lifecycle had two parallel "is this skill on?" states (enabled vs setup_complete) and three places where credentials could be wired up (onOAuthComplete, onAuthComplete, start). That meant skills could end up half-activated — credential persisted to disk but cron never registered, or cron registered against a credential the upstream API rejects on the next call. The tri-state was also a footgun for the test harness, which had to know which RPC to send in which order.

Solution

  • preferences.rs: drop enabled, is_enabled, set_enabled, get_or_default. Tests rewritten to drop the enabled references.
  • schemas.rs: handle_skills_enable becomes a compat shim (set_setup_complete(true) + start_skill); handle_skills_disable is the inverse; handle_skills_is_enabled reads setup_complete.
  • qjs_engine.rs: remove enable_skill / disable_skill / is_skill_enabled.
  • event_loop/rpc_handlers.rs: handle_oauth_complete and handle_auth_complete now both temp-inject credentials → call start({validate:true}) via handle_js_call → check {status} → rollback bridge state if validation failed (no disk write), otherwise persist oauth_credential.json / auth_credential.json (and client_key.json for encrypted OAuth). The old onOAuthComplete / onAuthComplete JS calls are deleted.
  • qjs_skill_instance/instance.rs: new build_start_credentials_arg(data_dir) reads the persisted credential files and produces the {oauth, auth} bag passed to call_lifecycle("start", ...) on spawn. All other call_lifecycle callers updated to pass None.
  • js_handlers.rs: call_lifecycle now takes args_json: Option<&str> so callers can pass JSON into start({...}).

Submission Checklist

  • Unit testscargo test -p openhuman_core for the rewritten preferences module (existing assertions adjusted for the dropped enabled field).
  • E2E / integration — exercised end-to-end from the skills repo via src/core/notion/live-test-stress.ts (12/12 passes once the matching skills-side fixes land in refactor(notion+gmail): unify auth lifecycle on start(), fix proxy /v1 prefix regression openhuman-skills#12).
  • Doc comments/// on handle_oauth_complete, handle_auth_complete, and build_start_credentials_arg explain the validate-then-persist contract.
  • Inline comments — rollback paths in the RPC handlers call out why the bridge is cleared on validation failure.

Impact

  • Runtime: desktop core only — JS skills must now treat start() as authoritative (no separate OAuth/auth complete hooks). The matching TypeScript skill rewrites land in refactor(notion+gmail): unify auth lifecycle on start(), fix proxy /v1 prefix regression openhuman-skills#12.
  • Compatibility: skills_enable / skills_disable / skills_is_enabled JSON-RPC methods still work as compat shims for existing UI code paths; nothing in app/ needs changes.
  • Migration: on-disk skill-preferences.json files keep working — enabled is just ignored if present.

Related

Summary by CodeRabbit

  • Refactor
    • Simplified skill preference system by consolidating state management and removing redundant fields
    • Updated skill lifecycle initialization to handle credential injection more directly during startup
    • Streamlined skill enable/disable logic to use a single state indicator instead of multiple flags

…ving the enabled toggle

- Removed the `enabled` field from `SkillPreference`, simplifying the preference model to focus solely on `setup_complete`.
- Updated related methods and RPC handlers to reflect this change, ensuring that skills are automatically started based on the completion of their setup process.
- Adjusted tests to validate the new behavior, ensuring consistent functionality without the `enabled` toggle.
- Enhanced documentation to clarify the new preference management approach.
…te hook

- Updated the `handle_auth_complete` function to eliminate the separate `onAuthComplete` JavaScript hook, streamlining the authentication process.
- Revised the flow to directly inject new credentials and validate them using the `start` function, which now handles both validation and activation.
- Enhanced rollback logic to ensure temporary credentials are cleared if validation fails, preventing persistence on disk.
- Improved documentation to clarify the new authentication steps and their implications for credential management.
…h_complete`

- Removed the `build_start_credentials_arg` function and integrated its logic directly into `handle_oauth_complete`, simplifying the flow.
- Updated the OAuth handling to validate credentials before persisting them, ensuring that only successful validations are saved.
- Enhanced rollback logic to clear temporary credentials if validation fails, preventing incorrect state persistence.
- Improved documentation to clarify the new steps in the OAuth process and their implications for credential management.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 10, 2026

📝 Walkthrough

Walkthrough

The PR removes the enabled field from skill preferences, making setup_complete the sole preference state. It eliminates runtime API methods for enabling/disabling skills, updates lifecycle call signatures to accept optional credential arguments, and refactors OAuth/Auth completion handlers to validate credentials through the start() lifecycle before persisting.

Changes

Cohort / File(s) Summary
Preference Model
src/openhuman/skills/preferences.rs
Removed enabled field from SkillPreference; updated loading/query logic to use setup_complete only; removed is_enabled and set_enabled public methods; updated resolve_should_start to bypass enabled fallback.
Runtime API Surface
src/openhuman/skills/qjs_engine.rs
Removed three public methods: enable_skill, disable_skill, and is_skill_enabled.
Lifecycle Infrastructure
src/openhuman/skills/qjs_skill_instance/js_handlers.rs
Updated call_lifecycle signature to accept optional args_json: Option<&str> parameter for injecting credential/configuration arguments into lifecycle functions.
Lifecycle Invocations
src/openhuman/skills/qjs_skill_instance/instance.rs, src/openhuman/skills/qjs_skill_instance/event_loop/mod.rs
Added build_start_credentials_arg() to read and serialize persisted credentials; updated init, start, and stop lifecycle calls to use new optional-args signature.
Credential Validation & Persistence
src/openhuman/skills/qjs_skill_instance/event_loop/rpc_handlers.rs
Refactored handle_oauth_complete and handle_auth_complete to invoke start() with injected credentials and validate: true; added validation gating and rollback logic; persist credentials only on successful validation.
RPC Endpoints
src/openhuman/skills/schemas.rs
Updated skills.enable, skills.disable, and skills.is_enabled RPC handlers to use setup_complete preference instead of removed enabled field and runtime methods.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Handler as OAuth Handler
    participant JS as JS Runtime
    participant Persist as Disk Storage
    
    Client->>Handler: handleOAuthComplete(oauth_params)
    Handler->>JS: Inject oauth + __oauthClientKey
    Handler->>JS: Call start({oauth, auth, validate: true})
    JS-->>Handler: Result {status, ...}
    
    alt Validation Success
        Handler->>Persist: Write oauth_credential.json
        Handler->>Persist: Write client_key.json
        Handler-->>Client: Return success
    else Validation Failed (status:"error" or Err)
        Handler->>JS: Clear injected credentials
        Handler-->>Client: Return error
        Note over Persist: No writes
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • graycyrus

Poem

🐰 Away with enabled, let setup_complete reign,
Credentials now validated before they're maintained,
Lifecycle calls flow with arguments in hand,
The skill preferences refactored, precisely planned!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: removing the enabled flag and unifying auth/oauth handling through the start() method with validation.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/skill-states

Comment @coderabbitai help to get the list of available commands and usage tips.

@senamakel senamakel marked this pull request as ready for review April 10, 2026 06:19
@senamakel senamakel changed the title Fix/skill states refactor(skills): unify auth/oauth handshake on start({validate}) and drop enabled flag Apr 10, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/openhuman/skills/schemas.rs (1)

902-910: Consider rolling back setup_complete if start_skill fails.

If start_skill fails after set_setup_complete(true), the skill remains marked as "enabled" but isn't actually running. On next application restart, the runtime will attempt to auto-start it (because setup_complete is true), which may fail again.

This might be intentional (retry on restart), but if not:

💡 Optional: Rollback on failure
 fn handle_skills_enable(params: Map<String, Value>) -> ControllerFuture {
     Box::pin(async move {
         let p: SkillIdParams =
             serde_json::from_value(Value::Object(params)).map_err(|e| e.to_string())?;
         let engine = require_engine()?;
         engine.preferences().set_setup_complete(&p.skill_id, true);
-        engine.start_skill(&p.skill_id).await?;
+        if let Err(e) = engine.start_skill(&p.skill_id).await {
+            // Rollback setup_complete so auto-start doesn't retry a broken skill
+            engine.preferences().set_setup_complete(&p.skill_id, false);
+            return Err(e);
+        }
         Ok(serde_json::json!({ "ok": true }))
     })
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/openhuman/skills/schemas.rs` around lines 902 - 910, handle_skills_enable
currently calls engine.preferences().set_setup_complete(&p.skill_id, true)
before awaiting engine.start_skill(&p.skill_id), so if start_skill fails the
preference remains true; change the flow to revert setup_complete to false on
start_skill failure by calling set_setup_complete(&p.skill_id, false) in the
error path (or only set to true after a successful start), ensuring you handle
the async error from start_skill and still propagate the error from
handle_skills_enable; reference the functions handle_skills_enable,
engine.preferences().set_setup_complete, and engine.start_skill when making the
change.
src/openhuman/skills/qjs_skill_instance/instance.rs (1)

259-264: Minor: String-contains check for logging is fragile but acceptable.

The detection of non-null credentials via !start_args.contains("\"oauth\":null") works but could be brittle if JSON formatting changes. Since this is only for informational logging and doesn't affect control flow, it's fine as-is.

A slightly more robust alternative would be to check the serde_json::Value before serialization:

💡 Optional improvement
     let start_args = build_start_credentials_arg(&data_dir);
+    // For logging: check the parsed values before serialization
+    let oauth_path = data_dir.join("oauth_credential.json");
+    let auth_path = data_dir.join("auth_credential.json");
+    let has_oauth = oauth_path.exists() && std::fs::read_to_string(&oauth_path).map(|s| !s.trim().is_empty()).unwrap_or(false);
+    let has_auth = auth_path.exists() && std::fs::read_to_string(&auth_path).map(|s| !s.trim().is_empty()).unwrap_or(false);
     log::info!(
         "[skill:{}] Calling start() with credentials (oauth={}, auth={})",
         config.skill_id,
-        !start_args.contains("\"oauth\":null"),
-        !start_args.contains("\"auth\":null"),
+        has_oauth,
+        has_auth,
     );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/openhuman/skills/qjs_skill_instance/instance.rs` around lines 259 - 264,
The current info log in instance.rs uses fragile string contains checks on
start_args to infer credential presence; instead, parse or build the credentials
as a serde_json::Value (or inspect the existing Value used to produce
start_args) and check the actual keys/values (e.g., whether the "oauth" and
"auth" fields are null or missing) before logging; update the log in the start()
context to use config.skill_id and booleans derived from that serde_json::Value
rather than string.contains to make the informational message more robust.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/openhuman/skills/qjs_skill_instance/instance.rs`:
- Around line 259-264: The current info log in instance.rs uses fragile string
contains checks on start_args to infer credential presence; instead, parse or
build the credentials as a serde_json::Value (or inspect the existing Value used
to produce start_args) and check the actual keys/values (e.g., whether the
"oauth" and "auth" fields are null or missing) before logging; update the log in
the start() context to use config.skill_id and booleans derived from that
serde_json::Value rather than string.contains to make the informational message
more robust.

In `@src/openhuman/skills/schemas.rs`:
- Around line 902-910: handle_skills_enable currently calls
engine.preferences().set_setup_complete(&p.skill_id, true) before awaiting
engine.start_skill(&p.skill_id), so if start_skill fails the preference remains
true; change the flow to revert setup_complete to false on start_skill failure
by calling set_setup_complete(&p.skill_id, false) in the error path (or only set
to true after a successful start), ensuring you handle the async error from
start_skill and still propagate the error from handle_skills_enable; reference
the functions handle_skills_enable, engine.preferences().set_setup_complete, and
engine.start_skill when making the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2f6422a8-a87e-41cf-8299-8f83428ce654

📥 Commits

Reviewing files that changed from the base of the PR and between 6410db1 and e0c91cc.

📒 Files selected for processing (7)
  • src/openhuman/skills/preferences.rs
  • src/openhuman/skills/qjs_engine.rs
  • src/openhuman/skills/qjs_skill_instance/event_loop/mod.rs
  • src/openhuman/skills/qjs_skill_instance/event_loop/rpc_handlers.rs
  • src/openhuman/skills/qjs_skill_instance/instance.rs
  • src/openhuman/skills/qjs_skill_instance/js_handlers.rs
  • src/openhuman/skills/schemas.rs
💤 Files with no reviewable changes (1)
  • src/openhuman/skills/qjs_engine.rs

@graycyrus graycyrus merged commit a2fb111 into main Apr 10, 2026
13 of 14 checks passed
@senamakel senamakel deleted the fix/skill-states branch April 10, 2026 18:39
@coderabbitai coderabbitai Bot mentioned this pull request Apr 10, 2026
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants