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
@@ -1189,12 +1189,12 @@ export const dict: Record<I18nKey, string> = {
"walkthrough.blocked.structuredOutput.descriptionUnknownModel": "O modelo pequeno não suporta as respostas estruturadas que um percurso exige.",
"contextRail.surface.plan.description": "Ver o plano atual",
"contextRail.surface.pr.description": "Crie, revise e faça merge do pull request do branch atual",
"contextRail.surface.notes.description": "Notas, tarefas e planos do projeto",
"contextRail.surface.notes.description": "Notas, tarefas, planos e memória do agente do projeto",
"contextRail.surface.context.description": "Contexto da sessão e uso de tokens",
"contextRail.surface.browser.description": "Navegador web integrado",
"contextRail.surface.preview.description": "Pré-visualização do servidor de desenvolvimento",
"contextRail.surface.chat.description": "Sessão aberta lado a lado",
"contextRail.surface.notes": "Notas do projeto",
"contextRail.surface.notes": "Conhecimento do projeto",
"contextRail.editorTree.toggle": "Alternar árvore de arquivos",
"contextPanel.browser.open": "Abrir painel do navegador",
"contextPanel.browser.addressAria": "Endereço do navegador",
@@ -1425,6 +1425,7 @@ export const dict: Record<I18nKey, string> = {
"planView.file.defaultName": "plano",
"planView.title.default": "Plano",
"planView.error.saveFailed": "Não foi possível salvar",
"planView.error.loadFailed": "Não foi possível carregar este plano",
"planView.error.previewUnavailable": "Pré-visualização indisponível",
"planView.error.switchToEditMode": "Alterne para o modo de edição para resolver o problema.",
"planView.error.writeFailed": "Não foi possível gravar",
@@ -1519,11 +1520,46 @@ export const dict: Record<I18nKey, string> = {
"diffView.hunk.unsupported": "A preparação de trechos individuais não é suportada neste ambiente.",
"rightSidebar.contextNotesTodo.plan.defaultTitle": "Plano",
"rightSidebar.contextNotesTodo.empty.selectProject": "Selecione um projeto para adicionar notas e tarefas pendentes.",
"rightSidebar.contextNotesTodo.notes.title": "Notas rápidas — {project}",
"rightSidebar.contextNotesTodo.notes.placeholder": "Capture contexto, lembretes ou links",
"rightSidebar.contextNotesTodo.todo.title": "Tarefas pendentes",
"rightSidebar.contextNotesTodo.todo.itemsSingle": "{count} elemento",
"rightSidebar.contextNotesTodo.todo.itemsPlural": "{count} elementos",
"rightSidebar.contextNotesTodo.notes.addAria": "Adicionar nota",
"rightSidebar.contextNotesTodo.notes.empty": "Ainda não há notas. Registre contexto, lembretes ou links.",
"rightSidebar.contextNotesTodo.notes.actions.expand": "Expandir nota",
"rightSidebar.contextNotesTodo.notes.actions.collapse": "Recolher nota",
"rightSidebar.contextNotesTodo.notes.actions.delete": "Excluir nota",
"rightSidebar.contextNotesTodo.notes.actions.pin": "Fixar no contexto do agente",
"rightSidebar.contextNotesTodo.notes.actions.unpin": "Desafixar do contexto do agente",
"rightSidebar.contextNotesTodo.notes.source.selection": "Do chat",
"rightSidebar.contextNotesTodo.notes.source.agent": "Do agente",
"rightSidebar.contextNotesTodo.search.placeholder": "Buscar",
"rightSidebar.contextNotesTodo.search.clear": "Limpar busca",
"rightSidebar.contextNotesTodo.search.noResults": "Nada corresponde a \"{query}\".",
"rightSidebar.contextNotesTodo.toast.deleteNoteFailed": "Falha ao excluir a nota",
"rightSidebar.contextNotesTodo.toast.createNoteFailed": "Falha ao criar a nota",
"rightSidebar.contextNotesTodo.tabs.notes": "Notas",
"rightSidebar.contextNotesTodo.tabs.todos": "Tarefas",
"rightSidebar.contextNotesTodo.tabs.plans": "Planos",
"rightSidebar.contextNotesTodo.plans.actions.back": "Voltar aos planos",
"rightSidebar.contextNotesTodo.tabs.memory": "Memória",
"rightSidebar.contextNotesTodo.sections.label": "Seções do contexto do projeto",
"rightSidebar.contextNotesTodo.sections.resize": "Redimensionar a barra de seções",
"rightSidebar.contextNotesTodo.memory.scope.project": "Projeto",
"rightSidebar.contextNotesTodo.memory.scope.label": "Escopo da memória",
"rightSidebar.contextNotesTodo.memory.scope.global": "Sobre você",
"rightSidebar.contextNotesTodo.memory.type.fact": "fato",
"rightSidebar.contextNotesTodo.memory.badge.new": "novo",
"rightSidebar.contextNotesTodo.memory.flagged": "Retido do agente — parece uma instrução",
"rightSidebar.contextNotesTodo.memory.badge.changed": "alterado",
"rightSidebar.contextNotesTodo.memory.type.preference": "preferência",
"rightSidebar.contextNotesTodo.memory.type.reference": "referência",
"rightSidebar.contextNotesTodo.memory.actions.delete": "Esquecer esta memória",
"rightSidebar.contextNotesTodo.memory.actions.editTitle": "Título da memória",
"rightSidebar.contextNotesTodo.memory.actions.editBody": "Texto da memória",
"rightSidebar.contextNotesTodo.memory.toast.saveFailed": "Não foi possível salvar a memória",
"rightSidebar.contextNotesTodo.memory.toast.deleteFailed": "Não foi possível esquecer a memória",
"rightSidebar.contextNotesTodo.memory.empty.nothing": "O agente ainda não guardou nada aqui.",
"rightSidebar.contextNotesTodo.memory.empty.noMatches": "Nenhuma memória guardada corresponde à sua busca.",
"rightSidebar.contextNotesTodo.memory.empty.noProject": "Abra um projeto para ver o que o agente lembra sobre ele.",
"rightSidebar.contextNotesTodo.memory.empty.unavailable": "Não foi possível carregar a memória guardada. Nada foi perdido — tente novamente.",
"rightSidebar.contextNotesTodo.todo.clearCompleted": "Limpar completadas",
"rightSidebar.contextNotesTodo.todo.inputPlaceholder": "Adicione uma tarefa pendente",
"rightSidebar.contextNotesTodo.todo.addAria": "Adicionar tarefa pendente",
@@ -1534,13 +1570,9 @@ export const dict: Record<I18nKey, string> = {
"rightSidebar.contextNotesTodo.todo.actions.delete": "Excluir \"{text}\"",
"rightSidebar.contextNotesTodo.todo.actions.send": "Enviar \"{text}\"",
"rightSidebar.contextNotesTodo.todo.actions.reorder": "Reordenar \"{text}\"",
"rightSidebar.contextNotesTodo.todo.resizeAria": "Redimensionar lista de tarefas",
"rightSidebar.contextNotesTodo.todo.sendMenu.currentSession": "Enviar à sessão atual",
"rightSidebar.contextNotesTodo.todo.sendMenu.newSession": "Enviar a uma nova sessão",
"rightSidebar.contextNotesTodo.todo.sendMenu.newWorktreeSession": "Enviar a uma nova sessão de worktree",
"rightSidebar.contextNotesTodo.plans.title": "Planos",
"rightSidebar.contextNotesTodo.plans.filesSingle": "{count} arquivo",
"rightSidebar.contextNotesTodo.plans.filesPlural": "{count} arquivos",
"rightSidebar.contextNotesTodo.plans.importFromFile": "Importar plano de arquivo",
"rightSidebar.contextNotesTodo.plans.empty": "Ainda não há planos salvos.",
"rightSidebar.contextNotesTodo.plans.deletePlan": "Excluir plano",
@@ -1560,6 +1592,7 @@ export const dict: Record<I18nKey, string> = {
"rightSidebar.contextNotesTodo.toast.sentToNewSession": "Tarefa enviada para uma nova sessão",
"rightSidebar.contextNotesTodo.toast.sentToNewWorktreeSession": "Tarefa enviada para uma nova sessão de worktree",
"rightSidebar.contextNotesTodo.toast.sendTodoFailed": "Não foi possível enviar a tarefa",
"rightSidebar.contextNotesTodo.toast.updatePlanFailed": "Falha ao atualizar o plano",
"rightSidebar.contextNotesTodo.toast.deletePlanFailed": "Não foi possível excluir o plano",
"rightSidebar.contextNotesTodo.toast.planFileEmpty": "O arquivo do plano está vazio",
"rightSidebar.contextNotesTodo.toast.importPlanFailed": "Não foi possível importar o plano",
@@ -3033,6 +3066,12 @@ export const dict: Record<I18nKey, string> = {
'chat.workStatus.subagent.askedQuestion': 'fez uma pergunta',
'chat.workStatus.section.contextBreakdown': 'Fontes de contexto',
'chat.workStatus.breakdown.skills': 'Habilidades',
'chat.workStatus.breakdown.pinnedNote': 'nota',
'chat.workStatus.breakdown.unpin': 'Desafixar do contexto',
'chat.workStatus.breakdown.pinnedPlan': 'plano',
'chat.workStatus.breakdown.memory': 'Memória do agente',
'chat.workStatus.breakdown.pinnedKnowledgeSingle': '{count} fixado',
'chat.workStatus.breakdown.pinnedKnowledgePlural': '{count} fixados',
'chat.workStatus.breakdown.mcp': 'Servidores MCP',
'chat.workStatus.action.openChanges': 'Abrir alterações',
'chat.workStatus.action.openGit': 'Abrir painel do Git',