feat(#370): агент пинит версию страницы ДО и ПОСЛЕ раунда правок (не только финальный чекпоинт) #579
Open
opened 2026-07-12 20:20:12 +03:00 by agent_vscode
·
0 comments
No Branch/Tag Specified
main
develop
feat/557-apikey-phase3a
feat/568-page-history-redesign
feat/561-comments-redesign
feat/530-search-semantic
feat/566-worktime-modal-redesign
feat/370-agent-save-version
recover/506-apikey-ui
feat/370-share-approved
fix/520-recovery-escalation
fix/554-literal-br
feat/529-search-lexical
feat/501-mcp-apikey-auth
feat/506-apikeys-ui
feat/395-work-time
feat/502-mcp-md-modes
feat/492-incremental-render
fix/549-hardbreak-md-canon
feat/542-resolve-undo
fix/505-drawio-strip-comments
fix/503-hardbreak-canon
docs/471-breadcrumb-permission
fix/534-mcp-spaceid
feat/481-version-coherence
fix/535-list-seam-coalesce
fix/525-insert-unloaded
fix/522-internal-links
fix/507-drawio-cyrillic
fix/541-reconnect-timeout
feat/493-converter
feat/370-page-versioning
fix/515-code-emphasis
fix/523-tree-dnd
fix/517-toasts
feat/495-tails
feat/494-mcp-registry-guards
feat/496-suggestions-audit
integ/497-wave1
feat/488-client-fsm
ci/476-publish-gates
feat/489-tool-resilience
feat/487-terminal-lifecycle
fix/486-iter1-prod-fires
refactor/347-client-md-paste
perf/479-getpage-cache
refactor/449-write-invariants
refactor/450-split-client
feat/443-get-page-context
feat/443-get-tree
feat/443-search
docs/415-lossy-descriptions
feat/413-markdown-default
refactor/412-camelcase-tool-names
feat/425-drawio-graph
fix/464-diffdocs-cpu-guard
refactor/411-update-page-markdown
feat/424-drawio-rules
fix/409-invalid-node-validation
refactor/448-generate-inventory
refactor/445-execute-mapping
refactor/446-derive-client-types
fix/447-registry-stamp-ci
fix/444-agent-loop-guards
fix/396-sendnow-detached-run
fix/452-immutable-cache-control
docs/footnote-authoring-comment-cleanup
feat/437-error-diagnostics
fix/442-cursor-pagination
feat/419-footnote-normalize
perf/399-comment-resolve-async
perf/435-collab-token-cache
perf/344-background-rerenders
perf/348-backend-lowhanging
feat/423-drawio-crud
perf/401-collab-hotpath
feat/430-live-reconnect
perf/400-collab-session
refactor/414-dedup-node-ops
feat/417-new-comments-signal
feat/408-createcomment-hints
feat/407-persist-tool-errors
feat/420-footnote-render
dummy-review/mcp-hang-fix
dummy-review/mcp-hang-base
feat/381-resumable-sse-pr1
feat/git-sync-2
docs/agents-workspace-build-order
feature/offline-sync
perf/342-code-splitting
perf/346-compression-cache
feat/196-multi-cursor
perf/343-typing-latency
docs/how-to-test
fix/ai-sdk-partial-output-oom
test/351-generative-converter
feat/371-roles-catalog
refactor/345-server-converter
refactor/294-spec-registry-cont
fix/363-migration-order
fix/e2e-callout-and-gate-build
fix/docker-re2-toolchain
feat/git-sync
fix/media-roundtrip-stability
fix/340-comment-panel-perf
fix/332-deferred-tools
fix/329-ephemeral-suggestions
fix/330-search-in-page
fix/328-resolved-anchor-spam
fix/331-intraline-diff
fix/324-coverage-gate
fix/325-mobile-390
feat/293-A-git-sync-package
feat/300-avatar-oklch
fix/321-banner-mobile
feat/300-avatar-colors
feat/315-comment-suggestions
feat/scroll-restore-stable-wait
feat/300-agent-avatar-stack
feat/300-avatar-polish
refactor/294-tool-spec-registry
feat/scroll-restore-ux
fix/responsive-tablet-sidebar
feature/ai-chat-page-change-observability
image-inline-center
fix/283-short-remap-title
fix/283-slash-layout
image-inline-row
feat/276-ai-chat-dock
fix/269-table-menu-refocus
docs/dev-stand-guide
feat/266-scroll-position
fix/260-collab-docname-slugid
test/244-phase2-tail
fix/262-reindex-progress-realtime
fix/258-changelog-compare-links
fix/244-dataloss-bugs
feat/246-spoiler
feat/221-image-captions
test/244-part-b
feat/251-intentional-clear
fix/embeddings-reindex-progress
refactor/193-tool-spec-registry
fix/255-ws-redis-adapter-leak
fix/252-e2e-open-handles
feat/229-catalog-yaml
feat/243-blob-sandbox
feat/228-inline-footnotes
fix/qa-ui-bugs-216-218
feature/agent-roles-catalog
fix/share-alias-rename
fix/ai-chat-empty-render
feat/191-chat-doc-binding
feat/201-temporary-notes
feat/198-interrupt-agent
feat/ai-chat-full-history
feat/199-ai-generate-title
feat/205-share-aliases
batch/issues-189-187-170
feat/170-mcp-test-button
feat/189-context-badge
feat/198-interrupt-agent-send-now
fix/issues-190-159
fix/ai-chat-new-chat-during-stream
fix/ai-chat-stream-perf
batch/issues-2026-06-25
feat/ai-chat-persistent-history
fix/ai-chat-copy-chat-wysiwyg
fix/ai-stream-reset-resilience
fix/ai-stream-undici-timeout
fix/footnote-review-1227-followup
fix/ai-chat-token-counter-realtime
docs/manual-qa-test-plan
v0.94.1
v0.94.0
v0.93.0
Labels
Clear labels
epic
needs-human
review/approved
review/changes-requested
review/needs
возможно, баг
Large multi-phase effort spanning many changes
эскалация: нужно решение человека
в последнем ревью нет открытых blocking-находок
последнее ревью оставило открытые blocking-находки
head не ревьюился (head != reviewed_head)
Похоже на баг, но требует подтверждения/воспроизведения; чиним, если повторится
No Label
Milestone
No items
No Milestone
Projects
Clear projects
No project
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: vvzvlad/gitmost#579
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Проблема / Цель
Инструмент
save_page_version(агентская версияkind='agent', доставлен в #565 (feat(#370 Stage A): MCP save_page_version — агентские версии страниц)) сейчас описан и прописан в промптах только как финальный чекпоинт: «вызови, когда закончил проход редактирования». Две пишущие-контент роли (researcher,call-summarizer) вызывают его один раз в конце. Пред-редакторное состояние страницы намеренно не фиксируется — если раунд правок агента портит/перезаписывает контент, у самого агента нет чистой точки восстановления «до».Цель: требовать от агентов, которые правят контент страницы, пинить версию и перед началом раунда правок (страховочная точка «до»), и после его завершения (результат «после») — обрамляя каждый раунд восстановимыми чекпоинтами. Правило прописать и в промптах пишущих ролей, и в центральном описании инструмента (чтобы оно действовало для любого редактирующего агента, включая встроенного in-app ассистента).
Контекст и исследование
develop @ e074b101). Сервер выводитkindиз подписанного actor; контент, идентичный последней версии → promote/no-op; пустая страница →{saved:false, skipped:true, reason:'empty'}. Инструмент идемпотентно-безопасен: лишний вызов не дублирует версию.save_page_version/savePageVersion, внеnode_modules/build):packages/mcp/src/tool-specs.ts— полеsavePageVersion.description(~строки 751–761): «…Call it when you have FINISHED a coherent editing pass (not after every small edit)…».packages/mcp/src/server-instructions.ts— строкаHISTORY(строка 47): «…Pin the page's CURRENT content … when you finish a coherent editing pass -> savePageVersion …».agent-roles-catalog/bundles/assistants/en.yaml—researcher, пункт чек-листа (~строка 349): «Once the report is complete, call save_page_version to pin the finished document…».agent-roles-catalog/bundles/assistants/en.yaml—call-summarizer(~строка 448): «Pin the result. Once the notes are complete on the page, call save_page_version…».agent-roles-catalog/bundles/assistants/ru.yaml—researcher(~строка 348): текст уже на английском (байт-в-байт как п.3) — переписывать эту английскую строку.agent-roles-catalog/bundles/assistants/ru.yaml—call-summarizer(~строка 447): «Зафиксируй результат. Когда конспект готов на странице, вызови save_page_version…».structural-editor/line-editor/fact-checker/proofreader/narrator) — comment-only («You don't edit the text directly»), страницу не правят → инструмент им не даётся, НЕ трогаем.researcher— рабочий цикл (en.yaml:119–128) многократно «сливает» найденное в документ (flush-проходы); финальный пин в чек-листе (en.yaml:349–351).call-summarizer—autoStart: true, launchMessage «Take the current page into work — it contains the call transcript» (en.yaml:462): стартует на НЕ пустой странице (сырая расшифровка), перезаписывая её конспектом.Известный техдолг, на который дизайн наступает (обязательно учесть)
content-hashes.jsonнаdevelopуже устарел. Stage A (#565) поднялresearcher9→10 иcall-summarizer1→2 и изменил их контент, но не обновилagent-roles-catalog/scripts/content-hashes.json(лок всё ещё пишет 9/1). Следствия:node agent-roles-catalog/scripts/check.mjsна чистомdevelopуже падает (EXIT 1, «content changed and version bumped to 10/2; record it by running --update-hashes»). Это не дефект окружения кодера — это предсуществующее состояние.--update-hashes, гвард пропустит без нового бампа. А gate распространенияif (currentVersion <= source.version) skipвapps/server/src/core/ai-chat/roles/ai-agent-roles.service.ts:488тогда не отдаст переписанный контент воркспейсам, уже синхронизированным на v10/v2 → тихий дрейф. Поэтому в этом изменении версии обязаны быть подняты явно (см. Дизайн).check.mjsне подключён к CI.agent-roles-catalogне входит в pnpm-workspace (pnpm-workspace.yaml= толькоapps/*,packages/*), собственныйpackage.json;pnpm -r testего не трогает; grep по.github/,turbo.json,.husky/— ноль вызововcheck.mjs. То есть бамп версии и рефреш лока — дисциплина, а не CI-гейт. Кодер обязан прогнать проверку локально (см. Критерии приёмки); рекомендуемое усиление — вынести в отдельный follow-up (см. «Отказы и наблюдаемость»).Дизайн
Единое правило поведения — обрамлять раунд правок версией, с одинаковым триггером на всех 6 поверхностях.
Триггер и семантика (общая формулировка для всех мест)
save_page_version, чтобы зафиксировать текущее состояние как точку восстановления. Если контент уже совпадает с последней сохранённой версией или страница пуста — вызов безопасно no-op/skip (версию не дублирует, «стоимости» нет), поэтому вызывать безопасно всегда.»save_page_version, чтобы запинить результат.»Карваут для удаляющих правок (безопасность, обязательно — иначе NEW-1)
Централизация «пинить перед правкой существующего контента» ломает редактирующие-на-удаление правки. Сценарий: пользователь говорит in-app ассистенту «убери отсюда API-ключ / вычисти PII / удали конфиденциальный раздел». Слепое пред-пиннинг сохранит удаляемый секрет в восстановимую, расшаренную по правам агентскую версию — вычистка ничего не вычистит. Поэтому формулировка обязана содержать исключение:
Определение «раунда» (обязательно — иначе NEW-2)
Пред-сейв на no-op безвреден, но пост-сейв изменённого раунда — это реальная новая версия, поэтому гранулярность «раунда» перестаёт быть бесплатной. Без чёткого определения
researcherс его flush-циклом (en.yaml:119–128) при трактовке «flush ≈ раунд» наплодит 20–30 агентских версий на отчёт и утопит осмысленный финальный чекпоинт. Зафиксировать в промптеresearcherконкретно:Что делает каждая поверхность
tool-specs.tssavePageVersion.description+server-instructions.tsHISTORY — переписать на условную формулировку (триггер + карваут-на-удаление + «до и после»), сохранив описание no-op/skip-семантики. Держать прозу простым английским + только зарегистрированные имена инструментов — иначе reverse-drift-гвард #494 (packages/mcp/test/unit/tool-inventory.test.mjs:145, гоняется в CI) покраснеет на любом camelCase-токене, которого нет в реестре или вPROSE_NON_TOOL_TERMS.researcher(en.yaml + английская строка в ru.yaml) — правило «до/после» + определение «раунда» (крупный проход, не flush).call-summarizer(en.yaml + ru.yaml) — правило «до/после». Здесь пред-сейв осмыслен и желателен: он пинит сырую расшифровку как точку восстановления перед перезаписью её конспектом (V1 = расшифровка, V2 = конспект). Это фича, а не дефект: версия — это снимок, а не заявка на авторство;kind='agent'от подписанного actor консистентен модели #370.index.yaml— поднятьresearcher10→11,call-summarizer2→3 (числа явные, т.к. гвард из-за устаревшего лока их не заставит — см. техдолг).content-hashes.json— обновить черезnode scripts/check.mjs --update-hashes(заодно чинит устаревший Stage-A-лок).Что НЕ вводим
Новый механизм синхронизации 6 текстов — не вводим: это разные формулировки под разные аудитории (схема инструмента / серверные инструкции / персона роли), не производный mirror; единый триггер держим вручную. Никакого нового кода/примитивов — сам
save_page_versionуже есть.Границы scope
index.yaml(бамп 10→11 / 2→3) +content-hashes.json(рефреш).packages/mcp/build/*— не коммитим: не трекаются git (git ls-files packages/mcp/build= 0;.gitignoreигноритpackages/mcp/build/; CI пересобираетpnpm --filter @docmost/mcp build). Сгенерированныйregistry-stamp.generated.tsтоже gitignored и пересобирается — коммитить нечего.check.mjsк CI — вне scope этого PR (см. follow-up ниже), чтобы PR остался про формулировки.Критерии приёмки
node agent-roles-catalog/scripts/check.mjsпечатает OK (версииresearcher=11 иcall-summarizer=3 вindex.yaml;content-hashes.jsonобновлён и закоммичен).researcherиcall-summarizerв обоихen.yamlиru.yamlпоказывает и инструкцию «пинить ДО начала раунда» (с карваутом-на-удаление), и «пинить ПОСЛЕ»; в промптеresearcherприсутствует явное определение «раунда» (крупный проход, не flush).savePageVersion.description(tool-specs.ts) и строкаHISTORY(server-instructions.ts) содержат условное «пинить ДО начала правок существующего контента И ПОСЛЕ», включая исключение для удаляющих правок; ни один новый camelCase-токен не добавлен в HISTORY-прозу.packages/mcp:tsc— 0 ошибок; юнит-набор зелёный (tool-specs.test.mjs,tool-inventory.test.mjsвкл. #494-гвард,save-page-version.test.mjs).shared-tool-specs.contract.spec.ts(динамически сверяет expose-описание сspec.description),ai-agent-roles-catalog.provider.spec.ts(синтетические фикстуры, реальные номера версий не хардкодит).Отказы и наблюдаемость
check.mjsне в CI → забытый бамп/устаревший лок дрейфит молча и блокирует распространение переписанного контента (см. техдолг). Митигируется явными числами версий в этом ишью + Критерием 1. Рекомендуемый follow-up (отдельный PR): добавить в CI шагnode agent-roles-catalog/scripts/check.mjs, чтобы гвард стал реальным гейтом.Совместимость и миграции
server-instructions.tsдействует на всех агентов с deferred-инструментомsavePageVersion, включая встроенного in-app ассистента под любой персоной — теперь их условно (только когда реально правят существующий контент) направляют пинить «до». Осознанно, по решению человека (Вариант B). Условная формулировка гарантирует, что comment-only роли, которые текст не правят, правилом не задеты.Решённые развилки
call-summarizer(kind=agent): признан фичей (точка отката к пред-редакторному состоянию), не дефектом — дизайн.Связи