Reviewer changes-requested on #565.
F2 (real bug): a save on an empty page (or a missing page row) hung the client
for the full 20s ack window and then falsely reported the collab server as
unreachable + advised retry. Root cause: handleSaveVersion gated its
broadcastStateless on `if (result)`, and `result` stays undefined on the two
reachable early returns (isEmptyParagraphDoc and !page), so the server sent
NOTHING and the client waited out its timeout.
- SERVER (persistence.extension.ts): both early-return branches now record a
skip reason and the tail broadcasts a terminal `version.skipped` reply
(reason 'empty' | 'page-not-found'). Exactly one terminal reply per handled
save. Added VERSION_SAVED/VERSION_SKIPPED message consts.
- CLIENT (collaboration.ts): the predicate now matches both version.saved and
version.skipped; SaveVersionResult gains {saved, skipped, reason}. An empty
skip resolves to a clean {saved:false, skipped:true, reason:'empty'} (no
stall); page-not-found throws an immediate, truthful error; the genuine
no-reply timeout path is unchanged (that is the real "unreachable" case). A
terminal reply leaves the session cached (healthy connection); only a
transport failure destroys it. Kept the message-literal in-sync comments.
- tool-specs.ts / pages.ts: description + docstring reflect the new result shape.
F1 (coverage): added tests for the 4 previously-uncovered sendStatelessAndAwait
branches — unrelated-then-real (predicate filters noise), teardown-mid-wait
(rejects + removes the stateless listener, no leak), concurrent-in-flight
guard — plus the F2 empty-skip / page-not-found client outcomes. Server spec
gains empty-page and page-not-found terminal-reply tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Stage-A save_page_version tool's client method must be listed in the
DocmostClientMethod union so Pick<DocmostClient, DocmostClientMethod> exposes it
to the in-app tool adapter; the shared-tool-specs contract spec asserts parity.
Mirrors the existing restorePageVersion entry.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Stage A of #370: wire the MCP client transport for the already-shipped server
save-version handler (PR-1 #374). An agent can now pin an intentional named
version (kind='agent', derived server-side from the signed actor) of a page's
CURRENT live collaboration content.
- collab-session.ts: add CollabSession.sendStatelessAndAwait(payload, predicate,
timeoutMs) — sends a stateless message over the live provider and resolves on
the first matching reply, with a bounded timeout; lifecycle (inflightReject,
ready-guard, concurrent fail-fast, idle re-arm) mirrors mutate(). Extend
CollabProviderLike with sendStateless + the stateless on/off overloads.
- collaboration.ts: add savePageVersionRealtime() — under withPageLock, reuse the
cached agent-authenticated CollabSession (#400), send {type:'save-version'},
await {type:'version.saved', …}; no REST read (would race the stale page row).
- client/pages.ts: add savePageVersion(pageId) — resolvePageId +
getCollabTokenWithReauth + writeWithCollabAuthRetry (#486 self-heal).
- tool-specs.ts: add the deferred savePageVersion spec (+ DocmostClientLike Pick);
auto-registered on both hosts by the shared loops.
- server-instructions.ts: HISTORY family + routing-prose mention.
- ai-chat-tools.service.ts: contract type-assert for the new client method.
- agent-roles-catalog: tell the content-authoring roles (researcher,
call-summarizer, ru+en) to save a version when the document is done; bump
their catalog versions.
- test: unit coverage for savePageVersionRealtime (ack + bounded-timeout paths).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>