refactor(telemetry): replay feedback sessions without buffering
This commit is contained in:
@@ -13,7 +13,7 @@ import type { Agent } from '@deepseek-ai/dsh-agent'
|
||||
import {
|
||||
TelemetryCoordinator,
|
||||
type TelemetryBackend,
|
||||
type TelemetryDelivery,
|
||||
type TelemetryCapture,
|
||||
type TelemetryRecord,
|
||||
} from '../src/index.ts'
|
||||
|
||||
@@ -61,7 +61,7 @@ class FakeBackend implements TelemetryBackend {
|
||||
|
||||
async function setup(
|
||||
backend: FakeBackend = new FakeBackend(),
|
||||
delivery: TelemetryDelivery = 'immediate',
|
||||
capture: TelemetryCapture = 'live',
|
||||
) {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SessionStore)
|
||||
@@ -70,7 +70,7 @@ async function setup(
|
||||
name: 'fake-telemetry',
|
||||
inject: ['sessions'],
|
||||
apply: (inner: Context) => {
|
||||
coordinator = new TelemetryCoordinator(inner, backend, delivery)
|
||||
coordinator = new TelemetryCoordinator(inner, backend, capture)
|
||||
},
|
||||
})
|
||||
return { ctx, backend, coordinator, fiber }
|
||||
@@ -178,23 +178,24 @@ describe('TelemetryCoordinator capture', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('TelemetryCoordinator held delivery', () => {
|
||||
it('releases one pending prefix at a time without handing later records over early', async () => {
|
||||
const { ctx, backend, coordinator } = await setup(new FakeBackend(), 'held')
|
||||
const session = liveSession(ctx, 'held-prefix')
|
||||
describe('TelemetryCoordinator on-demand capture', () => {
|
||||
it('captures one canonical-log prefix at a time without following later events', async () => {
|
||||
const { ctx, backend, coordinator } = await setup(new FakeBackend(), 'on-demand')
|
||||
const session = liveSession(ctx, 'on-demand-prefix')
|
||||
appendTurn(session)
|
||||
const firstBoundary = session.events[1]!.seq
|
||||
session.append('turn/end', { turn: 1, reason: { kind: 'completed' } })
|
||||
expect(backend.records).toEqual([])
|
||||
|
||||
coordinator.release(session)
|
||||
coordinator.captureSession(session, firstBoundary)
|
||||
expect(backend.ledger().map(record => record.attributes['event.type'])).toEqual([
|
||||
'turn/start',
|
||||
'user/message',
|
||||
])
|
||||
|
||||
session.append('turn/end', { turn: 1, reason: { kind: 'completed' } })
|
||||
expect(backend.ledger()).toHaveLength(2)
|
||||
coordinator.release(session)
|
||||
coordinator.release(session)
|
||||
coordinator.captureSession(session)
|
||||
coordinator.captureSession(session)
|
||||
expect(backend.ledger().map(record => record.attributes['event.type'])).toEqual([
|
||||
'turn/start',
|
||||
'user/message',
|
||||
@@ -202,38 +203,42 @@ describe('TelemetryCoordinator held delivery', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('stores the capture-time redacted copy rather than re-running policy at release', async () => {
|
||||
const { ctx, backend, coordinator } = await setup(new FakeBackend(), 'held')
|
||||
it('runs the currently mounted redaction policy during canonical-log capture', async () => {
|
||||
const { ctx, backend, coordinator } = await setup(new FakeBackend(), 'on-demand')
|
||||
const session = liveSession(ctx, 'on-demand-redacted')
|
||||
session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } })
|
||||
const disposeRule = ctx.on('telemetry/record', (_record, next) => ({
|
||||
...next(),
|
||||
body: { scrubbed: true },
|
||||
}))
|
||||
const session = liveSession(ctx, 'held-redacted')
|
||||
session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } })
|
||||
|
||||
coordinator.captureSession(session)
|
||||
expect(backend.ledger()[0]!.body).toEqual({ scrubbed: true })
|
||||
disposeRule()
|
||||
|
||||
coordinator.release(session)
|
||||
expect(backend.ledger()[0]!.body).toEqual({ scrubbed: true })
|
||||
session.append('turn/end', { turn: 1, reason: { kind: 'completed' } })
|
||||
coordinator.captureSession(session)
|
||||
expect(backend.ledger()[1]!.body).toEqual({ turn: 1, reason: { kind: 'completed' } })
|
||||
})
|
||||
|
||||
it('contains each backend failure independently while releasing a batch', async () => {
|
||||
it('contains each backend failure independently while replaying a prefix', async () => {
|
||||
const backend = new FakeBackend()
|
||||
backend.rejectSeq = 1
|
||||
const { ctx, coordinator } = await setup(backend, 'held')
|
||||
const { ctx, coordinator } = await setup(backend, 'on-demand')
|
||||
const warn = vi.spyOn(ctx.logger, 'warn').mockImplementation(() => {})
|
||||
const session = liveSession(ctx, 'held-failure')
|
||||
const session = liveSession(ctx, 'on-demand-failure')
|
||||
appendTurn(session)
|
||||
session.append('turn/end', { turn: 1, reason: { kind: 'completed' } })
|
||||
|
||||
coordinator.release(session)
|
||||
coordinator.captureSession(session)
|
||||
expect(backend.ledger().map(record => record.attributes['event.seq'])).toEqual([0, 2])
|
||||
expect(warn).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('rebuilds an unreleased prefix after coordinator reload', async () => {
|
||||
it('captures a pending prefix after coordinator reload without retained records', async () => {
|
||||
const first = new FakeBackend()
|
||||
const { ctx, fiber } = await setup(first, 'held')
|
||||
const session = liveSession(ctx, 'held-reload')
|
||||
const { ctx, fiber } = await setup(first, 'on-demand')
|
||||
const session = liveSession(ctx, 'on-demand-reload')
|
||||
session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } })
|
||||
await fiber.dispose()
|
||||
expect(first.records).toEqual([])
|
||||
@@ -241,15 +246,34 @@ describe('TelemetryCoordinator held delivery', () => {
|
||||
const second = new FakeBackend()
|
||||
let coordinator!: TelemetryCoordinator
|
||||
await ctx.plugin({
|
||||
name: 'fake-telemetry-after-held-reload',
|
||||
name: 'fake-telemetry-after-on-demand-reload',
|
||||
inject: ['sessions'],
|
||||
apply: (inner: Context) => {
|
||||
coordinator = new TelemetryCoordinator(inner, second, 'held')
|
||||
coordinator = new TelemetryCoordinator(inner, second, 'on-demand')
|
||||
},
|
||||
})
|
||||
coordinator.release(session)
|
||||
coordinator.captureSession(session)
|
||||
expect(second.ledger().map(record => record.attributes['event.seq'])).toEqual([0])
|
||||
})
|
||||
|
||||
it('registers no continuous capture, flush, or ops listeners', async () => {
|
||||
const { ctx, backend, coordinator, fiber } = await setup(new FakeBackend(), 'on-demand')
|
||||
const redact = vi.fn((_record: TelemetryRecord, next: () => TelemetryRecord) => next())
|
||||
ctx.on('telemetry/record', redact)
|
||||
const session = liveSession(ctx, 'on-demand-ledger-only')
|
||||
session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } })
|
||||
await ctx.parallel('session/flush', session)
|
||||
const agent = { id: 'agent-1', session } as Agent
|
||||
ctx.emit('agent/error', agent, 1, 1, new Error('local only'))
|
||||
expect(backend.flush).not.toHaveBeenCalled()
|
||||
expect(backend.records).toEqual([])
|
||||
expect(redact).not.toHaveBeenCalled()
|
||||
|
||||
coordinator.captureSession(session)
|
||||
expect(redact).toHaveBeenCalledTimes(1)
|
||||
await fiber.dispose()
|
||||
expect(backend.records.map(record => record.channel)).toEqual(['ledger'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('TelemetryCoordinator adoption', () => {
|
||||
|
||||
Reference in New Issue
Block a user