feat(copilot): doc-management tools + Requesty/GLM reasoning-leak fix - #2
Merged
zebster-cmd merged 21 commits intoJul 15, 2026
Merged
Conversation
Initialize OpenSpec (spec-driven) and gitignore .claude/ (local Claude
Code state). Add the change 'fix-requesty-reasoning-leak' (proposal,
design, specs, tasks) for capability copilot-reasoning-separation:
isolate model reasoning ("thinking") from content and artifact text.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…acts Add ThinkTagSplitter (stateful, chunk-boundary-tolerant) + stripThinkTags. Wire it into the native adapter: streamText/streamObject route inline <think>…</think> onto the reasoning channel; text() excludes reasoning (emitReasoning:false). extractTextResponse now keeps only text parts and strips inline <think> from them, so code_artifact HTML is reasoning-free whether the model separates reasoning natively or emits it inline (e.g. GLM via Requesty). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Splitter: basic/multiple blocks, split-at-every-offset invariance, unterminated/partial tags, think-free passthrough. Integration: streamText/streamObject route inline <think> to the reasoning channel with clean content, text() excludes reasoning, extractTextResponse drops reasoning + strips inline tags, and code_artifact HTML is reasoning-free. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Proposal/design/specs/tasks for capabilities copilot-doc-properties and copilot-doc-linking: let the AI manage a doc's tags, custom properties, core metadata, journal/mode, favorite, and its links/backlinks — all via server-side YJS-direct writes (DatabaseWriter delta-push pattern). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add YjsDeltaWriter/applyYDocDelta (generalized DatabaseWriter delta-push, handles not-yet-existing docs), doc-properties-types (storage doc-id builders, property-type set, view shapes), and DocPropertiesReader: aggregates a doc's title/trash/tags (root doc), journal/mode/custom values (docProperties, resolved via docCustomPropertyInfo), and favorite (per-user userspace doc) into one resolved view. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fixtures build the four attribute docs (root meta, docProperties, docCustomPropertyInfo, favorites). Reader specs cover tag resolution (incl. unresolved-id fallback), journal/mode/custom values resolved to name+type, missing-row defaults, and per-user favorite state. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rty/favorite ops) Adds DocPropertiesWriter (extends YjsDeltaWriter) implementing the doc_properties_update op set: set_title/set_trash (root doc pages[]), set_journal/set_mode (docProperties), add_tag/remove_tag/create_tag (root doc tag options, name-or-id resolution with disambiguation errors), set_property/define_property (docCustomPropertyInfo, rejects select/multi-select), and set_favorite (per-user favorites doc, soft-delete on unfavorite). DocPropertiesCodec encodes/decodes custom:<id> string values by declared type. Ops touch up to four independent docs, so applyOps resolves and validates every op up front (including within-batch create_tag -> add_tag / define_property -> set_property chaining) before pushing any delta, then groups mutations per affected doc - giving all-or-nothing batch semantics without real cross-doc transactions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ava specs for DocPropertiesWriter covering: the string codec (text/number/checkbox/date/tags); set_title/set_trash/set_journal (incl. "" clearing and malformed-date rejection)/set_mode; create_tag/add_tag/remove_tag (by name and id, missing/ambiguous-name errors); define_property/set_property (undefined-property and unsupported-type errors); set_favorite (create + soft-delete); same-batch create_tag->add_tag chaining; mid-batch-invalid-op abort (no partial write); a round-trip across all four docs in one batch; and a CRDT concurrency test (writer delta + independent simulated client edit merge losslessly in both apply orders). Verified via a temporary tsx harness against src/prelude.ts (ava itself doesn't run on this machine's Node 24) - all 29 assertions green - and `tsc --noEmit`, `oxlint --deny-warnings`, and `eslint` clean on both new files. Marks openspec tasks.md groups 3, 4, and 9.1/9.2 done (9.1/9.2 covered at the writer/reader unit level; the doc_properties_update/read tool wrappers are separate Track A tasks). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ous-name paths
doc-properties-writer's add_tag/remove_tag cast page.get('tags') to a Y.Array
unconditionally, throwing a raw TypeError on legacy/externally-created docs
whose meta.pages[] entry has no tags field; ensureTagsArray() now creates and
attaches one on demand, mirroring the reader's tolerance. set_favorite always
wrote index: 'a0', colliding whenever more than one doc was favorited;
nextFavoriteIndex() now reads existing favorite indices and picks one that
sorts after them. Also removes the dead NotFoundException re-checks inside
the root-doc mutators (already guaranteed by the up-front pageMeta check),
and adds spec coverage for the missing-tags path and the remove_tag/
set_property ambiguous-name error paths (add_tag's was already covered).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DocLinksReader.read resolves a doc's outgoing links (block search, docId==X ∧ exists refDocId) and backlinks (aggregate on docId where refDocId==X), with best-effort title resolution via the indexer's doc table. Injects the real IndexerService but only calls search/aggregate, so tests pass a fake indexer instead of standing up a search provider. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DocLinksWriter.applyOps mutates a source doc's own binary (delta-against- state-vector push, via YjsDeltaWriter) to implement create_link (embed affine:embed-linked-doc block, default; or inline @-reference space-delta at an anchor block), remove_link, retarget_link, and create_doc_and_link (spins off + registers a new doc via DocWriter.createDoc, then links to it). Every op is validated against a read-only snapshot before any doc is created or delta pushed, so an invalid op in a batch aborts the whole batch with no partial write - mirroring DocPropertiesWriter. Includes the links-doc test fixture (page -> note -> paragraphs/embeds) and full writer specs covering every op, its doc-wide fallback search, missing-target/anchor errors, batch-abort semantics, and CRDT-merge concurrency safety. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Groups 5 (links reader) and 6 (links writer) are implemented and verified (tsx + typecheck); see the corresponding feat commits. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- create_doc_and_link (embed mode) now resolves and validates the source doc's note block during the up-front validation pass, before DocWriter.createDoc runs, so a source doc missing an affine:note block can no longer leave behind an orphaned, registered-but-unlinked doc. - remove_link/retarget_link given both an explicit blockId and a target doc id (targetDocId / fromTargetDocId) now verify the resolved embed block's prop:pageId actually matches before mutating/deleting it, throwing a clear error naming both ids on mismatch instead of silently acting on the wrong link. - Align the copilot-doc-linking spec's inline @-reference scenario with design.md's resolved decision: the anchor names a target block (no character-offset parameter) and the reference is appended at the end of that block. Verified via a throwaway tsx harness running all 21 ava specs (18 existing + 3 new) against Node 24, plus a clean `tsc --noEmit`/oxlint/eslint pass on the touched files. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the four AI-facing defineTool wrappers over the existing doc-properties
and doc-links readers/writers: doc_properties_read, doc_properties_update,
doc_links_read, doc_links_update. Mirrors the database-read/-update tool
shape (build<X>Handler + create<X>Tool, permission check via
PermissionAccess.doc().can('Doc.Read'|'Doc.Update'), thrown errors converted
to toolError).
doc_links_update owns name->id target resolution (deferred from
DocLinksWriter per design.md Decision 4/5): a target string that exactly
matches a doc id wins, otherwise it's matched against doc titles in the
workspace root's meta.pages[] via the new resolveDocIdsByTitle() helper on
doc-properties-reader.ts; zero/ambiguous matches surface as a toolError
before any write is attempted.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ava specs for the four tool wrappers: permission-denied -> toolError for all four; reader-returns-null and writer-throws -> toolError naming the doc id/op for doc_properties_read/update; doc_links_update's target resolution (exact id, exact title, missing target, ambiguous title listing candidate ids) and create_doc_and_link's root-doc-skip path. All fakes - no real storage/indexer touched. tsc clean; a scratch tsx harness (ava can't run on local Node 24) confirmed all 15 assertions pass before this file was written and the harness deleted. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ad-throw tests - Fix doc_links_update's top-level tool description to name the actual camelCase op fields (target/fromTarget/toTarget) instead of snake_case from_target/to_target, which a model could otherwise emit and have rejected by the discriminated union. - Parse the workspace root doc's page index once per doc_links_update invocation (buildPageIndexFromRoot) instead of re-parsing the whole Yjs binary per op via readPageMetaFromRoot/resolveDocIdsByTitle, cutting an N-op batch from up to 2N Y.Doc parses to 1. - Add tests proving doc_properties_read and doc_links_read convert a reader throw (not just a null return) into a toolError, mirroring database-tools.spec.ts's delegate-throw coverage. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a getDoc call counter to the fake DocReader and cover the buildPageIndexFromRoot single-parse refactor with a multi-op batch (create_link/remove_link/retarget_link, mixing by-id and by-title targets) plus an ambiguous-title case, both asserting a single root-doc fetch regardless of op count. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Export the four doc-properties/doc-links tool factories from plugins/copilot/tools/index.ts, add docPropertiesRead/docPropertiesUpdate/ docLinksRead/docLinksUpdate to PromptToolsSchema, and register their handlers in ToolRuntime.getTools (write tools gated behind env.dev || env.namespaces.canary, matching the database write tools; read tools ungated). DocPropertiesReader/DocPropertiesWriter/DocLinksWriter are provided and exported from core/doc's DocStorageModule, mirroring DatabaseWriter. DocLinksReader is instead provided and exported from plugins/indexer's IndexerModule: it depends on IndexerService, and importing it from core/doc/index.ts would close a static import cycle back through core/index.ts -> core/auth -> core/doc (verified via oxlint's import/no-cycle rule, which is clean both before and after this change). Updates the two existing ToolRuntime unit tests that construct it with positional constructor args for the 4 added DI parameters. Marks openspec task group 8 (8.1-8.3, 8.5) complete; 8.4 (built-in.json) is a separate commit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rompt Add docPropertiesRead/docPropertiesUpdate/docLinksRead/docLinksUpdate to the "Chat With AFFiNE AI" prompt's config.tools, alongside the existing database tools. This is a JSON-only edit; the native crate embeds this prompt at build time, so it takes effect after CI's native rebuild (not run locally as part of this task, per the task brief). Marks openspec task 8.4 complete. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DocPropertiesWriter's set_title op only wrote the denormalized root meta.pages[].title, leaving the doc's own affine:page prop:title stale. Delegate to DocWriter.updateDocMeta (already used by doc_update_meta), which writes both the doc-body title (native updateDocTitle) and the root meta copy (native updateRootDocMetaTitle) via one canonical path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Batches two changes for a single self-host image build + deploy. (Stacked on top of #1, so this PR includes #1's Track B commits; merging this closes #1.)
Track A — copilot doc-management tools
Lets the AI manage everything you can attribute to a doc, plus links — all server-side, YJS-direct, delta-push (mirroring
DatabaseWriter), noaffine_doc_loaderchanges.core/doc/):DocPropertiesReader/Writer(tags, custom properties, title, trash, journal, primary mode, favorite — across root-meta,docProperties,docCustomPropertyInfo, and the per-user favorites doc),DocLinksReader(outgoing + backlinks via the indexer, no per-doc scan),DocLinksWriter(embed-block + inline@-ref create/remove/retarget,create_doc_and_link), sharedyjs-deltahelper.doc_properties_read/doc_links_read(ungated,Doc.Read),doc_properties_update/doc_links_update(gateddev||canary,Doc.Update). Name-based referencing with id fallback + disambiguation; explicitcreate_tag/define_property; batched, up-front-validated writes.tool-runtime.ts+PromptToolsSchema+built-in.json(needs the CI native rebuild to take effect).Track B — Requesty/GLM reasoning leak (#1)
ThinkTagSplitterseparates inline<think>…</think>from content (streamText/streamObject → reasoning channel;text()/extractTextResponseexclude reasoning), so code artifacts are reasoning-free. Live-confirmed on the deployment.Verification
Subagent-driven: every task implement → review → fix → re-review; final whole-branch review (found + fixed a
set_titledual-write bug). All logic verified viatsxharnesses + cleantsc --noEmit+ oxlint/eslint — the ava suite runs in CI (Node 22), since local Node 24 can't bootstrap ava's prelude. OpenSpec changes underopenspec/changes/.🤖 Generated with Claude Code