feat(knowledge): rebuild the project notes panel as Project knowledge (#2973)

The panel stored notes, todos and plans inside one shared JSON file that
six unrelated domains also wrote to, synchronised itself through window
CustomEvents, and could only read plans. It is now Project knowledge:
server-owned storage with explicit routes, a store with rollback, a
section sidebar, plans that open and edit in place, and search across
all of it.

Notes and plans the user pins travel with every message sent in that
project. Pinning is project state, not an attachment to one message, so
it holds until unpinned and the work status panel names what is riding
along and can detach it.

Agent memory is added alongside, in two scopes: what is true about the
user, and what is true about this codebase. The split is not cosmetic —
a wrong project fact costs one project and is noticed, while a wrong
global fact quietly shapes every session everywhere and the user has no
code to check it against. It stays separate from notes so an agent
mistake cannot land in what the user wrote. Sessions receive an index of
titles only; bodies are read on demand, because an index carrying full
text grows until it crowds out the conversation.

Deciding what a session must be told, and whether it has been told, now
lives on the server. The client owned it before, which meant sessions
started without a UI — scheduled tasks, sessions the agent dispatches —
received nothing at all, and a tab's record of what it had sent outlived
the conversation: after compaction the agent no longer held the block
while the tab went on believing it did. What was delivered is recorded
in the session's own metadata, and compaction restores it through the
runtime that already restores pinned messages, in the same turn.

Agent memory ships dark behind OPENCHAMBER_MEMORY_ENABLE: unset, there
is no tool, no routes, no session index, no settings row and no panel
tab. Absent rather than switched off, so nothing invites turning on a
feature that has not been announced. Pinned notes and plans are
unaffected and ship as normal.
This commit is contained in:
Bohdan Triapitsyn
2026-08-18 02:59:04 +03:00
committed by GitHub
parent 7611076436
commit 34e8a24b20
102 changed files with 10640 additions and 1630 deletions
+49 -10
View File
@@ -1008,12 +1008,12 @@ export const dict = {
'walkthrough.blocked.structuredOutput.descriptionUnknownModel': 'Le petit modèle ne prend pas en charge les réponses structurées nécessaires à un parcours.',
'contextRail.surface.plan.description': 'Voir le plan actuel',
'contextRail.surface.pr.description': 'Créer, relire et fusionner la pull request de la branche actuelle',
'contextRail.surface.notes.description': 'Notes, tâches et plans du projet',
'contextRail.surface.notes.description': 'Notes, tâches, plans et mémoire de l’agent pour le projet',
'contextRail.surface.context.description': 'Contexte de session et utilisation des tokens',
'contextRail.surface.browser.description': 'Navigateur web intégré',
'contextRail.surface.preview.description': 'Aperçu du serveur de développement',
'contextRail.surface.chat.description': 'Session ouverte côte à côte',
'contextRail.surface.notes': 'Notes du projet',
'contextRail.surface.notes': 'Connaissances du projet',
'contextRail.editorTree.toggle': 'Afficher/masquer l’arborescence de fichiers',
'contextPanel.browser.open': 'Ouvrir le panneau du navigateur',
'contextPanel.browser.addressAria': 'Adresse du navigateur',
@@ -1224,6 +1224,7 @@ export const dict = {
'planView.file.defaultName': 'plan',
'planView.title.default': 'Plan',
'planView.error.saveFailed': 'Échec de l\'enregistrement',
'planView.error.loadFailed': 'Impossible de charger ce plan',
'planView.error.previewUnavailable': 'Aperçu indisponible',
'planView.error.switchToEditMode': 'Passez en mode édition pour résoudre le problème.',
'planView.error.writeFailed': 'Échec de l\'écriture',
@@ -1306,11 +1307,46 @@ export const dict = {
'diffView.hunk.unsupported': "La préparation de sections individuelles n'est pas prise en charge dans cet environnement.",
'rightSidebar.contextNotesTodo.plan.defaultTitle': 'Plan',
'rightSidebar.contextNotesTodo.empty.selectProject': 'Sélectionnez un projet pour ajouter des notes et des tâches.',
'rightSidebar.contextNotesTodo.notes.title': 'Notes rapides - {project}',
'rightSidebar.contextNotesTodo.notes.placeholder': 'Capturez le contexte, les rappels ou les liens',
'rightSidebar.contextNotesTodo.todo.title': 'Faire',
'rightSidebar.contextNotesTodo.todo.itemsSingle': 'Article {count}',
'rightSidebar.contextNotesTodo.todo.itemsPlural': 'Articles {count}',
'rightSidebar.contextNotesTodo.notes.addAria': 'Ajouter une note',
'rightSidebar.contextNotesTodo.notes.empty': 'Aucune note pour le moment. Notez du contexte, des rappels ou des liens.',
'rightSidebar.contextNotesTodo.notes.actions.expand': 'Développer la note',
'rightSidebar.contextNotesTodo.notes.actions.collapse': 'Réduire la note',
'rightSidebar.contextNotesTodo.notes.actions.delete': 'Supprimer la note',
'rightSidebar.contextNotesTodo.notes.actions.pin': 'Épingler au contexte de l\'agent',
'rightSidebar.contextNotesTodo.notes.actions.unpin': 'Détacher du contexte de l\'agent',
'rightSidebar.contextNotesTodo.notes.source.selection': 'Depuis le chat',
'rightSidebar.contextNotesTodo.notes.source.agent': 'Depuis l\'agent',
'rightSidebar.contextNotesTodo.search.placeholder': 'Rechercher',
'rightSidebar.contextNotesTodo.search.clear': 'Effacer la recherche',
'rightSidebar.contextNotesTodo.search.noResults': 'Aucun résultat pour "{query}".',
'rightSidebar.contextNotesTodo.toast.deleteNoteFailed': 'Échec de la suppression de la note',
'rightSidebar.contextNotesTodo.toast.createNoteFailed': 'Échec de la création de la note',
'rightSidebar.contextNotesTodo.tabs.notes': 'Notes',
'rightSidebar.contextNotesTodo.tabs.todos': 'Tâches',
'rightSidebar.contextNotesTodo.tabs.plans': 'Plans',
'rightSidebar.contextNotesTodo.plans.actions.back': 'Retour aux plans',
'rightSidebar.contextNotesTodo.tabs.memory': 'Mémoire',
'rightSidebar.contextNotesTodo.sections.label': 'Sections du contexte du projet',
'rightSidebar.contextNotesTodo.sections.resize': 'Redimensionner la barre des sections',
'rightSidebar.contextNotesTodo.memory.scope.project': 'Projet',
'rightSidebar.contextNotesTodo.memory.scope.label': 'Portée de la mémoire',
'rightSidebar.contextNotesTodo.memory.scope.global': 'À votre sujet',
'rightSidebar.contextNotesTodo.memory.type.fact': 'fait',
'rightSidebar.contextNotesTodo.memory.badge.new': 'nouveau',
'rightSidebar.contextNotesTodo.memory.flagged': 'Retenu — se lit comme une instruction',
'rightSidebar.contextNotesTodo.memory.badge.changed': 'modifié',
'rightSidebar.contextNotesTodo.memory.type.preference': 'préférence',
'rightSidebar.contextNotesTodo.memory.type.reference': 'référence',
'rightSidebar.contextNotesTodo.memory.actions.delete': 'Oublier cette mémoire',
'rightSidebar.contextNotesTodo.memory.actions.editTitle': 'Titre de la mémoire',
'rightSidebar.contextNotesTodo.memory.actions.editBody': 'Texte de la mémoire',
'rightSidebar.contextNotesTodo.memory.toast.saveFailed': 'Impossible d’enregistrer la mémoire',
'rightSidebar.contextNotesTodo.memory.toast.deleteFailed': 'Impossible d’oublier la mémoire',
'rightSidebar.contextNotesTodo.memory.empty.nothing': 'L’agent n’a encore rien enregistré ici.',
'rightSidebar.contextNotesTodo.memory.empty.noMatches': 'Aucune mémoire enregistrée ne correspond à votre recherche.',
'rightSidebar.contextNotesTodo.memory.empty.noProject': 'Ouvrez un projet pour voir ce que l’agent en retient.',
'rightSidebar.contextNotesTodo.memory.empty.unavailable': 'Impossible de charger la mémoire enregistrée. Rien n’est perdu — réessayez.',
'rightSidebar.contextNotesTodo.todo.clearCompleted': 'Effacer terminé',
'rightSidebar.contextNotesTodo.todo.inputPlaceholder': 'Ajouter une tâche',
'rightSidebar.contextNotesTodo.todo.addAria': 'Ajouter une tâche',
@@ -1321,13 +1357,9 @@ export const dict = {
'rightSidebar.contextNotesTodo.todo.actions.delete': 'Supprimer "{text}"',
'rightSidebar.contextNotesTodo.todo.actions.send': 'Envoyer "{text}"',
'rightSidebar.contextNotesTodo.todo.actions.reorder': 'Récommander "{text}"',
'rightSidebar.contextNotesTodo.todo.resizeAria': 'Redimensionner la liste de tâches',
'rightSidebar.contextNotesTodo.todo.sendMenu.currentSession': 'Envoyer à la session en cours',
'rightSidebar.contextNotesTodo.todo.sendMenu.newSession': 'Envoyer à une nouvelle session',
'rightSidebar.contextNotesTodo.todo.sendMenu.newWorktreeSession': 'Envoyer à une nouvelle session Worktree',
'rightSidebar.contextNotesTodo.plans.title': 'Forfaits',
'rightSidebar.contextNotesTodo.plans.filesSingle': 'Fichier {count}',
'rightSidebar.contextNotesTodo.plans.filesPlural': 'Fichiers {count}',
'rightSidebar.contextNotesTodo.plans.importFromFile': 'Importer un plan à partir d\'un fichier',
'rightSidebar.contextNotesTodo.plans.empty': 'Aucun plan enregistré pour l\'instant.',
'rightSidebar.contextNotesTodo.plans.deletePlan': 'Supprimer le forfait',
@@ -1347,6 +1379,7 @@ export const dict = {
'rightSidebar.contextNotesTodo.toast.sentToNewSession': 'Todo envoyé à une nouvelle session',
'rightSidebar.contextNotesTodo.toast.sentToNewWorktreeSession': 'Todo envoyé à une nouvelle session Worktree',
'rightSidebar.contextNotesTodo.toast.sendTodoFailed': 'Échec de l\'envoi de la tâche',
'rightSidebar.contextNotesTodo.toast.updatePlanFailed': 'Échec de la mise à jour du plan',
'rightSidebar.contextNotesTodo.toast.deletePlanFailed': 'Échec de la suppression du plan',
'rightSidebar.contextNotesTodo.toast.planFileEmpty': 'Le fichier de plan est vide',
'rightSidebar.contextNotesTodo.toast.importPlanFailed': 'Échec de l\'importation du plan',
@@ -3030,6 +3063,12 @@ export const dict = {
'chat.workStatus.subagent.askedQuestion': 'a posé une question',
'chat.workStatus.section.contextBreakdown': 'Sources de contexte',
'chat.workStatus.breakdown.skills': 'Compétences',
'chat.workStatus.breakdown.pinnedNote': 'note',
'chat.workStatus.breakdown.unpin': 'Détacher du contexte',
'chat.workStatus.breakdown.pinnedPlan': 'plan',
'chat.workStatus.breakdown.memory': 'Mémoire de l’agent',
'chat.workStatus.breakdown.pinnedKnowledgeSingle': '{count} épinglé',
'chat.workStatus.breakdown.pinnedKnowledgePlural': '{count} épinglés',
'chat.workStatus.breakdown.mcp': 'Serveurs MCP',
'chat.workStatus.action.openChanges': 'Ouvrir les modifications',
'chat.workStatus.action.openGit': 'Ouvrir le panneau Git',