Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/awareness-follow-up.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Awareness follow-up

The desktop Awareness page currently groups cursor context and opt-in correction suggestions. It does not yet implement application-awareness controls, expression learning, or the complete correction system requested by the user.
The desktop Awareness page groups cursor context, opt-in correction suggestions, and an initial opt-in expression-learning implementation. The initial implementation learns only a small set of global formatting directions, stores abstract evidence locally, and applies stable directions to future dictation prompts. It does not yet implement application-awareness controls, per-app/category profiles, a user blacklist, or the complete correction system requested by the user.

## Reference and evidence

Expand All @@ -11,7 +11,7 @@ The reviewed implementation separates app/control context, cursor context, corre
## Required implementation

- Define independently controlled, opt-in learning and capture boundaries; keep secure fields, sensitive apps, uncertain target changes and blacklist entries excluded. Describe when contextual text may be sent to the configured model.
- Extend bounded, locally observed edits to distinguish content changes, lexical corrections, formatting-only edits and ambiguous cases. Do not learn facts, sensitive values or uncertain target edits as style.
- Persist only bounded abstract expression evidence; define migration, corruption handling, retention, clear/reset semantics and concurrency. Require repeated, consistent evidence across days before applying a profile, with clear app/category/global precedence and decay. Ensure style directives cannot override facts, explicit corrections or user instructions.
- The initial implementation accepts only uniquely anchored formatting edits, rejects sensitive or content-changing observations, and stores bounded text-free global evidence. Opt-out stops observation and new-session application; users can clear the profile. Mixed edits and richer classification remain unsupported.
- Extend the profile to app/category scopes with explicit precedence and decay, plus a user-maintained exclusion list. Validate reset and opt-out races across all hosts, and ensure form directives cannot override facts, explicit corrections or user instructions.
- Separate correction suggestion detection from style learning; retain explicit user confirmation for learned vocabulary. Cover conflicting candidates and repeated sessions without treating one edit as a stable habit.
- Integrate the controls into the single Awareness page and exercise opt-out, clearing, app exclusion, restart and prompt behavior across supported hosts. Add deterministic classification, persistence, safety, stale-observer and native integration tests before enabling the feature.
371 changes: 361 additions & 10 deletions openless-all/app/crates/openless-core/src/api.rs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -802,6 +802,13 @@ pub const PREFERENCE_FIELDS: &[PreferenceField] = &[
shape: PreferenceShape::Boolean,
reason: "local_consent_or_secret",
},
PreferenceField {
rust_name: "expression_learning_enabled",
key: "expressionLearningEnabled",
class: PreferenceClass::Excluded,
shape: PreferenceShape::Boolean,
reason: "local_consent_or_secret",
},
PreferenceField {
rust_name: "vocabulary_learning_settings",
key: "vocabularyLearningSettings",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,26 @@ fn testing_features_opt_in_is_never_exported_or_accepted_from_another_device() {
);
}

#[test]
fn expression_learning_consent_is_never_accepted_from_another_device() {
let mut invalid = sample().documents().clone();
invalid.documents.push(LogicalDocument {
id: "expressionLearningEnabled".into(),
kind: DocumentKind::Preferences,
schema_version: 1,
value: json!(true),
});
assert_eq!(
validate_sync_documents(invalid, Revision::new(3)).unwrap_err(),
DocumentError::ExcludedField
);
}

#[test]
fn controlled_export_keeps_real_service_keys_but_excludes_local_secrets_and_grants() {
let mut data = channel_snapshot();
data.preferences = SecretJson::new(
json!({"themeMode":"dark","remoteInputPin":"fixture-pin-never-export","codingAgentEnabled":true,"cursorContextEnabled":true,"vocabularyLearningEnabled":true,"marketplaceDevLogin":"fixture-login","splashSeenVersion":"2","activeLlmProvider":"llm-one","codingAgentExe":"/fixture/never-run","futureAppearance":{"accent":"violet"}}),
json!({"themeMode":"dark","remoteInputPin":"fixture-pin-never-export","codingAgentEnabled":true,"cursorContextEnabled":true,"vocabularyLearningEnabled":true,"expressionLearningEnabled":true,"marketplaceDevLogin":"fixture-login","splashSeenVersion":"2","activeLlmProvider":"llm-one","codingAgentExe":"/fixture/never-run","futureAppearance":{"accent":"violet"}}),
);
let exported = export_snapshot(data).unwrap();
let raw = serde_json::to_string(exported.documents.documents()).unwrap();
Expand All @@ -172,6 +187,7 @@ fn controlled_export_keeps_real_service_keys_but_excludes_local_secrets_and_gran
assert!(!raw.contains("codingAgentEnabled"));
assert!(raw.contains("cursorContextEnabled"));
assert!(!raw.contains("vocabularyLearningEnabled"));
assert!(!raw.contains("expressionLearningEnabled"));
assert!(!raw.contains("fixture-login"));
assert!(!raw.contains("activeLlmProvider"));
assert!(raw.contains("futureAppearance"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,8 @@ impl DictationContext {
| DictationOutputTarget::Qa
| DictationOutputTarget::VoiceEdit
),
observe_edits: preferences.vocabulary_learning_enabled,
observe_edits: preferences.vocabulary_learning_enabled
|| preferences.expression_learning_enabled,
// An undecided Android capture must not stream text into the
// foreground app before the terminal gesture classifies it.
streaming: !matches!(
Expand Down
Loading
Loading