feat(coach): document chats live in the Coach view and history

Asking the Coach about a document opened a separate, half-finished chat panel
that never appeared in Coach history. The bespoke drawer is gone; a document
conversation now opens the real Coach view, appears in the Coach list with a
document badge and a "Chatting about: <title>" note, and is read back through
the same history rail as any other thread.

The prompt-injection fence is unchanged and stays server-side. A document turn
is still sent through the hardened fenced endpoint (no tools, no health
snapshot, grounded in the document text only); the coach tool route continues
to fetch with documentId null, so a document-scoped conversation can never be
continued through the tool loop even from a hostile client. Only the surface
moved into the Coach; the boundary did not.

The relaxed conversation-list and detail filters drop only the documentId
predicate — userId scoping is unchanged, so a document thread is still visible
only to its owner. CoachConversationDTO gains documentId + documentTitle
(server-derived, title/filename only); no schema change (documentId exists
since 0230). OpenAPI regenerated.
This commit is contained in:
Marc-André Bombeck committed 2026-07-16 11:01:59 +02:00
1 parent 57539283c8
commit 5b24ca0fa1
28 files changed
+605 -1112

No files matched your search

+24
View File
@@ -508,6 +508,18 @@ const coachConversationSchema = z
.datetime({ offset: true })
.describe("Bumped on every appended message; the rail orders by this."),
messageCount: z.number().int(),
documentId: z
.string()
.nullish()
.describe(
"When set, this thread is scoped to one stored document (a document chat, run on the hardened fenced endpoint). Null/absent for a normal health thread.",
),
documentTitle: z
.string()
.nullish()
.describe(
"The owning document's resolved title (title, falling back to filename) for the scope badge. Null on a health thread or when the document has neither.",
),
})
.meta({
id: "CoachConversation",
@@ -548,6 +560,18 @@ const coachConversationDetailSchema = z
.describe(
"Rolling summary of turns elided past the history window; null when none is on file.",
),
documentId: z
.string()
.nullish()
.describe(
"When set, this thread is scoped to one stored document; the client routes its turns through the hardened fenced document endpoint, never the tool route.",
),
documentTitle: z
.string()
.nullish()
.describe(
"The owning document's resolved title for the scope badge. Null on a health thread.",
),
})
.meta({
id: "CoachConversationDetail",