Skip to content

feat(telemetry): capture installs from published packages#328

Merged
blove merged 1 commit into
mainfrom
codex/install-telemetry
May 15, 2026
Merged

feat(telemetry): capture installs from published packages#328
blove merged 1 commit into
mainfrom
codex/install-telemetry

Conversation

@blove
Copy link
Copy Markdown
Contributor

@blove blove commented May 15, 2026

Summary

  • Send Node telemetry through the Cacheplane ingest proxy instead of shipping posthog-node in @ngaf/telemetry.
  • Add npm install telemetry for every published @ngaf/* package by patching publishable dist manifests during release builds.
  • Capture npm package-manager name/version from existing npm env, honor existing opt-outs, skip local top-level installs, and keep app/browser telemetry off by default.
  • Add the website /api/ingest proxy and update public messaging/release docs to match the new trust contract.

Verification

  • PATH="/Users/blove/.nvm/versions/node/v22.14.0/bin:$PATH" NX_DAEMON=false npx nx test telemetry --skip-nx-cache
  • PATH="/Users/blove/.nvm/versions/node/v22.14.0/bin:$PATH" NX_DAEMON=false npx nx build website --skip-nx-cache
  • PATH="/Users/blove/.nvm/versions/node/v22.14.0/bin:$PATH" NX_DAEMON=false npx nx run-many -t build --projects=chat,langgraph,ag-ui,render,a2ui,licensing,telemetry --skip-nx-cache
  • node libs/telemetry/scripts/apply-install-telemetry.mjs dist/libs/chat dist/libs/langgraph dist/libs/ag-ui dist/libs/render dist/libs/a2ui dist/libs/licensing
  • Real npm tarball smoke: packed all seven publishable packages from dist, installed into a fresh temp project with a local ingest server, and captured 7 ngaf:postinstall events with package_manager=npm / package_manager_version=10.9.2.
  • PATH="/Users/blove/.nvm/versions/node/v22.14.0/bin:$PATH" NX_DAEMON=false npx nx release version patch --dry-run
  • git diff --check
  • rg '@ngaf/partial-json|libs/partial-json' --glob '!docs/superpowers/**' --glob '!node_modules/**' --glob '!dist/**'

@vercel
Copy link
Copy Markdown

vercel Bot commented May 15, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cacheplane Ready Ready Preview, Comment May 15, 2026 6:58pm

Request Review

@blove blove merged commit ba4904f into main May 15, 2026
16 checks passed
blove added a commit that referenced this pull request May 16, 2026
#351)

* docs(gtm): spec for analytics-foundation-1c (cockpit instrumentation)

Spec 1C of the GTM motion. Three-surface cockpit instrumentation:

- Outer (React shell): cockpit:recipe_opened, mode_switched, code_copied
  via posthog-js direct
- Inner (Angular iframes, per-example): cockpit:chat_first_message,
  transport_connected, thread_persisted, interrupt_handled,
  generative_component_rendered via new @ngaf/cockpit-telemetry
  private library that subscribes to lifecycle signals on
  @ngaf/chat, @ngaf/langgraph, @ngaf/render
- Cross-frame correlation via session UUID in URL params; memory-only
  persistence on both frames

Key decisions:
- Architecture B (lifecycle signals + external adapter) — libraries
  expose @ngaf/* tokens, adapter is private. Customer apps never emit
  cockpit:* events.
- cockpit-telemetry uses posthog-js directly, not @ngaf/telemetry/browser
  (cockpit is internal product, different posture from customer libs)
- main.cockpit.ts build-time entry override per example, so example
  reference code (main.ts, app.config.ts, components) stays pristine
- Telemetry on by default in production, off on localhost unless
  NEXT_PUBLIC_COCKPIT_CAPTURE_LOCAL=true; honors DO_NOT_TRACK
- Activation funnel = 5 signals (dropped cockpit:install_command_copied;
  ngaf:postinstall from PR #328 is uncorrelatable to cockpit sessions
  by design)
- Renamed cockpit:six_signals_complete → cockpit:activation_complete
- All 32 examples rolled out in batched per-category commits within
  this plan; canonical example: cockpit/langgraph/streaming/angular
- Website docs for the three public *_LIFECYCLE tokens land as Phase 5

Phases:
  0. Library lifecycle additions (~21 tests)
  1. @ngaf/cockpit-telemetry private library (~24 tests, incl.
     permanent browser silence test)
  2. React shell instrumentation (~17 tests)
  3. Canonical streaming example + Chrome MCP smoke
  4. 31 remaining examples in 4 category batches
  5. Website docs at /docs/<lib>/lifecycle
  6. Taxonomy + PostHog dashboard cleanup (drop install_command_copied,
     rename event + insight + dashboard, posthog:sync)

Total ~65 tests, 15 new spec files. Pre-PR-#328 design adjusted to
match the new ingest proxy + per-package install telemetry pattern.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs(gtm): implementation plan for analytics-foundation-1c (cockpit instrumentation)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(chat): add CHAT_LIFECYCLE InjectionToken + interface

Public API addition for cockpit-telemetry (and other consumers) to
subscribe to per-instance chat lifecycle signals. componentReady,
firstMessageSent (sticky), messageCount and inputSubmittedAt (reset on
clearThread). Token only; wiring lands in next task.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(chat): wire CHAT_LIFECYCLE in ChatComponent

Populates the four lifecycle signals from existing component code
paths: componentReady on the first agent-resolved effect,
firstMessageSent/messageCount/inputSubmittedAt in a new public
submitMessage() (also driven by the chat-input submitted output),
and reset (except sticky firstMessageSent) in a new public
clearThread(). The token is provided component-scoped via a factory
that hands ChatComponent a writable internal handle while consumers
see only the readonly Signal<T> surface. Adds 6 tests covering all
transitions.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test(chat): add onUserSubmitted coverage + clarify clearThread doc

Addresses code-quality review feedback on Task 0.2.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(langgraph): add AGENT_LIFECYCLE InjectionToken + interface

8 lifecycle signals exposing transition timestamps. Wiring lands in
agent.fn.ts in the next task. Three signals (interruptResolvedAt,
threadCreatedAt, threadPersistedAt) require new hook points; five are
derived from existing BehaviorSubjects.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(langgraph): wire AGENT_LIFECYCLE in agent.fn.ts

Eight signal updates hooked into existing stream subscriptions and the
agent's submit/switchThread/load-history paths. Three new hooks
(interruptResolvedAt, threadCreatedAt, threadPersistedAt) — five
signals derive from existing stream state. All reset on switchThread.

Lifecycle surface exposed via a new `lifecycle: AgentLifecycle` field
on the returned LangGraphAgent (the factory has no DI scope of its own,
so this is the minimal-pollution path; consumers can re-provide the
AGENT_LIFECYCLE token via standard Angular providers if needed).

Mock agent updated to satisfy the new field. 10 tests cover all
transitions; 154/154 langgraph suite passes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(render): add RENDER_LIFECYCLE token + service + wiring

Service subscribes to the existing render-event stream and reduces to
five signals. firstMountAt is sticky; the rest update on each event.
Provided via provideRender() so all consumers automatically have access.

All RenderEvents flow through a single emitTapped() in RenderSpecComponent,
which fans out to the events output AND notifies the lifecycle service
(when present). Service is injected optionally so the components remain
usable without provideRender().

5 tests cover all signal transitions.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(cockpit-telemetry): scaffold private Nx library

@ngaf/cockpit-telemetry — private (not in publishable group), Angular
library, consumed by the 32 Angular examples via main.cockpit.ts
build-time entry override. Mirrors @ngaf/cockpit-shell scaffold pattern.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(cockpit-telemetry): config token + typed event names

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(cockpit-telemetry): readCockpitConfigFromIframe — URL param reader

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(cockpit-telemetry): ActivationAggregator — 5-signal rollup with 30-min window

6 tests cover the rollup math: pre-complete state, fire-once-when-complete,
idempotent signals, 30-min window reset, duration_ms property on emit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(cockpit-telemetry): CockpitTelemetryService — lifecycle subscribers + posthog init

Initializes posthog-js with memory persistence + parent-provided
distinct_id, subscribes to CHAT/AGENT/RENDER lifecycle tokens
(each optional — graceful no-op if absent), fires cockpit:* events
and marks signals on the ActivationAggregator.

6 tests cover init idempotency, capture format, missing-lifecycle
gracefulness, capability property stamping.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(cockpit-telemetry): provideCockpitTelemetry() EnvironmentProviders factory

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(cockpit-telemetry): bootstrapWithCockpitHarness — main.cockpit.ts entry helper

Each cockpit example's main.cockpit.ts calls this with its
AppComponent + appConfig. When URL params present, telemetry providers
are added; otherwise bootstraps pristine.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test(cockpit-telemetry): permanent browser silence contract test

When the cockpit harness is not present (no URL params), no eager
import of posthog-js. Mirrors @ngaf/telemetry/browser silence pattern.
Stays green permanently.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(cockpit-telemetry): declare peer deps + use lib- selector in spec

- Add @angular/platform-browser, @ngaf/chat, @ngaf/langgraph, @ngaf/render
  to peerDependencies (consumed by harness + service).
- TestComponent in harness.spec.ts uses lib- prefix per project eslint rules.
- Lockfile updated to record posthog-js install for the new private lib.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(cockpit): analytics module — distinct-id, properties, events, client

Mirrors apps/website/src/lib/analytics/ structure. Memory-only session
UUID, shouldCaptureAnalytics guard with localhost gate + DO_NOT_TRACK
honoring, typed track() helper. ~10 tests.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(cockpit): posthog-js initialization via instrumentation-client.ts

Memory persistence + parent-side session UUID. Off on localhost by
default (NEXT_PUBLIC_COCKPIT_CAPTURE_LOCAL=true to override). Honors
DO_NOT_TRACK. Three new env vars documented in .env.example.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(cockpit): fire cockpit:recipe_opened on sidebar capability click

Properties: capability, category, from_capability.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(cockpit): fire cockpit:mode_switched on mode tab change

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(cockpit): fire cockpit:code_copied on Code mode copy

Properties: capability, surface=code_mode, file_path.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(cockpit): fire cockpit:code_copied on narrative docs copy buttons

Two surfaces: docs_code_snippet (inline code blocks) and agentic_prompt
(prompt callouts).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(cockpit): RunMode appends cockpit_did/cockpit_cap to iframe src

The iframe URL now carries the session UUID + capability slug + posthog
key + host so the Angular harness can correlate to the parent session.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(cockpit-streaming): main.cockpit.ts harness entry

Three-line harness uses bootstrapWithCockpitHarness from
@ngaf/cockpit-telemetry. Pristine main.ts unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(cockpit-streaming): add cockpit build configuration

cockpit/<...>/project.json gains a cockpit build that uses main.cockpit.ts
as the entry. apps/cockpit:serve-streaming now invokes serve:cockpit on
the example. Production build unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(cockpit-langgraph): wire 7 examples to cockpit-telemetry harness

Add main.cockpit.ts + cockpit build/serve configurations to the
remaining LangGraph cockpit examples (memory, durable-execution,
subgraphs, deployment-runtime, interrupts, persistence, time-travel).
Update apps/cockpit serve-* targets to use the :serve:cockpit config
so the iframe loads the harness-enabled build.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(cockpit-deep-agents): wire 6 examples to cockpit-telemetry harness

Add main.cockpit.ts + cockpit build/serve configurations to all
Deep Agents cockpit examples (sandboxes, subagents, memory, planning,
filesystem, skills). Update apps/cockpit serve-* targets to use the
:serve:cockpit config so the iframe loads the harness-enabled build.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(cockpit-chat): wire 11 examples to cockpit-telemetry harness

Add main.cockpit.ts + cockpit build/serve configurations to all chat
cockpit examples (tool-calls, messages, subagents, input, a2ui,
theming, threads, interrupts, timeline, generative-ui, debug). The
timeline harness preserves the installEmbeddedTheme() call before
bootstrap. Chat examples are launched via the serve-example.ts script;
that script is updated in the render batch.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(cockpit-render): wire 6 examples to cockpit-telemetry harness

Add main.cockpit.ts + cockpit build/serve configurations to all render
cockpit examples (computed-functions, element-rendering, repeat-loops,
state-management, spec-rendering, registry). Also update the shared
serve-example.ts script so both --capability and --all modes launch
the harness-enabled :serve:cockpit configuration; this covers the
chat + render capabilities that don't have per-capability serve-*
targets in apps/cockpit/project.json.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs(website): chat/lifecycle.md — CHAT_LIFECYCLE signal docs

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs(website): langgraph/lifecycle.md — AGENT_LIFECYCLE signal docs

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs(website): render/lifecycle.md — RENDER_LIFECYCLE signal docs

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs(website): link lifecycle pages from each lib's landing

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore(gtm): drop install_command_copied + rename activation event

Activation funnel is 5 signals per Spec 1C. ngaf:postinstall is its
own top-of-funnel metric, uncorrelated to cockpit sessions.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore(posthog): rename six-signal-activation-funnel → activation-funnel

5 steps (dropped install_command_copied), 30-minute window. posthog_id
nulled to force create on next sync (PostHog will assign a new id).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore(posthog): developer-funnel references activation-funnel insight

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test(cockpit): polyfill CSS.escape in jsdom test setup

jsdom doesn't implement CSS.escape; code-mode copy handler calls it. Tests
passed but vitest flagged an unhandled error. Polyfill restores green nx
test target.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore(gtm): align taxonomy with implemented cockpit shell events

- Rename cockpit:recipe_start → cockpit:recipe_opened (sidebar click)
- Add cockpit:mode_switched (Run/Code/Docs tab change)
- Add cockpit:code_copied (code mode, doc snippet, agentic prompt)
- Update cockpit-recipe-completion insight to use renamed event
- Document that shell events are funnel context, not activation steps

Addresses code-review finding on Spec 1C.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(langgraph,cockpit-telemetry): wire AGENT_LIFECYCLE via registry

Bug: CockpitTelemetryService.subscribeAgent() injected AGENT_LIFECYCLE
which is never provided in DI (agent() exposes lifecycle on its return
object). Three activation signals (transport_connected, thread_persisted,
interrupt_handled) never fired; activation funnel was unreachable.

Fix: Add AgentLifecycleRegistry to @ngaf/langgraph as an optional
service. agent() registers itself if the registry is provided.
provideCockpitTelemetry now provides the registry, and the service
subscribes to lifecycles reactively via a signal effect.

Addresses code-review item on Spec 1C.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor(render): drop providedIn:root on RenderLifecycleService

Service is already provided by provideRender(). The redundant
providedIn:'root' caused both paths to resolve to the same singleton —
removing it makes the scope follow the consumer's provideRender() call
(sub-tree-friendly).

Addresses code-review minor item on Spec 1C.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs(website): regenerate api-docs.json for lifecycle additions

Picks up CHAT_LIFECYCLE, AGENT_LIFECYCLE, RENDER_LIFECYCLE tokens +
AgentLifecycleRegistry from Spec 1C. CI drift check would fail without
this regeneration.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant