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
3 changes: 3 additions & 0 deletions frontend/src/api/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1977,6 +1977,9 @@ export interface ComposerIntegrationReference {
url?: string;
summaryExcerpt?: string;
includeTranscript?: boolean;
selectedExcerpt?: string;
selectedSourcePath?: string;
selectedRangeLabel?: string;
}

export interface ComposerSelectionSnapshot {
Expand Down
31 changes: 31 additions & 0 deletions frontend/src/components/agents/composer/agentComposerCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,20 @@ export interface AgentComposerIntegrationReference {
url?: string;
summaryExcerpt?: string;
includeTranscript?: boolean;
/**
* User-selected excerpt text captured from an artifact surface (ticket, PR,
* Granola note). Untrusted external context — never treated as instructions.
*/
selectedExcerpt?: string;
/** Human-readable location the excerpt was selected from (path/section). */
selectedSourcePath?: string;
/** Human-readable range/line label for the selection (e.g. "L12-L20"). */
selectedRangeLabel?: string;
}

/** Bound on stored selected-excerpt length so composer metadata stays small. */
export const COMPOSER_SELECTED_EXCERPT_MAX_LENGTH = 4000;

export interface AgentComposerArtifactReference {
artifactId: string;
kind: "plan" | string;
Expand Down Expand Up @@ -408,11 +420,30 @@ export function normalizeComposerIntegrationReferences(
...(typeof reference.includeTranscript === "boolean"
? { includeTranscript: reference.includeTranscript }
: {}),
...(normalizeSelectedExcerpt(reference.selectedExcerpt)
? { selectedExcerpt: normalizeSelectedExcerpt(reference.selectedExcerpt) }
: {}),
...(reference.selectedSourcePath?.trim()
? { selectedSourcePath: reference.selectedSourcePath.trim().slice(0, 300) }
: {}),
...(reference.selectedRangeLabel?.trim()
? { selectedRangeLabel: reference.selectedRangeLabel.trim().slice(0, 80) }
: {}),
});
}
return [...safeReferences.values()];
}

function normalizeSelectedExcerpt(
excerpt: string | undefined,
): string | undefined {
const trimmed = excerpt?.trim();
if (!trimmed) {
return undefined;
}
return trimmed.slice(0, COMPOSER_SELECTED_EXCERPT_MAX_LENGTH);
}

function parseIntegrationTriggerQuery(
query: string,
): { kind: AgentComposerIntegrationKind; query: string } | null {
Expand Down
3 changes: 3 additions & 0 deletions src-tauri/src/application/agent_conversation_granola_note.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,9 @@ pub fn assigned_note_to_composer_reference(
url: link.note_url.clone(),
summary_excerpt: link.summary_markdown.clone(),
include_transcript: Some(link.include_transcript),
selected_excerpt: None,
selected_source_path: None,
selected_range_label: None,
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ fn granola_reference() -> ComposerIntegrationReference {
url: Some("https://granola.ai/notes/not_1234567890ABCD".to_string()),
summary_excerpt: Some("Initial summary".to_string()),
include_transcript: Some(true),
selected_excerpt: None,
selected_source_path: None,
selected_range_label: None,
}
}

Expand Down
3 changes: 3 additions & 0 deletions src-tauri/src/application/agent_conversation_jira_issue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ pub fn assigned_issue_to_composer_reference(
url: link.issue_url.clone(),
summary_excerpt: None,
include_transcript: None,
selected_excerpt: None,
selected_source_path: None,
selected_range_label: None,
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ fn jira_ref(key: &str) -> ComposerIntegrationReference {
url: Some(format!("https://jira.test/browse/{key}")),
summary_excerpt: None,
include_transcript: None,
selected_excerpt: None,
selected_source_path: None,
selected_range_label: None,
}
}

Expand All @@ -36,6 +39,9 @@ fn confluence_ref(id: &str) -> ComposerIntegrationReference {
url: Some(format!("https://jira.test/wiki/{id}")),
summary_excerpt: None,
include_transcript: None,
selected_excerpt: None,
selected_source_path: None,
selected_range_label: None,
}
}

Expand Down
3 changes: 3 additions & 0 deletions src-tauri/src/application/agent_conversation_linear_issue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,9 @@ pub fn assigned_issue_to_composer_reference(
url: link.issue_url.clone(),
summary_excerpt: None,
include_transcript: None,
selected_excerpt: None,
selected_source_path: None,
selected_range_label: None,
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ fn linear_ref(id: &str, key: Option<&str>) -> ComposerIntegrationReference {
url: key.map(|value| format!("https://linear.app/acme/issue/{value}/example")),
summary_excerpt: None,
include_transcript: None,
selected_excerpt: None,
selected_source_path: None,
selected_range_label: None,
}
}

Expand All @@ -106,6 +109,9 @@ fn jira_ref(key: &str) -> ComposerIntegrationReference {
url: Some(format!("https://jira.test/browse/{key}")),
summary_excerpt: None,
include_transcript: None,
selected_excerpt: None,
selected_source_path: None,
selected_range_label: None,
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,9 @@ fn integration_ref(
url: None,
summary_excerpt: None,
include_transcript: None,
selected_excerpt: None,
selected_source_path: None,
selected_range_label: None,
}
}

Expand Down
6 changes: 6 additions & 0 deletions src-tauri/src/application/atlassian_integration_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1211,6 +1211,9 @@ fn reference_from_atlassian_url(
url: Some(format!("{site_origin}/browse/{key}")),
summary_excerpt: None,
include_transcript: None,
selected_excerpt: None,
selected_source_path: None,
selected_range_label: None,
});
}
let page_id = confluence_page_id_from_uri(&uri, &segments)?;
Expand All @@ -1223,6 +1226,9 @@ fn reference_from_atlassian_url(
url: Some(raw_url.to_string()),
summary_excerpt: None,
include_transcript: None,
selected_excerpt: None,
selected_source_path: None,
selected_range_label: None,
})
}

Expand Down
30 changes: 30 additions & 0 deletions src-tauri/src/application/atlassian_integration_service_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,9 @@ fn atlassian_reference(id: impl Into<String>) -> ComposerIntegrationReference {
url: None,
summary_excerpt: None,
include_transcript: None,
selected_excerpt: None,
selected_source_path: None,
selected_range_label: None,
}
}

Expand Down Expand Up @@ -705,6 +708,9 @@ async fn fetch_resource_content_routes_to_client() {
url: None,
summary_excerpt: None,
include_transcript: None,
selected_excerpt: None,
selected_source_path: None,
selected_range_label: None,
})
.await
.expect("fetch should succeed");
Expand Down Expand Up @@ -932,6 +938,9 @@ async fn project_listing_methods_require_enabled_settings() {
url: None,
summary_excerpt: None,
include_transcript: None,
selected_excerpt: None,
selected_source_path: None,
selected_range_label: None,
})
.await
.unwrap_err(),
Expand Down Expand Up @@ -971,6 +980,9 @@ async fn expand_references_returns_message_when_disabled() {
url: None,
summary_excerpt: None,
include_transcript: None,
selected_excerpt: None,
selected_source_path: None,
selected_range_label: None,
}],
)
.await;
Expand All @@ -995,6 +1007,9 @@ async fn expand_references_skips_non_atlassian_and_reports_fetch_errors() {
url: None,
summary_excerpt: None,
include_transcript: None,
selected_excerpt: None,
selected_source_path: None,
selected_range_label: None,
},
ComposerIntegrationReference {
provider: "atlassian".to_string(),
Expand All @@ -1005,6 +1020,9 @@ async fn expand_references_skips_non_atlassian_and_reports_fetch_errors() {
url: None,
summary_excerpt: None,
include_transcript: None,
selected_excerpt: None,
selected_source_path: None,
selected_range_label: None,
},
],
)
Expand Down Expand Up @@ -1034,6 +1052,9 @@ async fn expand_references_reports_fetch_error_as_skipped() {
url: None,
summary_excerpt: None,
include_transcript: None,
selected_excerpt: None,
selected_source_path: None,
selected_range_label: None,
}],
)
.await;
Expand All @@ -1060,6 +1081,9 @@ async fn expand_references_truncates_large_resource_body() {
url: None,
summary_excerpt: None,
include_transcript: None,
selected_excerpt: None,
selected_source_path: None,
selected_range_label: None,
}],
)
.await;
Expand Down Expand Up @@ -1534,6 +1558,9 @@ async fn empty_client_returns_happy_path_stubs() {
url: None,
summary_excerpt: None,
include_transcript: None,
selected_excerpt: None,
selected_source_path: None,
selected_range_label: None,
},
)
.await
Expand Down Expand Up @@ -1608,6 +1635,9 @@ async fn unavailable_client_propagates_reason() {
url: None,
summary_excerpt: None,
include_transcript: None,
selected_excerpt: None,
selected_source_path: None,
selected_range_label: None,
}
)
.await
Expand Down
3 changes: 3 additions & 0 deletions src-tauri/src/application/automation/provisioning_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,9 @@ fn automation_run_start_request_maps_to_manual_start_input() {
url: None,
summary_excerpt: None,
include_transcript: None,
selected_excerpt: None,
selected_source_path: None,
selected_range_label: None,
}];
request.composer_artifact_references = vec![ComposerArtifactReference {
artifact_id: "artifact-1".to_string(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ fn live_reference_merge_prefers_current_references_and_deduplicates_inherited_id
url: None,
summary_excerpt: None,
include_transcript: None,
selected_excerpt: None,
selected_source_path: None,
selected_range_label: None,
};
let inherited_duplicate = ComposerIntegrationReference {
title: Some("Older title".to_string()),
Expand All @@ -42,6 +45,9 @@ fn live_reference_merge_prefers_current_references_and_deduplicates_inherited_id
url: None,
summary_excerpt: None,
include_transcript: None,
selected_excerpt: None,
selected_source_path: None,
selected_range_label: None,
};

let merged = super::merge_conversation_integration_references(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,9 @@ fn queue_reference_merge_uses_the_live_deduplication_seam() {
url: None,
summary_excerpt: None,
include_transcript: None,
selected_excerpt: None,
selected_source_path: None,
selected_range_label: None,
};
let current = ComposerIntegrationReference {
title: Some("Queued current".to_string()),
Expand Down Expand Up @@ -622,6 +625,9 @@ fn provider_switch_send_options_for_queued_message_preserve_payload() {
url: None,
summary_excerpt: None,
include_transcript: None,
selected_excerpt: None,
selected_source_path: None,
selected_range_label: None,
}];
message.composer_artifact_references = vec![ComposerArtifactReference {
artifact_id: "artifact-1".to_string(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ fn clickup_reference(id: impl Into<String>) -> ComposerIntegrationReference {
url: None,
summary_excerpt: None,
include_transcript: None,
selected_excerpt: None,
selected_source_path: None,
selected_range_label: None,
}
}

Expand Down Expand Up @@ -595,6 +598,9 @@ async fn prompt_expansion_renders_clickup_task_and_reports_zero_budget() {
url: None,
summary_excerpt: None,
include_transcript: None,
selected_excerpt: None,
selected_source_path: None,
selected_range_label: None,
};

let rendered = service
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ fn note_reference(include_transcript: Option<bool>) -> ComposerIntegrationRefere
url: Some("https://granola.ai/notes/not_1234567890ABCD".to_string()),
summary_excerpt: Some("Summary decisions".to_string()),
include_transcript,
selected_excerpt: None,
selected_source_path: None,
selected_range_label: None,
}
}

Expand Down
3 changes: 3 additions & 0 deletions src-tauri/src/application/granola_integration_prompt_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,9 @@ fn note_reference(include_transcript: Option<bool>) -> ComposerIntegrationRefere
url: Some("https://granola.ai/notes/not_1234567890ABCD".to_string()),
summary_excerpt: Some("Summary decisions".to_string()),
include_transcript,
selected_excerpt: None,
selected_source_path: None,
selected_range_label: None,
}
}

Expand Down
Loading
Loading