telemetry, agents-md: the test plugins hook and call the collector stream by name, and stand in for telemetry through its events (#96930)

* telemetry: the test plugins a person installed hook and call the collector's stream by name

* agents-md: the test plugin standing in for telemetry hooks its two events, adding the noun only where the engine has none
This commit is contained in:
Alice T'Poteat
2026-09-24 19:35:26 -07:00
committed by GitHub
parent 163ae3a264
commit e1bb7b065b
8 changed files with 56 additions and 35 deletions
+22 -13
View File
@@ -1,29 +1,38 @@
import type { Plugin } from 'claude-code/testing'
/**
* A plugin standing in for the telemetry built-in: it adds the `telemetry`
* noun at `engine.create` over the nouns beneath.
* A plugin standing in for the telemetry built-in: it hooks `telemetry.log`
* and `telemetry.mark` and answers them without going on.
*
* Its `log` and `mark` write the entry they are handed as one `$.ui.log`
* line, `<op> <json>`, which startedOf keeps and rowsOf reads back.
* Each writes the entry as the caller handed it (`to` left off) as one
* `$.ui.log` line, `<op> <json>`, which startedOf keeps and rowsOf reads
* back. Where the engine has no `telemetry` its engine.create step adds one.
*/
export const RECORDING: Plugin = {
name: 'recording',
tier: 'builtin',
register(on) {
on('telemetry.log', async ($, e) => {
const { to: _to, ...entry } = e
await $.ui.log(`log ${JSON.stringify(entry)}`)
return { value: undefined }
})
on('telemetry.mark', async ($, e) => {
await $.ui.log(`mark ${JSON.stringify(e)}`)
return { value: undefined }
})
on('engine.create', async ($, e, next) => {
const beneath = await next(e)
const built = { ...beneath }
built.telemetry = {
log: async entry => {
await beneath.ui.log(`log ${JSON.stringify(entry)}`)
},
mark: async entry => {
await beneath.ui.log(`mark ${JSON.stringify(entry)}`)
},
const added = {
telemetry: { log: async () => undefined, mark: async () => undefined },
}
return built
return { ...added, ...beneath }
})
},
}
+1 -1
View File
@@ -21,7 +21,7 @@ export const holding: Plugin = {
})
on('command.run', { command: 'hold' }, async () => {
await held?.telemetry.log({ event: 'held' })
await held?.telemetry.log({ to: 'collector', event: 'held' })
return { text: 'served' }
})
+7 -4
View File
@@ -1,17 +1,20 @@
import type { Plugin } from 'claude-code/testing'
/**
* A plugin a person installed that hooks `telemetry.log` above the gate to
* rename every row, and logs one of its own through `/meddle`.
* A plugin a person installed that hooks `telemetry.log` above the gate for
* the one stream it may, the collector's, to rename every record there, and
* logs one of its own through `/meddle`.
*/
export const meddling: Plugin = {
name: 'meddling',
tier: 'user',
register(on) {
on('telemetry.log', (_$, e, next) => next({ ...e, event: 'renamed' }))
on('telemetry.log', { to: 'collector' }, (_$, e, next) =>
next({ ...e, event: 'renamed' }),
)
on('command.run', { command: 'meddle' }, $ =>
$.telemetry.log({ event: 'mine' }).then(
$.telemetry.log({ to: 'collector', event: 'mine' }).then(
() => ({ text: 'served' }),
(error: unknown) => ({ text: String(error) }),
),
+3 -3
View File
@@ -17,7 +17,7 @@ export const reaching: Plugin = {
const beneath = await next(e)
during = await beneath.telemetry
.log({ event: 'during_create' })
.log({ to: 'collector', event: 'during_create' })
.then(() => 'served', String)
return beneath
@@ -26,7 +26,7 @@ export const reaching: Plugin = {
on('session.start', ($, e, next) => {
$.clock.after(1, () => {
void $.telemetry
.log({ event: 'from_timer' })
.log({ to: 'collector', event: 'from_timer' })
.then(() => 'served', String)
.then(outcome => $.ui.log(`timer: ${outcome}`, { to: 'debug' }))
})
@@ -36,7 +36,7 @@ export const reaching: Plugin = {
on('tool.call', { tool: 'Read' }, async ($, e, next) => {
const outcome = await $.telemetry
.log({ event: 'from_tool' })
.log({ to: 'collector', event: 'from_tool' })
.then(() => 'served', String)
await $.ui.log(`tool: ${outcome}`, { to: 'debug' })
+1 -1
View File
@@ -16,7 +16,7 @@ export const replacing: Plugin = {
}))
on('command.run', { command: 'replace' }, $ =>
$.telemetry.log({ event: 'mine' }).then(
$.telemetry.log({ to: 'collector', event: 'mine' }).then(
() => ({ text: 'served' }),
(error: unknown) => ({ text: String(error) }),
),
+3 -2
View File
@@ -2,12 +2,13 @@ import type { Plugin } from 'claude-code/testing'
/**
* A plugin a person installed that answers `telemetry.log` above the gate
* without going on beneath: the rows of everyone beneath it stop there.
* without going on beneath, for the one stream it may hook, the
* collector's: the records of everyone beneath it stop there.
*/
export const swallowing: Plugin = {
name: 'swallowing',
tier: 'user',
register(on) {
on('telemetry.log', () => ({ value: undefined }))
on('telemetry.log', { to: 'collector' }, () => ({ value: undefined }))
},
}
+9 -6
View File
@@ -1,18 +1,21 @@
import type { Plugin } from 'claude-code/testing'
/**
* A plugin a person installed whose `/visit <entry>` logs the entry through
* `$.telemetry`, answering "queued", or why the row was refused.
* A plugin a person installed whose `/visit <entry>` logs the entry's event
* and properties through `$.telemetry`, naming the one stream it may, the
* collector's; it answers "queued", or why the call was refused.
*/
export const visiting: Plugin = {
name: 'visiting',
tier: 'user',
register(on) {
on('command.run', { command: 'visit' }, ($, e) =>
$.telemetry.log(JSON.parse(e.args)).then(
on('command.run', { command: 'visit' }, ($, e) => {
const { event, props } = JSON.parse(e.args)
return $.telemetry.log({ to: 'collector', event, props }).then(
() => ({ text: 'queued' }),
(error: unknown) => ({ text: String(error) }),
),
)
)
})
},
}
+10 -5
View File
@@ -61,7 +61,7 @@ describe('gate', () => {
)
test(
'a hook above may rename a row a built-in sends, never send its own',
"a hook above on the collector's stream renames no row, sends none",
{ plugins: [Fixtures.recording, Fixtures.meddling] },
async ($, on) => {
mock.env(on, Fixtures.SENDING_ENV)
@@ -77,13 +77,13 @@ describe('gate', () => {
rows: Fixtures.rowsOf(session).map(Fixtures.eventNameOf),
}).toEqual({
own: `HooksError: meddling: $.telemetry.log: ${Hooks.REFUSED.deny}`,
rows: ['tengu_plugin_renamed'],
rows: ['tengu_plugin_survey_answered'],
})
},
)
test(
'a hook above that answers without going on sends nothing at all',
"a hook above that swallows the collector's stream stops no row",
{ plugins: [Fixtures.recording, Fixtures.swallowing] },
async ($, on) => {
mock.env(on, Fixtures.SENDING_ENV)
@@ -96,9 +96,14 @@ describe('gate', () => {
await session.clock.advance(Hooks.BATCH_WINDOW_MS)
expect({ answer, posts: session.posts }).toEqual({
expect({
answer,
posts: session.posts.length,
rows: Fixtures.rowsOf(session).map(Fixtures.eventNameOf),
}).toEqual({
answer: 'queued',
posts: [],
posts: 1,
rows: ['tengu_plugin_survey_answered'],
})
},
)