feat(notes): attach selected Notes/Todos as Agent context (#6495)

* feat: add Notes and Todos as agent chat context

- Send selected Note/Todo IDs from the Notes UI
- Load selected notes with owner scoping in chat routes
- Format note content and checklist items for the model
- Inject selected notes as untrusted chat context
- Add a selected-context indicator beside Shell Access
- Keep selected context associated with the active chat session

* Improved selected notes context handling and discouragin redundant note lookup and permission requests

* Fix selected notes UI when switching chats

* fix(notes): harden selected context lifecycle and validation

---------

Co-authored-by: Alexandre Teixeira <111787685+alteixeira20@users.noreply.github.com>
This commit is contained in:
Beyrem Weslati
2026-10-13 18:57:19 +01:00
committed by GitHub
co-authored by Alexandre Teixeira
parent a8c147b238
commit 8feb50e894
9 changed files with 324 additions and 2 deletions
+68
View File
@@ -65,6 +65,8 @@ import { invalidateSettings } from './appConfig.js';
let _contextHeaderAnchorEl = null;
let _contextHeaderPopupCleanup = null;
let _pendingToolApproval = null;
const _selectedNoteIdsBySession = new Map();
let _pendingSelectedNoteIds = [];
let _lastPrivateBrowserUrl = '';
function _isPrivateBrowserTool(tool) {
const name = String(tool || '').toLowerCase();
@@ -240,6 +242,46 @@ import { invalidateSettings } from './appConfig.js';
_submitToolApprovalWhenIdle(_pendingToolApproval.approval_id);
});
window.addEventListener('odysseus:notes-context-selected', (event) => {
const noteIds = event.detail?.noteIds;
if (!Array.isArray(noteIds) || noteIds.length === 0) return;
const sessionId = sessionModule.getCurrentSessionId?.();
if (!sessionId && !sessionModule.hasPendingChat?.()) {
console.warn('Cannot attach Notes context: no active or pending chat');
return;
}
if (sessionId) {
_selectedNoteIdsBySession.set(sessionId, [...noteIds]);
} else {
_pendingSelectedNoteIds = [...noteIds];
}
if (event.detail) event.detail.accepted = true;
_renderSelectedNotesIndicator(noteIds);
});
window.addEventListener('odysseus:session-changed', (event) => {
const sessionId = event.detail?.sessionId;
if (sessionId && event.detail?.materialized && _pendingSelectedNoteIds.length) {
_selectedNoteIdsBySession.set(sessionId, _pendingSelectedNoteIds);
}
_pendingSelectedNoteIds = [];
_renderSelectedNotesIndicator(
sessionId ? (_selectedNoteIdsBySession.get(sessionId) || []) : []
);
});
document.getElementById('selected-notes-context-btn')?.addEventListener('click', () => {
const sessionId = sessionModule.getCurrentSessionId?.();
if (sessionId) {
_selectedNoteIdsBySession.delete(sessionId);
} else {
_pendingSelectedNoteIds = [];
}
_renderSelectedNotesIndicator([]);
});
function _fmtContextNumber(n) {
const v = Number(n || 0);
return v ? v.toLocaleString() : '?';
@@ -290,6 +332,26 @@ import { invalidateSettings } from './appConfig.js';
return Math.max(50, Math.min(95, Math.round(n)));
}
function _renderSelectedNotesIndicator(noteIds){
const button = document.getElementById('selected-notes-context-btn');
const count = document.getElementById('selected-notes-context-count');
if (!button || !count) return;
const total = Array.isArray(noteIds) ? noteIds.length : 0;
if(!total){
button.style.display = 'none';
count.textContent = '';
return;
}
count.textContent = `${total} note${total === 1 ? '' : 's'} selected`;
button.style.display = '';
button.title = `${total} selected Note/Todo${total === 1 ? '' : 's'} — click to clear`;
button.setAttribute(
'aria-label',
`Clear ${total} selected Note/Todo${total === 1 ? '' : 's'} context`
);
}
function _liveSessionModule() {
return (window.sessionModule && window.sessionModule.getCurrentSessionId)
? window.sessionModule
@@ -2497,6 +2559,12 @@ import { invalidateSettings } from './appConfig.js';
const fd = new FormData();
fd.append('message', approvalForSend ? '' : _finalMsgWithInject);
fd.append('session', streamSessionId);
const selectedNoteIds = _selectedNoteIdsBySession.get(streamSessionId) || [];
if(selectedNoteIds.length){
fd.append('selected_note_ids', JSON.stringify(selectedNoteIds))
}
if (approvalForSend) {
fd.append('tool_approval_id', approvalForSend.approval_id);
fd.append('tool_approval_decision', approvalForSend.decision);