| name | create-event-type |
| description | Step-by-step process for adding a new event type to the Copilot Agent Activity Visualizer. Touches four locations in sequence: event schema definition, hook emitter registration, state machine transition handling, and web UI rendering rule. Use this skill whenever a new lifecycle event type needs to be introduced beyond the MVP set.
|
Skill: Create a New Event Type
Adding a new event type to the Copilot Activity Visualiser requires
coordinated changes across four packages in a specific order. This skill
walks through each step with the correct sequence, templates, and validation
checkpoints so nothing is missed.
Step 1: Define the Event Schema
Open shared/event-schema/src/schema.ts.
Add a new Zod schema for the event payload, then add it to the discriminated
union:
const MyNewEventPayload = z.object({
agentId: z.string(),
customField: z.string().optional(),
});
const MyNewEvent = EventEnvelopeBase.extend({
type: z.literal("myNewEventType"),
payload: MyNewEventPayload,
});
Export the new type inference:
export type MyNewEvent = z.infer<typeof MyNewEvent>;
Validation checkpoint: Run npm run typecheck in shared/event-schema/. Zero errors required before proceeding.
Step 2: Register the Hook Emitter
Open packages/hook-emitter/src/hooks.ts.
Register a new hook callback for the event and emit a schema-compliant
envelope:
import { applyRedaction } from "../../shared/redaction/src/index.js";
import { writeJsonlEvent } from "./transport/jsonl.js";
copilotHooks.on("myNewEventType", async (rawPayload) => {
const event: MyNewEvent = {
id: generateUUID(),
schemaVersion: CURRENT_SCHEMA_VERSION,
sessionId: getCurrentSessionId(),
timestamp: new Date().toISOString(),
type: "myNewEventType",
payload: {
agentId: rawPayload.agentId,
customField: rawPayload.customField,
},
};
const redacted = applyRedaction(event);
await writeJsonlEvent(redacted);
});
Validation checkpoint:
- Run
npm run typecheck in packages/hook-emitter/.
- Add a Vitest unit test in
packages/hook-emitter/test/ confirming the new event type produces a valid redacted envelope.
Step 3: Handle the Transition in the State Machine
Open shared/state-machine/src/reducer.ts.
Add a case branch to the switch (event.type) block in reduceEvent:
case "myNewEventType": {
const agentKey = event.payload.agentId;
return {
...state,
agents: {
...state.agents,
[agentKey]: {
...state.agents[agentKey],
status: "tool_running",
},
},
};
}
Important: The switch must remain exhaustive. If TypeScript reports an unreachable default branch, the union coverage is correct. If the default branch is still reachable, the new event type was not added to the discriminated union in Step 1.
Validation checkpoint:
- Run
npm run typecheck in shared/state-machine/.
- Add a Vitest determinism fixture test: apply the new event type to a baseline state and assert the output matches the expected state. Run the assertion three times to confirm determinism.
Step 4: Add the UI Rendering Rule
Open packages/web-ui/src/live/StateTile.tsx.
Add the new state mapping to the tile renderer:
case "myNewEventType":
return {
label: "My New State",
cssClass: "tile--my-new-state",
ariaLive: "polite",
};
Add the CSS definition in packages/web-ui/src/live/StateTile.css:
.tile--my-new-state {
background-color: var(--color-my-new-state);
}
@media (prefers-reduced-motion: reduce) {
.tile--my-new-state {
animation: none;
}
}
Define the CSS custom property in the design tokens file:
:root {
--color-my-new-state: #xxxxxx;
}
Validation checkpoint:
- Run
npm run typecheck in packages/web-ui/.
- Add a Vitest component test in
packages/web-ui/test/ confirming the new tile renders with the correct CSS class.
- Visually inspect the tile in the dev server (
npm run dev in packages/web-ui/).
- Check contrast ratio with a browser accessibility inspector tool.
Step 5: Update the Test Fixture Factory
Open tests/fixtures/makeEvent.ts.
Add the new event type to the makeEvent factory so all existing integration
tests and any future tests can produce instances of it:
case "myNewEventType":
return {
...baseEnvelope,
type: "myNewEventType",
payload: {
agentId: overrides?.agentId ?? "agent-fixture-1",
customField: overrides?.customField,
},
};
Run the full test suite to confirm no existing tests are broken by the new
discriminated union branch:
npm run test
Step 6: Validate End-to-End
Run the integration test harness (see setup-integration-test skill) with a
fixture session that includes the new event type. Confirm:
- The new event appears in the JSONL output with a valid envelope.
- The state machine transitions to the correct state on ingestion.
- The live board displays the new tile correctly.
- The replay timeline includes and correctly plays back the new event.
- Redaction applies correctly (no sensitive fields in the JSONL output).
Reference
See the following documents for event type requirements and state mapping rules: