feat(feedback): disclose session sharing in the /feedback acknowledgement

The /feedback acknowledgement now echoes the receiving session id and
reports the mounted telemetry backend's sharing policy: the telemetry seam
exposes a backend-independent TelemetrySharingStatus through a required
abstract sharing member on the Telemetry service, the OTel backend maps its
mode onto it, and the command appends one policy-only sharing sentence
(full / feedback-only / disabled / not configured) to the acknowledgement.
The web client renders the text through the existing command row without a
client change; a new assembled-browser e2e mounts the shipped telemetry row
in FULL mode against a local dead endpoint and pins the shipped default
sentence as a keyless golden.
This commit is contained in:
Chinesezjc
2026-08-08 02:27:37 +08:00
parent 94abd8631a
commit 3f9d0436eb
28 changed files with 394 additions and 18 deletions

View File

@@ -5,6 +5,7 @@ import AgentRegistry, { Inbox } from '@deepseek-ai/dsh-agent'
import type { Agent, AgentStatus } from '@deepseek-ai/dsh-agent'
import CommandService from '@deepseek-ai/dsh-commands'
import SessionStore, { foldSurface, Session, SessionId } from '@deepseek-ai/dsh-session'
import { Telemetry, type TelemetrySharingStatus } from '@deepseek-ai/dsh-session-telemetry'
import * as commandFeedback from '@deepseek-ai/dsh-command-feedback'
const { USER_ID, getOrCreateAnonymousUserId } = vi.hoisted(() => {
@@ -25,6 +26,20 @@ interface Harness {
readonly plugin: Awaited<ReturnType<Context['plugin']>>
}
/** Minimal mounted backend disclosing one sharing policy. */
class FakeTelemetry extends Telemetry {
override readonly sharing: TelemetrySharingStatus
constructor(ctx: Context, config: { sharing: TelemetrySharingStatus }) {
super(ctx)
this.sharing = config.sharing
}
emit(): void {}
async shutdown(): Promise<void> {}
}
/** Build a live idle agent over a store-owned session, as an app's spine does. */
function stubAgent(ctx: Context, id: string): { agent: Agent; session: Session } {
const session = ctx.sessions.create(SessionId(id))
@@ -48,12 +63,17 @@ function stubAgent(ctx: Context, id: string): { agent: Agent; session: Session }
return { agent, session }
}
/** Mount the real command registry and this producer. */
async function harness(): Promise<Harness> {
/**
* Mount the real command registry, this producer, and optionally a telemetry
* backend disclosing one sharing policy. Without `sharing`, no telemetry
* service exists and the acknowledgement reports "not configured".
*/
async function harness(sharing?: TelemetrySharingStatus): Promise<Harness> {
const ctx = new Context()
await ctx.plugin(CommandService)
await ctx.plugin(AgentRegistry)
await ctx.plugin(SessionStore)
if (sharing !== undefined) await ctx.plugin(FakeTelemetry, { sharing })
const plugin = await ctx.plugin(commandFeedback)
const { agent, session } = stubAgent(ctx, `command-feedback-${Math.random()}`)
ctx.agents.register(agent)
@@ -104,7 +124,7 @@ describe('/feedback human command', () => {
const test = await harness()
await expect(run(test, ' the diff view is unreadable')).resolves.toEqual({
kind: 'success',
text: `Feedback recorded for session ${test.session.id}\nUser: ${USER_ID}`,
text: `Feedback recorded for session ${test.session.id}\nUser: ${USER_ID}. Session sharing is not configured.`,
})
expect(feedbackTexts(test.session)).toEqual(['the diff view is unreadable'])
const commandRun = test.session.events.find(event => event.type === 'command/run')
@@ -152,12 +172,39 @@ describe('/feedback human command', () => {
test.ctx.commands.execute(test.agent, '/feedback second', signal),
])
expect(settled.map(item => item?.result)).toEqual([
{ kind: 'success', text: `Feedback recorded for session ${test.session.id}\nUser: ${USER_ID}` },
{ kind: 'success', text: `Feedback recorded for session ${test.session.id}\nUser: ${USER_ID}` },
{ kind: 'success', text: `Feedback recorded for session ${test.session.id}\nUser: ${USER_ID}. Session sharing is not configured.` },
{ kind: 'success', text: `Feedback recorded for session ${test.session.id}\nUser: ${USER_ID}. Session sharing is not configured.` },
])
expect(feedbackTexts(test.session)).toEqual(['first', 'second'])
})
it('discloses full session sharing in the acknowledgement', async () => {
const test = await harness('full')
await expect(run(test, ' everything shared')).resolves.toEqual({
kind: 'success',
text: `Feedback recorded for session ${test.session.id}\nUser: ${USER_ID}. Session sharing is enabled.`,
})
expect(feedbackTexts(test.session)).toEqual(['everything shared'])
})
it('discloses feedback-gated session sharing in the acknowledgement', async () => {
const test = await harness('feedback-only')
await expect(run(test, ' gated sharing')).resolves.toEqual({
kind: 'success',
text: `Feedback recorded for session ${test.session.id}\nUser: ${USER_ID}. Session sharing is feedback-gated; recording feedback releases the session prefix for sharing.`,
})
expect(feedbackTexts(test.session)).toEqual(['gated sharing'])
})
it('discloses disabled session sharing in the acknowledgement', async () => {
const test = await harness('disabled')
await expect(run(test, ' local only')).resolves.toEqual({
kind: 'success',
text: `Feedback recorded for session ${test.session.id}\nUser: ${USER_ID}. Session sharing is disabled.`,
})
expect(feedbackTexts(test.session)).toEqual(['local only'])
})
it('keeps every recorded event off the model surface and out of derived history', async () => {
const test = await harness()
await run(test, ' invisible to the model')