diff --git a/README.md b/README.md index aa02c8ba..ef3f12f1 100644 --- a/README.md +++ b/README.md @@ -24,9 +24,9 @@ --- -Threadplane is a production-ready agent UI framework for Angular. Use `@threadplane/chat` for chat surfaces, `@threadplane/langgraph` for LangGraph-backed agents, `@threadplane/ag-ui` for AG-UI event streams, and `@threadplane/render` for generative UI that stays inside your Angular design system. +Threadplane is a production-ready agent UI framework for Angular. `@threadplane/chat` provides chat surfaces (headless primitives, opinionated compositions, interrupts, generative UI). `@threadplane/langgraph` adapts a LangGraph Platform endpoint into Angular Signals via `provideAgent()` + `injectAgent()`. `@threadplane/ag-ui` bridges any AG-UI-compatible backend into the same chat surface. `@threadplane/render` renders JSON specs to Angular components inside your design system. -When you are building on LangGraph, `injectAgent()` is the Angular equivalent of LangGraph's React `useStream()` hook, projected into a runtime-neutral `Agent` contract consumed by `@threadplane/chat`. Configure it once with `provideAgent({...})`, inject it into any Angular 20+ component, and get signal-driven access to messages, status, tool calls, interrupts, subagents, regenerate, and thread history. +`injectAgent()` is the Angular equivalent of LangGraph's React `useStream()` hook, projected through a runtime-neutral `Agent` contract that `@threadplane/chat` consumes. Configure it once with `provideAgent({...})`, inject it into any Angular 20+ component, and get signal-driven access to messages, status, tool calls, interrupts, subagents, history, and thread management — no subscriptions, no `async` pipe, no zone.js required. --- @@ -36,7 +36,7 @@ When you are building on LangGraph, `injectAgent()` is the Angular equivalent of npm install @threadplane/langgraph @threadplane/chat ``` -**Peer dependencies:** `@angular/core ^20.0.0 || ^21.0.0`, `@langchain/core ^1.1.0`, `@langchain/langgraph-sdk ^1.7.0`, `rxjs ~7.8.0` +**Peer dependencies:** `@angular/core ^20.0.0 || ^21.0.0`, `@langchain/core ^1.1.33`, `@langchain/langgraph-sdk ^1.7.4`, `rxjs ~7.8.0` --- @@ -82,7 +82,7 @@ export class SupportChatComponent { } ``` -That's it. `chat.messages()` and `chat.status()` are Angular Signals. Bind them directly in your template — no subscriptions, no `async` pipe, no zone.js required. +`chat.messages()` and `chat.status()` are Angular Signals. Bind them directly in your template — no subscriptions, no `async` pipe, no zone.js required. --- @@ -95,13 +95,12 @@ That's it. `chat.messages()` and `chat.status()` are Angular Signals. Bind them | Loading state | `isLoading()` | `isLoading` | | Error state | `error()` | — | | Runtime-neutral status | `status()` — `'idle' \| 'running' \| 'error'` | partial | -| Interrupt / human-in-the-loop | `interrupt()` / `interrupts()` | `interrupt` / `interrupts` | +| Interrupt / human-in-the-loop | `interrupt()` (runtime-neutral) / `langGraphInterrupts()` (raw plural) | `interrupt` / `interrupts` | | Tool call progress | `toolCalls()` | `toolCalls` | -| Tool calls with results | `toolCalls()` | `toolCalls` | | Branch / history | `branch()` / `history()` / `experimentalBranchTree()` | `branch` / `history` / `experimental_branchTree` | | Pending run queue | `queue()` | `queue` | -| Subagent streaming and lookup helpers | `subagents()` / `getSubagent()` | `subagents` / helper methods | -| Reactive thread switching | `Signal` input | prop | +| Subagent map and lookup | `subagents()` — `Signal>` / `getSubagent(toolCallId)` | `subagents` / helper methods | +| Reactive thread switching | `switchThread(id)` | prop | | Submit | `submit(values, opts?)` | `submit(values, opts?)` | | Stop | `stop()` | `stop()` | | Regenerate response | `regenerate(assistantMessageIndex)` | — | @@ -113,17 +112,37 @@ That's it. `chat.messages()` and `chat.status()` are Angular Signals. Bind them --- +## Packages + +All packages are published at version `0.0.47` under a patch-only `0.0.x` release policy. + +| Package | Purpose | License | +|---|---|---| +| `@threadplane/chat` | Drop-in agent chat UI for Angular: headless primitives and opinionated compositions (``, popup, sidebar, interrupts, GenUI) | PolyForm Noncommercial + Commercial (dual) | +| `@threadplane/langgraph` | LangGraph adapter; `provideAgent()`/`injectAgent()` exposes a LangGraph run as Angular Signals | MIT | +| `@threadplane/ag-ui` | AG-UI adapter; bridges any `@ag-ui/client`-compatible backend into the chat surface | MIT | +| `@threadplane/render` | `@json-render/core`-backed Angular engine that renders JSON specs to components (powers GenUI) | MIT | +| `@threadplane/a2ui` | A2UI protocol types, streaming parser, and dynamic-value resolver; pure TypeScript, no Angular dependency | MIT | +| `@threadplane/licensing` | Browser-safe Ed25519 license-token verification and evaluation (backs the chat commercial check) | MIT | +| `@threadplane/telemetry` | Transparent, opt-out anonymous usage telemetry (node + browser) | MIT | + +--- + ## Architecture

Threadplane architecture: Angular Component → agent() → StreamManager Bridge → LangGraph Platform, with signals returned reactively

-`injectAgent()` resolves an agent whose internal `BehaviorSubject`s were created at injection-context time — once, when `provideAgent()`'s factory ran. The `StreamManager` bridge (the only file that touches `@langchain/langgraph-sdk` internals) pushes stream events into those subjects. `toSignal()` converts each subject to an Angular Signal, also at construction time. Dynamic actions (`submit`, `stop`, `switchThread`) push into the existing subjects — no new subjects are ever created after construction. This architecture is required because `toSignal()` must be called in an injection context and cannot be called again later. +`provideAgent()` creates the agent's internal `BehaviorSubject`s at injection-context time — once, when the provider factory runs. `injectAgent()` retrieves the configured `LangGraphAgent` in any component. The `StreamManager` bridge (the only file that touches `@langchain/langgraph-sdk` internals) pushes stream events into those subjects. `toSignal()` converts each subject to an Angular Signal, also at construction time. Dynamic actions (`submit`, `stop`, `switchThread`) push into the existing subjects — no new subjects are ever created after construction. This architecture is required because `toSignal()` must be called in an injection context and cannot be called again later. + +The runtime-neutral `Agent` contract is the stability boundary between adapters and the chat surface. `@threadplane/chat` consumes `Agent` — not `LangGraphAgent` — so swapping `@threadplane/langgraph` for `@threadplane/ag-ui` requires no changes to your chat components or templates. + +**Reliability:** Every pull request runs the "Library — lint / test / build" CI job across all packages. Testing uses `MockAgentTransport` to swap the transport layer, so you never need to mock `injectAgent()` itself — just substitute the transport. The patch-only `0.0.x` release policy ensures no minor or major version bumps silently break your lockfile. --- diff --git a/docs/superpowers/plans/2026-05-28-readme-refactor.md b/docs/superpowers/plans/2026-05-28-readme-refactor.md new file mode 100644 index 00000000..2f5031a9 --- /dev/null +++ b/docs/superpowers/plans/2026-05-28-readme-refactor.md @@ -0,0 +1,483 @@ +# README Refactor & Complete Update — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Rewrite the root `README.md` and all 7 published `@threadplane/*` package READMEs so every claim traces to verified source, missing capabilities are surfaced, and reliability/production trust signals are present. + +**Architecture:** Two phases. Phase 1 audits each package's public API and repo-wide facts into a "Ground Truth" inventory (the anti-drift gate). Phase 2 writes each README strictly from that inventory, verifying every named export against source before commit. This is a documentation task — there is no unit-test harness for prose, so each task's "test" is a **source-verification command** whose expected output is shown. + +**Tech Stack:** Nx monorepo, Angular 20+/21 libraries, TypeScript, npm. Markdown READMEs published to npm. Verification via `grep` against `src` entry points and `npx nx build `. + +--- + +## Spec + +Design spec: `docs/superpowers/specs/2026-05-28-readme-refactor-design.md`. The Ground Truth appendix in that spec is filled by Task 1. + +## File Structure + +Files this plan creates or modifies: + +- Modify: `README.md` — root developer landing surface. +- Modify: `libs/langgraph/README.md` — LangGraph adapter. +- Modify: `libs/ag-ui/README.md` — AG-UI adapter. +- Modify: `libs/chat/README.md` — chat UI primitives + compositions (largest). +- Create/replace: `libs/a2ui/README.md` — **net-new** (currently absent). +- Modify: `libs/telemetry/README.md` — telemetry + transparency contract. +- Modify: `libs/render/README.md` — render primitives (small). +- Modify: `libs/licensing/README.md` — license verification (small). +- Modify: `docs/superpowers/specs/2026-05-28-readme-refactor-design.md` — Ground Truth appendix filled in Task 1. + +Entry points to audit (confirmed present): + +| Package | Entry point | package.json | +|---|---|---| +| a2ui | `libs/a2ui/src/index.ts` | `libs/a2ui/package.json` | +| ag-ui | `libs/ag-ui/src/public-api.ts` | `libs/ag-ui/package.json` | +| chat | `libs/chat/src/index.ts` | `libs/chat/package.json` | +| langgraph | `libs/langgraph/src/public-api.ts` | `libs/langgraph/package.json` | +| licensing | `libs/licensing/src/index.ts` | `libs/licensing/package.json` | +| render | `libs/render/src/public-api.ts` | `libs/render/package.json` | +| telemetry | `libs/telemetry/src/index.ts` | `libs/telemetry/package.json` | + +Repo facts: CI workflow `.github/workflows/ci.yml` job `library` ("Library — lint / test / build"); release policy patch-only at `0.0.x` (current `0.0.47`); Angular peer support to confirm from each `package.json`. + +--- + +## Task 1: Audit packages and fill the Ground Truth inventory + +**Files:** +- Read: all 7 entry points + `package.json` files (table above). +- Read: `.github/workflows/ci.yml`, root `package.json`. +- Modify: `docs/superpowers/specs/2026-05-28-readme-refactor-design.md` (Ground Truth appendix). + +This task is parallelizable — one audit per package. It produces the single source of truth every later task reads from. + +- [ ] **Step 1: Dump every public export per package** + +Run for each package (example shown for langgraph; repeat for all 7 using the entry points in the table): + +```bash +grep -nE '^export ' libs/langgraph/src/public-api.ts +grep -nE '^export ' libs/ag-ui/src/public-api.ts +grep -nE '^export ' libs/chat/src/index.ts +grep -nE '^export ' libs/a2ui/src/index.ts +grep -nE '^export ' libs/telemetry/src/index.ts +grep -nE '^export ' libs/render/src/public-api.ts +grep -nE '^export ' libs/licensing/src/index.ts +``` + +Expected: a list of `export ... from './...'` / `export const|function|class|interface|type` lines. For barrel re-exports (`export * from`), follow into the referenced file and list the concrete symbols. + +- [ ] **Step 2: Capture peer deps, version, sub-path exports, license per package** + +Run for each package: + +```bash +for p in a2ui ag-ui chat langgraph licensing render telemetry; do + echo "=== $p ==="; node -e "const j=require('./libs/$p/package.json'); console.log(JSON.stringify({version:j.version, license:j.license, peerDependencies:j.peerDependencies, exports:Object.keys(j.exports||{})}, null, 2))"; +done +``` + +Expected: per-package version (`0.0.47`), license string, exact `peerDependencies` ranges (verbatim — these go into Install sections), and sub-path export keys (e.g. `./themes/*` for chat). + +- [ ] **Step 3: Capture repo-wide trust facts** + +```bash +grep -nE "name:|nx (test|build|lint)" .github/workflows/ci.yml | head -40 +ls libs/e2e-harness libs/cockpit-testing 2>/dev/null +grep -rl "MockAgentTransport" libs/langgraph/src libs/chat/src 2>/dev/null +``` + +Expected: confirm the `library` CI job runs lint/test/build; confirm `MockAgentTransport` exists and where; note E2E harness presence. Record: patch-only `0.0.x` release policy, Angular peer range (from Step 2). + +- [ ] **Step 4: Write the inventory into the spec** + +Replace each `_TBD_` block in the Ground Truth appendix of `docs/superpowers/specs/2026-05-28-readme-refactor-design.md` with the concrete findings: per package — exported symbols (one-line purpose each), shipped capabilities, peer-dep ranges, sub-path exports, license. Fill the repo-wide facts block (CI job, test/E2E harness, release policy, Angular peers). + +- [ ] **Step 5: Verify no `_TBD_` remains** + +Run: + +```bash +grep -n "_TBD_" docs/superpowers/specs/2026-05-28-readme-refactor-design.md +``` + +Expected: no output (all placeholders filled). + +- [ ] **Step 6: Commit** + +```bash +git add docs/superpowers/specs/2026-05-28-readme-refactor-design.md +git commit -m "docs(spec): fill README refactor ground-truth inventory" +``` + +--- + +## Reusable verification pattern (used by Tasks 2–9) + +After writing a README, every public symbol it names must exist in that package's entry point, and every peer dep it lists must match `package.json`. The check: + +```bash +# 1. Each backticked export named in the README exists in source. +# Manually list the symbols the README references, then: +for sym in agent provideAgent MockAgentTransport extractCitations; do + grep -q "$sym" libs/langgraph/src/public-api.ts libs/langgraph/src/**/*.ts && echo "OK: $sym" || echo "MISSING: $sym"; +done + +# 2. Peer-dep ranges in the README match package.json verbatim. +node -e "console.log(JSON.stringify(require('./libs/langgraph/package.json').peerDependencies,null,2))" + +# 3. No stale branding (ngaf:* event names are allowed; old framework name is not). +grep -niE "angular agent framework|@ngaf/" libs/langgraph/README.md || echo "clean" +``` + +Expected: every symbol `OK`, peer deps match what the README's Install section shows, branding `clean`. + +--- + +## Task 2: Rewrite `libs/langgraph/README.md` + +**Files:** +- Modify: `libs/langgraph/README.md` +- Read: inventory block for `@threadplane/langgraph` in the spec; `libs/langgraph/src/public-api.ts` + +- [ ] **Step 1: Write the README from the inventory** + +Apply the loose-convention template: Title + tagline → badges (npm version, Angular 20+/21, MIT) → What it does → Install (peer deps verbatim) → Quick start (`provideAgent` + `agent()`, verified signatures) → Capabilities (messages/status/isLoading/error/interrupt/toolCalls/subagents/queue/branch/history/regenerate/reload — only those that exist in source) → Reliability (`MockAgentTransport` testing story, runtime-neutral architecture, patch-only releases) → Documentation → License (MIT). Keep the citations example only if `extractCitations` exists in source. + +- [ ] **Step 2: Verify every named export exists in source** + +Run the reusable verification pattern (above) with the exact symbol list the README references against `libs/langgraph/src/public-api.ts`. +Expected: every symbol `OK`; peer deps match `package.json`; branding `clean`. + +- [ ] **Step 3: Verify the package still builds (sub-path/export claims valid)** + +Run: + +```bash +npx nx build langgraph +``` + +Expected: build succeeds (README changes don't break build; this confirms the project name and that no example references a removed sub-path). + +- [ ] **Step 4: Commit** + +```bash +git add libs/langgraph/README.md +git commit -m "docs(langgraph): rewrite README from verified API inventory" +``` + +--- + +## Task 3: Rewrite `libs/ag-ui/README.md` + +**Files:** +- Modify: `libs/ag-ui/README.md` +- Read: inventory block for `@threadplane/ag-ui`; `libs/ag-ui/src/public-api.ts` + +- [ ] **Step 1: Write the README from the inventory** + +Template applied: Title + tagline (adapter for any AG-UI backend; keep the backend list) → badges → What it does → Install (`@threadplane/ag-ui @threadplane/chat @ag-ui/client` + peer deps verbatim) → Quick start (`provideAgUiAgent` + `AG_UI_AGENT`, verified) → Capabilities (citations via `bridgeCitationsState`; surface interrupts/subagents/queue/branch/history **only if** present in source) → Reliability → Documentation → License (MIT). + +- [ ] **Step 2: Verify every named export exists in source** + +Run the reusable verification pattern with the README's symbol list against `libs/ag-ui/src/public-api.ts`. +Expected: every symbol `OK`; peer deps match; branding `clean`. + +- [ ] **Step 3: Verify build** + +```bash +npx nx build ag-ui +``` + +Expected: build succeeds. + +- [ ] **Step 4: Commit** + +```bash +git add libs/ag-ui/README.md +git commit -m "docs(ag-ui): rewrite README from verified API inventory" +``` + +--- + +## Task 4: Rewrite `libs/chat/README.md` + +**Files:** +- Modify: `libs/chat/README.md` +- Read: inventory block for `@threadplane/chat`; `libs/chat/src/index.ts`; `libs/chat/package.json` exports map + +- [ ] **Step 1: Write the README from the inventory** + +Largest surface — Capabilities stays rich. Template applied: +- Title + tagline → badges (npm version, Angular 20+/21, PolyForm/Commercial). +- **License story** (keep): source-available, PolyForm Noncommercial for free noncommercial use, Threadplane Commercial for production; commercial token via `provideChat({ license })`; offline verification, non-blocking warn. +- Install + peer deps verbatim. +- Quick start (``). +- Capabilities: headless primitives + compositions (``, ``, GenUI surfaces), citations (`Citation`, ``, inline markers, `CitationsResolverService`), interrupts/HITL, subagents — **only those confirmed in source**. +- **Theming**: collapse the ~50-token dump into a tight subsection — name the four shipped theme CSS files (verify against the `exports` map / `themes/` dir), the two agent-driven knobs (`font`, `primaryColor`), and link to docs for the full token list. Do **not** inline every `--a2ui-*` token. +- Reliability → Documentation → License block. + +- [ ] **Step 2: Verify exports, theme sub-paths, and peer deps** + +Run: + +```bash +# Symbols named in the README exist: +for sym in ChatComponent provideChat Citation CitationsResolverService; do + grep -rq "$sym" libs/chat/src && echo "OK: $sym" || echo "MISSING: $sym"; +done +# Theme CSS sub-paths the README claims actually ship: +node -e "console.log(Object.keys(require('./libs/chat/package.json').exports||{}).filter(k=>k.includes('theme')))" +ls libs/chat/**/themes/*.css 2>/dev/null || find libs/chat -name '*.css' -path '*theme*' +# Peer deps: +node -e "console.log(JSON.stringify(require('./libs/chat/package.json').peerDependencies,null,2))" +``` + +Expected: every symbol `OK`; the theme files named in the README match the files/exports that actually exist; peer deps match the Install section. + +- [ ] **Step 3: Verify build** + +```bash +npx nx build chat +``` + +Expected: build succeeds. + +- [ ] **Step 4: Commit** + +```bash +git add libs/chat/README.md +git commit -m "docs(chat): rewrite README from verified API inventory" +``` + +--- + +## Task 5: Create `libs/a2ui/README.md` (net-new) + +**Files:** +- Create: `libs/a2ui/README.md` +- Read: inventory block for `@threadplane/a2ui`; `libs/a2ui/src/index.ts`; `libs/a2ui/package.json` + +- [ ] **Step 1: Write the README from the inventory** + +Full template, depth set by what the audit found a2ui actually exports: Title + tagline (what A2UI is — agent-to-UI spec rendering) → badges (npm version, Angular 20+/21, license from `package.json`) → What it does → Install (peer deps verbatim) → Quick start (using only confirmed exports) → Capabilities → Reliability → Documentation → License. If a2ui is a thin/internal-feeling surface, keep it short (collapsed template) rather than inventing features. + +- [ ] **Step 2: Verify exports and peer deps** + +Run the reusable verification pattern with the README's symbol list against `libs/a2ui/src/index.ts`, plus: + +```bash +node -e "const j=require('./libs/a2ui/package.json'); console.log(j.license, JSON.stringify(j.peerDependencies))" +``` + +Expected: every symbol `OK`; license and peer deps in the README match `package.json`. + +- [ ] **Step 3: Verify build and that README is packaged** + +```bash +npx nx build a2ui +node -e "const f=require('./libs/a2ui/package.json').files; console.log(f? f : '(no files field — npm includes README by default)')" +``` + +Expected: build succeeds; confirm README.md will be published (either no `files` allowlist, or README is included). + +- [ ] **Step 4: Commit** + +```bash +git add libs/a2ui/README.md +git commit -m "docs(a2ui): add README from verified API inventory" +``` + +--- + +## Task 6: Rewrite `libs/telemetry/README.md` + +**Files:** +- Modify: `libs/telemetry/README.md` +- Read: inventory block for `@threadplane/telemetry`; `libs/telemetry/src/index.ts` + +- [ ] **Step 1: Write the README from the inventory** + +Template applied, transparency contract preserved. Keep all `ngaf:*` event names verbatim (real wire format). Sections: Title + tagline → badges → What it does → the **event catalog** (`ngaf:postinstall`, `ngaf:runtime_instance_created`, `ngaf:runtime_request_created`, `ngaf:stream_started/ended/errored`, browser events) with the "what is NOT collected" guarantees → opt-out (`NGAF_TELEMETRY_DISABLED`, sample rate, ingest URL override) → Reliability/transparency framing → License (MIT). Verify each env var name and event name against source. + +- [ ] **Step 2: Verify event names and env vars exist in source** + +Run: + +```bash +for sym in NGAF_TELEMETRY_DISABLED NGAF_TELEMETRY_SAMPLE_RATE NGAF_TELEMETRY_INGEST_URL; do + grep -rq "$sym" libs/telemetry/src && echo "OK: $sym" || echo "MISSING: $sym"; +done +grep -roE "ngaf:[a-z_]+" libs/telemetry/src | sort -u +``` + +Expected: every env var `OK`; the printed `ngaf:*` event list matches exactly the events the README documents (no extras, none missing). + +- [ ] **Step 3: Verify build** + +```bash +npx nx build telemetry +``` + +Expected: build succeeds. + +- [ ] **Step 4: Commit** + +```bash +git add libs/telemetry/README.md +git commit -m "docs(telemetry): rewrite README from verified event inventory" +``` + +--- + +## Task 7: Rewrite `libs/render/README.md` (small) + +**Files:** +- Modify: `libs/render/README.md` +- Read: inventory block for `@threadplane/render`; `libs/render/src/public-api.ts` + +- [ ] **Step 1: Write the README from the inventory** + +Collapsed template (sections 6–7 compressed to a few lines): Title + tagline → badges → What it does (2–3 bullets) → Install (peer deps verbatim) → Quick start (confirmed exports only) → short Reliability line → Documentation → License (MIT). + +- [ ] **Step 2: Verify exports and peer deps** + +Run the reusable verification pattern with the README's symbol list against `libs/render/src/public-api.ts`. +Expected: every symbol `OK`; peer deps match; branding `clean`. + +- [ ] **Step 3: Verify build** + +```bash +npx nx build render +``` + +Expected: build succeeds. + +- [ ] **Step 4: Commit** + +```bash +git add libs/render/README.md +git commit -m "docs(render): rewrite README from verified API inventory" +``` + +--- + +## Task 8: Rewrite `libs/licensing/README.md` (small) + +**Files:** +- Modify: `libs/licensing/README.md` +- Read: inventory block for `@threadplane/licensing`; `libs/licensing/src/index.ts` + +- [ ] **Step 1: Write the README from the inventory** + +Collapsed template. Frame as the browser-safe license-token verification primitive used by `@threadplane/chat`. Title + tagline → badges → What it does → Install (peer deps verbatim) → Quick start (confirmed exports only) → short Reliability line → Documentation → License (MIT). Do not imply Node-only APIs — this lib ships into Angular browser bundles. + +- [ ] **Step 2: Verify exports and peer deps** + +Run the reusable verification pattern with the README's symbol list against `libs/licensing/src/index.ts`. +Expected: every symbol `OK`; peer deps match; branding `clean`. + +- [ ] **Step 3: Verify build** + +```bash +npx nx build licensing +``` + +Expected: build succeeds. + +- [ ] **Step 4: Commit** + +```bash +git add libs/licensing/README.md +git commit -m "docs(licensing): rewrite README from verified API inventory" +``` + +--- + +## Task 9: Rewrite root `README.md` + +**Files:** +- Modify: `README.md` +- Read: all 7 inventory blocks in the spec + +- [ ] **Step 1: Rewrite from the inventory** + +Keep the hero SVG, tagline, and architecture explainer. Changes: +- Tighten the intro. +- **Verify the `agent()` vs `useStream()` comparison table** — every Angular-side method named (`messages()`, `status()`, `isLoading()`, `error()`, `interrupt()/interrupts()`, `toolCalls()`, `branch()/history()/experimentalBranchTree()`, `queue()`, `subagents()/getSubagent()`, `submit`, `stop`, `regenerate`, `reload`, `MockAgentTransport`) must exist in `libs/langgraph/src` or `libs/chat/src`. Drop any row whose Angular symbol no longer exists. +- Add a **Packages** table: each `@threadplane/*` package → one-line purpose (from inventory) → license. +- Weave in reliability/production framing (CI `library` job, MockAgentTransport, patch-only `0.0.x`, runtime-neutral architecture). +- Keep the License section accurate to the per-package licenses confirmed in the inventory. + +- [ ] **Step 2: Verify every comparison-table and Packages-table claim** + +Run: + +```bash +for sym in messages status isLoading error interrupt interrupts toolCalls branch history experimentalBranchTree queue subagents getSubagent submit stop regenerate reload MockAgentTransport; do + grep -rqE "\b$sym\b" libs/langgraph/src libs/chat/src && echo "OK: $sym" || echo "MISSING: $sym"; +done +# Packages table: every package named is published (not private) +for p in a2ui ag-ui chat langgraph licensing render telemetry; do + node -e "const j=require('./libs/$p/package.json'); console.log('$p', j.private?'PRIVATE-REMOVE':'ok', j.license)"; +done +grep -niE "angular agent framework|@ngaf/" README.md || echo "branding clean" +``` + +Expected: every Angular symbol `OK` (rows with `MISSING` must have been dropped in Step 1); all 7 packages `ok` with correct license; no stale framework name in prose (the `cacheplane/angular-agent-framework` repo URL is allowed). + +- [ ] **Step 3: Commit** + +```bash +git add README.md +git commit -m "docs: rewrite root README from verified API inventory" +``` + +--- + +## Task 10: Final consistency & link pass + +**Files:** +- Read: all 8 READMEs + +- [ ] **Step 1: Cross-README consistency check** + +Run: + +```bash +# Same install/peer-dep claims agree across root and package READMEs. +grep -rn "peer dep" -i README.md libs/*/README.md | head +# No leftover old branding anywhere; ngaf:* event names allowed only in telemetry. +grep -rniE "@ngaf/|angular agent framework" README.md libs/*/README.md +# Every package has a README. +for p in a2ui ag-ui chat langgraph licensing render telemetry; do test -f libs/$p/README.md && echo "OK: $p" || echo "MISSING: $p"; done +``` + +Expected: peer-dep claims consistent; no `@ngaf/` or old framework name; all 7 packages `OK`. + +- [ ] **Step 2: Verify all libs still build together** + +Run: + +```bash +npx nx run-many -t build -p a2ui ag-ui chat langgraph licensing render telemetry +``` + +Expected: all builds succeed. + +- [ ] **Step 3: Final commit (if Step 1 surfaced fixes)** + +```bash +git add README.md libs/*/README.md +git commit -m "docs: README consistency pass" +``` + +--- + +## Self-Review (completed during planning) + +- **Spec coverage:** Phase 1 audit → Task 1. Per-package rewrites → Tasks 2–8. Root README incl. Packages table + comparison-table verification → Task 9. Trust signals (version/Angular badges, MockAgentTransport/CI, production framing) → embedded in every write task's Step 1. a2ui net-new → Task 5. chat theming collapse + dual-license → Task 4. telemetry `ngaf:*` preservation → Task 6. Consistency → Task 10. All spec sections covered. +- **Placeholders:** The only `_TBD_` markers live in the spec's Ground Truth appendix and are explicitly filled by Task 1, Step 4 (verified empty in Step 5). No placeholders in executable steps. +- **Type/name consistency:** Symbol names in verification commands (`agent`, `provideAgent`, `provideAgUiAgent`, `AG_UI_AGENT`, `bridgeCitationsState`, `MockAgentTransport`, `extractCitations`, `provideChat`, `CitationsResolverService`) are treated as **claims to verify against source**, not assumed-correct — if a symbol reports `MISSING`, the README must not reference it. This is by design, since the audit (Task 1) is what establishes the true names. diff --git a/docs/superpowers/specs/2026-05-28-readme-refactor-design.md b/docs/superpowers/specs/2026-05-28-readme-refactor-design.md new file mode 100644 index 00000000..3ee035ef --- /dev/null +++ b/docs/superpowers/specs/2026-05-28-readme-refactor-design.md @@ -0,0 +1,723 @@ +# README Refactor & Complete Update — Design + +**Date:** 2026-05-28 +**Status:** Approved (brainstorming) — pending implementation plan +**Scope:** Root `README.md` + the 7 published `@threadplane/*` npm package READMEs + +## Problem + +The README surfaces (root + per-package) have drifted from the framework. Two +gaps to close, confirmed with the user: + +1. **Factual / API drift** — content may describe APIs, exports, or behavior that + no longer matches the actual library source. +2. **Missing capabilities** — real shipped features (interrupts, subagents, + GenUI/A2UI, theming, citations, queue/branch/history, etc.) may not be + surfaced. + +These are developer-facing surfaces (GitHub + npm). Beyond accuracy, they should +highlight **features/capabilities**, **reliability**, and **production-readiness / +continued maintenance**. + +`@threadplane/a2ui` currently ships to npm (v0.0.47) with **no README at all** — +a concrete gap to fix. + +## In Scope + +Root `README.md` plus the 7 published packages: + +| Package | Lib path | License | +|---|---|---| +| `@threadplane/a2ui` | `libs/a2ui` | MIT (verify) | +| `@threadplane/ag-ui` | `libs/ag-ui` | MIT | +| `@threadplane/chat` | `libs/chat` | PolyForm Noncommercial + Commercial (dual) | +| `@threadplane/langgraph` | `libs/langgraph` | MIT | +| `@threadplane/licensing` | `libs/licensing` | MIT | +| `@threadplane/render` | `libs/render` | MIT | +| `@threadplane/telemetry` | `libs/telemetry` | MIT | + +## Out of Scope + +- Private/internal libs: `cockpit-*`, `db`, `design-tokens`, `example-layouts`, + `ui-react` (all `"private": true`). +- App-level READMEs: `apps/`, `examples/`, `cockpit/`, `marketing/`, `tools/`. + +## Approach (C — Verified inventory first, then write) + +Accuracy is the #1 concern, so ground truth is established **before** any prose is +written. Two phases. + +### Phase 1 — Audit (parallelizable) + +One audit per published package. Each reads: + +- Public entry points: `src/public-api.ts` / `index.ts` and barrel exports. +- `package.json`: version, `peerDependencies`, `exports` map / sub-path exports, + `files`, license. +- Capability surfaces: exported components, providers, services, functions. + +Each produces an **inventory block** (see Ground Truth appendix): + +- Public exported symbols, each with a one-line purpose. +- Shipped capabilities. +- Peer-dependency ranges (verbatim). +- Sub-path exports (e.g. `@threadplane/chat/themes/*`). +- License. + +Capture repo-wide trust facts once: CI workflow names, test/E2E harness presence, +patch-only `0.0.x` release policy, Angular 20/21 peer support. + +### Phase 2 — Write + +Rewrite all 8 READMEs from the inventory. **Anti-drift gate: every claim in a +README must trace to an entry in the Ground Truth appendix.** If it is not in the +inventory, it does not go in a README. Load-bearing claims (method signatures, +provider names) are spot-checked directly against source, not trusted from a +summary alone. + +## README Template (loose convention) + +Common section order; each package flexes depth. Tiny packages collapse 6–7 into +a line or two. + +1. **Title + one-line tagline** — what it is, who it's for. +2. **Badges** — npm version, Angular 20+/21 support, license. *(Trust: version & + Angular support.)* +3. **What it does** — 2–4 bullets of real capabilities. +4. **Install** — command + peer-dependency ranges (verbatim from `package.json`). +5. **Quick start** — minimal working example, verified against the actual API. +6. **Capabilities / Features** — the meat; scaled to package size. +7. **Reliability** — testing story (`MockAgentTransport`, E2E harness), + production-readiness framing, runtime-neutral architecture as stability + guarantee. *(Trust: test/CI + production framing.)* +8. **Documentation** — links to threadplane.ai docs. +9. **License** — per-package (MIT for most; PolyForm/commercial block for `chat`). + +**No changelog / release-cadence section** (not a selected trust signal). + +### Trust signals to surface + +- Version & Angular support: npm version badges, Angular 20/21 support matrix, + peer-dep ranges, patch-only `0.0.x` release policy. +- Test & CI signals: test coverage / E2E harness, CI status, `MockAgentTransport` + testing story. +- Production-readiness framing: "production-ready" lead language, real example + apps, runtime-neutral architecture as a stability guarantee. + +(Release cadence & changelog links intentionally excluded.) + +## Per-Package Handling + +- **`a2ui`** — net-new README, full template. Audit actual exports first to set + depth. +- **`ag-ui`** — adapter framing is good. Verify `provideAgUiAgent()` / `AG_UI_AGENT` + / `bridgeCitationsState()` against source; surface missing capabilities + (interrupts, subagents, queue, branch/history) if present. +- **`chat`** — keep the dual-license story (PolyForm Noncommercial + commercial + token via `provideChat({ license })`). Fold the ~50-token A2UI theming dump into + a tighter "Theming" subsection that links to docs instead of listing every + token inline. Largest surface (compositions, citations, GenUI, theming) — + Capabilities stays rich. +- **`langgraph`** — verify `agent()` / `provideAgent()` / `MockAgentTransport` / + `extractCitations()` signatures and citation paths; surface missing capabilities. +- **`telemetry`** — `ngaf:*` strings are real wire-format event names; keep them. + The transparency / opt-out contract is a feature; keep it, align framing. +- **`licensing`** — small; verify exports, apply collapsed template. Must stay + browser-safe in framing (consumed by Angular bundles). +- **`render`** — small; verify exports, apply collapsed template. + +## Root README + +The current root README is close. Refactor to: + +- Tighten the intro. +- Keep the `agent()` vs `useStream()` comparison table — **verify each row against + actual exports; drop rows that no longer match.** +- Add a **Packages** table mapping each `@threadplane/*` package to a one-line + purpose and its license. +- Weave in reliability / production framing. +- Keep the architecture explainer and the SVG hero / arch-diagram references as-is. + +## Ground Truth Appendix (filled during Phase 1) + +> Populated by the audit phase. Each package gets a block below. + +### Repo-wide facts + +- **CI workflows** (`.github/workflows/ci.yml`): + - `Library — lint / test / build` — runs lint, test, and build for all published libs. + - `Website — lint / build` — lints and builds the docs website. + - `Cockpit — build / test`, `Cockpit — build all examples`, `Cockpit — representative capability smoke`, `Cockpit — e2e` — example-app CI. + - `examples/chat — python smoke`, `examples/chat — e2e` — end-to-end Playwright tests (4 shards). + - `Website — e2e` — Playwright tests for the marketing/docs site. + - `CI — required` — gate job that all PRs must pass. + - `Deploy → Vercel` — deploy pipeline. + +- **Test / E2E harness**: + - `libs/e2e-harness` — present (README.md, project.json, src). Repo-level E2E harness. + - `libs/cockpit-testing` — present (package.json, project.json, src, tsconfig files). Cockpit-specific test helpers. + - `MockAgentTransport` — exported from `@threadplane/langgraph` (`libs/langgraph/src/lib/transport/mock-stream.transport.ts`). Used extensively in langgraph unit tests (agent.fn.spec, agent.provider.spec, lifecycle.spec, stream-manager.bridge.spec, agent.conformance.spec). + +- **Release policy**: patch-only at `0.0.x` (e.g. 0.0.47 → 0.0.48). Never minor-bump. Current version: `0.0.47`. + +- **Angular peer support**: `^20.0.0 || ^21.0.0` (confirmed from `@threadplane/ag-ui`, `@threadplane/chat`, `@threadplane/langgraph`, `@threadplane/render`, `@threadplane/telemetry` package.json files). + +--- + +### Per-package inventories + +#### `@threadplane/a2ui` + +**Version:** 0.0.47 | **License:** MIT | **No peerDependencies** | **No sub-path exports** + +Entry point: `libs/a2ui/src/index.ts` + +**Exported symbols:** + +Types (all `export type`): +- `A2uiTheme` — theme variant type for A2UI surfaces. +- `DynamicString`, `DynamicNumber`, `DynamicBoolean`, `DynamicStringList` — dynamic value types (literal or path-reference). +- `A2uiChildren` — child node container type. +- `A2uiActionContextEntry`, `A2uiAction` — action definition types. +- `A2uiComponent`, `A2uiComponentDef` — component node types. +- `A2uiText`, `A2uiImage`, `A2uiIcon`, `A2uiVideo`, `A2uiAudioPlayer` — media/display element types. +- `A2uiRow`, `A2uiColumn`, `A2uiList`, `A2uiCard`, `A2uiTabs`, `A2uiTabItem`, `A2uiDivider`, `A2uiModal` — layout element types. +- `A2uiButton`, `A2uiCheckBox`, `A2uiTextField`, `A2uiDateTimeInput`, `A2uiMultipleChoice`, `A2uiSlider` — interactive input element types. +- `A2uiSurfaceUpdate`, `A2uiDataModelEntry`, `A2uiDataModelUpdate`, `A2uiBeginRendering`, `A2uiDeleteSurface` — protocol message types. +- `A2uiMessage`, `A2uiSurface` — top-level message and surface types. +- `A2uiClientDataModel`, `A2uiActionMessage` — client-side data model and action message types. +- `A2uiMessageParser` — parser function type (from `parser.js`). +- `A2uiScope` — scope type for dynamic value resolution (from `resolve.js`). + +Functions: +- `getByPointer(obj, pointer)` — read a value from a JSON-pointer path. +- `setByPointer(obj, pointer, value)` — write a value at a JSON-pointer path. +- `deleteByPointer(obj, pointer)` — delete a value at a JSON-pointer path. +- `createA2uiMessageParser()` — factory that returns a streaming A2UI protocol message parser. +- `resolveDynamic(value, scope)` — resolve a dynamic value (literal or path-ref) against a data scope. +- `isLiteralString(v)`, `isLiteralNumber(v)`, `isLiteralBoolean(v)`, `isPathRef(v)` — type guards for dynamic value variants. + +**Shipped capabilities:** A2UI (Agent-to-UI) protocol type system and utilities — JSON-pointer helpers, streaming message parser, dynamic value resolver, type guards. Pure TypeScript; no Angular dependency. Runtime-neutral (used in both browser and server contexts). + +--- + +#### `@threadplane/ag-ui` + +**Version:** 0.0.47 | **License:** MIT | **No sub-path exports** + +**peerDependencies:** +```json +{ + "@threadplane/chat": "*", + "@angular/core": "^20.0.0 || ^21.0.0", + "@ag-ui/client": "*", + "rxjs": "~7.8.0" +} +``` + +Entry point: `libs/ag-ui/src/public-api.ts` + +**Exported symbols:** + +Functions / values: +- `toAgent(agUiAgent, options?)` — adapts an `@ag-ui/client` Agent instance into a `LangGraphAgent`-compatible surface consumable by ``. +- `provideAgUiAgent(config)` — DI provider function; registers an AG-UI agent in the Angular injector. +- `AG_UI_AGENT` — injection token for the AG-UI agent (type: `InjectionToken`). +- `injectAgUiAgent()` — inject helper to retrieve the AG-UI agent from DI. +- `FakeAgent` — test-only class; a fake `@ag-ui/client` Agent for unit tests. +- `provideFakeAgUiAgent(config?)` — DI provider function for the `FakeAgent` test double. +- `bridgeCitationsState(state)` — utility for advanced consumers; bridges citations from non-standard AG-UI state paths into `ngaf Citation[]`. + +Types: +- `ToAgentOptions` — options for `toAgent()`. +- `AgUiAgentConfig` — config shape for `provideAgUiAgent()`. +- `FakeAgUiAgentConfig` — config shape for `provideFakeAgUiAgent()`. + +**Shipped capabilities:** AG-UI adapter — bridges any `@ag-ui/client`-compliant agent into the Threadplane chat surface. `toAgent()` exposes `messages`, `status`, `isLoading`, `error`, `toolCalls`, and `state` as Angular Signals, plus `events$` (Observable) and `submit()`/`stop()`/`regenerate()` actions. NOTE (verified `libs/ag-ui/src/lib/to-agent.ts`): `toAgent()` does NOT wire up `interrupt` or `subagents` signals — those `Agent`-contract fields are unpopulated by this adapter. Citations are bridged separately via `bridgeCitationsState()`. Includes test utilities (`FakeAgent`, `provideFakeAgUiAgent`). + +**Confirmed present vs. plan's symbol list:** +- `provideAgUiAgent` ✓, `AG_UI_AGENT` ✓, `bridgeCitationsState` ✓, `injectAgUiAgent` ✓, `toAgent` ✓. +- `provideChat` — NOT in ag-ui (lives in `@threadplane/chat`). Not a drift; correct. + +--- + +#### `@threadplane/chat` + +**Version:** 0.0.47 | **License:** `PolyForm-Noncommercial-1.0.0 OR LicenseRef-Threadplane-Commercial` + +**dependencies:** +```json +{ + "@cacheplane/partial-json": ">=0.1.1 <0.3.0", + "@cacheplane/partial-markdown": "^0.3.0" +} +``` + +**peerDependencies:** +```json +{ + "@angular/core": "^20.0.0 || ^21.0.0", + "@angular/common": "^20.0.0 || ^21.0.0", + "@angular/forms": "^20.0.0 || ^21.0.0", + "@angular/platform-browser": "^20.0.0 || ^21.0.0", + "@threadplane/licensing": "*", + "@threadplane/render": "*", + "@threadplane/a2ui": "*", + "@json-render/core": "^0.16.0", + "@langchain/core": "^1.1.33", + "rxjs": "~7.8.0", + "marked": "^15.0.0 || ^16.0.0" +} +``` + +**Sub-path CSS exports** (keys of `exports` in package.json): +- `@threadplane/chat/chat.css` — main chat stylesheet (build output; `chat.css` is not a source file in `libs/chat/src` — it is generated at build time from `chat-tokens.ts`). +- `@threadplane/chat/themes/default-dark.css` — default dark theme (source confirmed at `libs/chat/src/themes/default-dark.css`). +- `@threadplane/chat/themes/default-light.css` — default light theme (source confirmed at `libs/chat/src/themes/default-light.css`). +- `@threadplane/chat/themes/material-dark.css` — Material dark theme (source confirmed at `libs/chat/src/themes/material-dark.css`). +- `@threadplane/chat/themes/material-light.css` — Material light theme (source confirmed at `libs/chat/src/themes/material-light.css`). + +Entry point: `libs/chat/src/public-api.ts` (barrel-re-exported from `libs/chat/src/index.ts`) + +**Exported symbols:** + +Types: +- `ChatConfig` — config shape for `provideChat()`. +- `MessageTemplateType` — string union for message template variants. +- `Agent` — runtime-neutral agent contract (base). +- `AgentWithHistory` — agent contract extended with history/branch. +- `Citation` — citation record type. +- `Message` — runtime-neutral chat message type. +- `Role` — `'user' | 'assistant' | 'tool' | 'system'`. +- `ContentBlock` — typed message content block. +- `ToolCall`, `ToolCallStatus` — tool call and its status. +- `AgentStatus` — `'idle' | 'running' | 'error'`. +- `AgentInterrupt` — interrupt value payload. +- `Subagent`, `SubagentStatus` — subagent delegate and its status. +- `AgentSubmitInput`, `AgentSubmitOptions` — submit parameter types. +- `AgentEvent`, `AgentStateUpdateEvent`, `AgentCustomEvent` — event union types. +- `AgentCheckpoint` — checkpoint record (time-travel/history). +- `AgentRuntimeTelemetryEvent`, `AgentRuntimeTelemetryPayload`, `AgentRuntimeTelemetryProperties`, `AgentRuntimeTelemetrySink` — telemetry hook types. +- `ChatRenderEvent` — render lifecycle event type for ``. +- `ChatSidenavMode` — `'side' | 'over'` sidenav mode. +- `InterruptAction` — action result type from ``. +- `ChatApprovalAction` — approval/rejection result from ``. +- `ToolCallInfo` — display data for ``. +- `TraceState` — state type for ``. +- `ChatMessageRole` — role union used in ``. +- `OverflowMenuItem` — menu item shape for ``. +- `ThreadMatch` — search result for ``. +- `ChatScrollBubbleMode` — `'up' | 'down'` scroll bubble direction. +- `ChatSelectOption` — option shape for ``. +- `Thread`, `ThreadActionAdapter` — thread record and action adapter for ``. +- `Project`, `ProjectActionAdapter` — project record and action adapter for ``. +- `ResolvedCitation` — resolved citation with source metadata. +- `ChatToolCallTemplateContext` — template context type for `ChatToolCallTemplateDirective`. +- `ViewRegistry`, `RenderEvent`, `RenderHandlerEvent`, `RenderStateChangeEvent`, `RenderLifecycleEvent`, `RenderViewEntry` — re-exported from `@threadplane/render`. +- `ContentClassifier`, `ContentType` — streaming content classifier types. +- `ParseTreeStore`, `ElementAccumulationState` — parse tree streaming types. +- `A2uiSurfaceStore`, `A2uiSurfaceState`, `A2uiViewEntry`, `A2uiViews`, `A2uiComponentView`, `PartialArgsBridge` — A2UI integration types. +- `A2uiActionMessage`, `A2uiClientDataModel`, `A2uiSurface`, `A2uiComponent`, `A2uiTheme`, `DynamicString`, `DynamicNumber`, `DynamicBoolean`, `A2uiChildren`, `A2uiAction`, `A2uiActionContextEntry`, `A2uiComponentDef` — re-exported from `@threadplane/a2ui`. +- `MockAgent`, `MockAgentOptions` — test double types. + +Functions: +- `isUserMessage(msg)`, `isAssistantMessage(msg)`, `isToolMessage(msg)`, `isSystemMessage(msg)` — role type guards. +- `getMessageType(msg)` — get the message template type string. +- `provideChat(config)` — DI provider; registers chat config (license, theme, etc.) in the Angular injector. +- `submitMessage(input, options?)` — imperative function to submit a chat message. +- `isTyping(agent)` — returns true while the agent is generating a response. +- `extractErrorMessage(err)` — extract a human-readable string from an error value. +- `getInterrupt(agent)` — get the current interrupt from an agent. +- `views(...)`, `withViews(...)`, `withoutViews(...)`, `toRenderRegistry(...)` — re-exported from `@threadplane/render`; build/modify view registries. +- `provideViews(registry)` — re-exported from `@threadplane/render`; DI provider for view registries. +- `createContentClassifier()` — factory for streaming content type classifier. +- `createParseTreeStore()` — factory for streaming parse tree state. +- `createA2uiSurfaceStore()` — factory for A2UI surface state store. +- `normalizeViewEntry(entry)` — normalize an A2UI view entry. +- `createPartialArgsBridge(...)` — factory for partial-JSON args bridge. +- `normalizeEnvelopeArgs(envelope)` — normalize A2UI action envelope args. +- `surfaceToSpec(surface)` — convert A2UI surface to `@json-render/core` spec. +- `buildA2uiActionMessage(...)` — build an A2UI action message for submission. +- `a2uiBasicCatalog` — built-in A2UI component catalog (object). +- `emitBinding(ctx, key, value)` — emit a data binding event from an A2UI catalog component. +- `isPathRef(v)`, `isLiteralString(v)`, `isLiteralNumber(v)`, `isLiteralBoolean(v)` — re-exported from `@threadplane/a2ui`. +- `renderMarkdown(md, options?)` — render a markdown string to a parse tree. +- `messageContent(msg)` — extract display-safe content string from a `Message`. +- `formatDuration(ms)` — format milliseconds to a human-readable duration string. +- `statusColor(status)` — return a CSS color token string for a subagent status. +- `cacheplaneMarkdownViews` — default Cacheplane markdown view registry (object). + +Constants / tokens: +- `CHAT_CONFIG` — injection token for `ChatConfig`. +- `CHAT_LIFECYCLE` — injection token for `ChatLifecycle` hooks. +- `MARKDOWN_VIEW_REGISTRY` — injection token for the markdown view registry. +- `VIEW_REGISTRY` — re-exported from `@threadplane/render`. +- `IS_HEADER_ROW` — injection token for table header row context. +- `CHAT_MARKDOWN_STYLES` — CSS string constant for markdown element styles. +- `ICON_CHEVRON_DOWN`, `ICON_CHEVRON_UP`, `ICON_TOOL`, `ICON_WARNING`, `ICON_AGENT`, `ICON_CHECK`, `ICON_SEND` — SVG icon string constants. + +Angular Components (UI primitives — `selector` in brackets): +- `ChatMessageListComponent` [``] — renders a scrollable list of chat messages. +- `ChatMessageComponent` [``] — renders a single chat message bubble. +- `ChatMessageActionsComponent` [``] — action toolbar for a message (copy, regenerate, etc.). +- `ChatWindowComponent` [``] — outer scroll container for a chat conversation. +- `ChatTraceComponent` [``] — collapsible execution trace / debug view. +- `ChatReasoningComponent` [``] — displays model reasoning/thinking blocks. +- `ChatLauncherButtonComponent` [``] — floating launcher button for chat popup. +- `ChatSuggestionsComponent` [``] — renders quick-reply / suggestion chips. +- `ChatInputComponent` [``] — chat text input field with send button. +- `ChatTypingIndicatorComponent` [``] — animated dots while agent is streaming. +- `ChatHistorySearchPaletteComponent` [``] — command-palette search over thread history. +- `ChatOverflowMenuComponent` [``] — kebab/overflow menu for chat actions. +- `ChatConfirmDialogComponent` [``] — modal confirmation dialog. +- `ChatScrollBubbleComponent` [``] — sticky "scroll to bottom/top" bubble indicator. +- `ChatErrorComponent` [``] — displays agent error state. +- `ChatInterruptComponent` [``] — renders an interrupt prompt inline. +- `ChatToolCallsComponent` [``] — renders the list of in-progress / completed tool calls. +- `ChatSubagentsComponent` [``] — renders delegated subagent activity. +- `ChatThreadListComponent` [``] — sidebar list of conversation threads. +- `ChatProjectListComponent` [``] — sidebar list of projects. +- `ChatGenuiSkeletonComponent` [``] — loading skeleton for GenerativeUI surfaces. +- `ChatTimelineComponent` [``] — timeline/debug strip showing agent event history. +- `ChatGenerativeUiComponent` [``] — renders A2UI generative UI surfaces. +- `A2uiSurfaceComponent` [``] — A2UI surface host; declares the `--a2ui-*` theming tokens and renders the catalog component tree. +- `ChatWelcomeComponent` [``] — empty-state welcome screen. +- `ChatWelcomeSuggestionComponent` [``] — individual suggestion chip inside the welcome screen. +- `ChatSelectComponent` [``] — styled select / dropdown primitive. +- `ChatCitationsComponent` [``] — renders citation cards attached to an assistant message. +- `ChatCitationsCardComponent` [``] — single citation card. +- `ChatSidenavScrimComponent` [``] — overlay scrim for sidenav. +- `ChatStreamingMdComponent` [``] — live streaming markdown renderer. +- `DefaultFallbackComponent` — re-exported from `@threadplane/render`; fallback when no view matches. +- `RenderElementComponent` — re-exported from `@threadplane/render`. +- `RenderSpecComponent` — re-exported from `@threadplane/render`. + +Angular Components (Compositions): +- `ChatComponent` [``] — full-page chat composition; accepts `[agent]` input. +- `ChatPopupComponent` [``] — floating popup chat composition. +- `ChatSidebarComponent` [``] — sidebar-docked chat composition. +- `ChatTimelineSliderComponent` [``] — time-travel slider UI composition. +- `ChatSidenavComponent` [``] — sidenav host composition. +- `ChatInterruptPanelComponent` [``] — full interrupt handling composition with approve/reject actions. +- `ChatApprovalCardComponent` [``] — approval card dialog composition for human-in-the-loop. +- `ChatToolCallCardComponent` [``] — rich card composition for a single tool call. +- `ChatSubagentCardComponent` [``] — rich card composition for a subagent delegation. + +Angular Directives: +- `MessageTemplateDirective` — structural directive for custom message templates in ``. +- `ChatToolCallTemplateDirective` — structural directive for custom tool call templates in ``. +- `ChatCitationCardTemplateDirective` — structural directive for custom citation card templates in ``. + +Angular Services: +- `CitationsResolverService` — resolves raw citation references into display-ready `ResolvedCitation` objects. + +Markdown view components (for per-node override via `withViews(cacheplaneMarkdownViews, {...})`): +`MarkdownDocumentComponent`, `MarkdownParagraphComponent`, `MarkdownHeadingComponent`, `MarkdownBlockquoteComponent`, `MarkdownListComponent`, `MarkdownListItemComponent`, `MarkdownCodeBlockComponent`, `MarkdownThematicBreakComponent`, `MarkdownTextComponent`, `MarkdownEmphasisComponent`, `MarkdownStrongComponent`, `MarkdownStrikethroughComponent`, `MarkdownInlineCodeComponent`, `MarkdownLinkComponent`, `MarkdownAutolinkComponent`, `MarkdownImageComponent`, `MarkdownSoftBreakComponent`, `MarkdownHardBreakComponent`, `MarkdownCitationReferenceComponent`, `MarkdownTableComponent`, `MarkdownTableRowComponent`, `MarkdownTableCellComponent`, `MarkdownChildrenComponent` (renders child markdown nodes within a parent view). + +A2UI catalog components (for `withViews()` customization): +`A2uiTextFieldComponent`, `A2uiCheckBoxComponent`, `A2uiButtonComponent`, `A2uiMultipleChoiceComponent`, `A2uiSliderComponent`, `A2uiDateTimeInputComponent`, `A2uiTextComponent`, `A2uiIconComponent`, `A2uiImageComponent`, `A2uiColumnComponent`, `A2uiRowComponent`, `A2uiCardComponent`, `A2uiDividerComponent`, `A2uiListComponent`, `A2uiModalComponent`, `A2uiTabsComponent`, `A2uiAudioPlayerComponent`, `A2uiVideoComponent`. + +Test utilities: +- `mockAgent(options?)` — factory returning a `MockAgent` test double implementing the runtime-neutral `Agent` contract. + +**Shipped capabilities:** Full Angular chat UI library. Compositions (``, ``, ``, ``), interrupt handling (``, ``), tool call visualization, subagent tracking, citation rendering, streaming markdown, time-travel timeline, A2UI generative UI surfaces, history/thread/project management UIs. Dual-license (noncommercial + commercial via `provideChat({ license })`). + +**Note on `provideChat`:** confirmed present. Commercial license token passed as `provideChat({ license: 'your-token' })`. + +--- + +#### `@threadplane/langgraph` + +**Version:** 0.0.47 | **License:** MIT + +**peerDependencies:** +```json +{ + "@threadplane/chat": "*", + "@angular/core": "^20.0.0 || ^21.0.0", + "@langchain/core": "^1.1.33", + "@langchain/langgraph-sdk": "^1.7.4", + "rxjs": "~7.8.0" +} +``` + +No sub-path exports. + +Entry point: `libs/langgraph/src/public-api.ts` + +**Exported symbols:** + +Primary function: +- `agent(options)` — creates a LangGraph-backed Angular agent within an injection context; returns a `LangGraphAgent` with Angular Signals for all state. + +Provider / DI: +- `provideAgent(config)` — DI provider; registers global `AgentConfig` (apiUrl, transport) so `agent()` calls inherit defaults. +- `AGENT_CONFIG` — injection token for `AgentConfig`. +- `AGENT_LIFECYCLE` — injection token for `AgentLifecycle`. +- `AgentLifecycleRegistry` — Angular service; registry of per-agent lifecycle objects, injectable for telemetry/observability consumers. + +Transport: +- `MockAgentTransport` — test utility; an `AgentTransport` implementation that replays scripted stream events; confirmed in `libs/langgraph/src/lib/transport/mock-stream.transport.ts`. +- `FetchStreamTransport` — production transport; streams LangGraph events over HTTP SSE. + +Client helpers: +- `createLangGraphClient(config)` — creates a `@langchain/langgraph-sdk` client; handles relative-URL normalization for browser contexts. +- `toAbsoluteApiUrl(url)` — normalizes a relative `/api`-style URL to an absolute URL. + +Thread store: +- `LangGraphThreadsAdapter` — Angular service; SDK-backed thread CRUD (list, create, delete) replacing hand-rolled `ThreadsService`. +- `LANGGRAPH_THREADS_CONFIG` — injection token for `LangGraphThreadsConfig`. +- `LANGGRAPH_CLIENT` — injection token for the `@langchain/langgraph-sdk` `Client` instance. + +Lifecycle helpers: +- `refreshOnRunEnd(agent, refreshFn)` — sets up an effect to call `refreshFn` when a run ends. +- `refreshOnTransition(agent, from, to, refreshFn)` — sets up an effect to call `refreshFn` on a specific status transition. + +Citation utility: +- `extractCitations(msg)` — extracts `Citation[]` from a LangGraph message's `additional_kwargs`; useful for consumers building custom adapters. + +Test utility: +- `mockLangGraphAgent(options?)` — factory returning a `MockLangGraphAgent` test double. + +Types: +- `AgentConfig` — config for `provideAgent()` (apiUrl, transport). +- `AgentLifecycle` — eight read-only signals tracking key stream transitions (streamStartedAt, streamErrorAt, interruptReceivedAt, interruptResolvedAt, threadCreatedAt, threadPersistedAt, toolCallStartedAt, toolCallCompletedAt). +- `AgentOptions` — options for `agent()`. +- `LangGraphAgent` — full agent surface (extends `AgentWithHistory`). +- `LangGraphMultitaskStrategy` — `'reject' | 'interrupt' | 'rollback' | 'enqueue'`. +- `LangGraphSubmitOptions` — all submit parameters (signal, config, command, resume, checkpoint, multitaskStrategy, etc.). +- `AgentQueue`, `AgentQueueEntry` — server-side queued run surface. +- `AgentBranchTree`, `AgentBranchTreeFork`, `AgentBranchTreeNode` — checkpoint branch tree types for time-travel UIs. +- `AgentTransport` — transport interface (stream, joinStream?, createQueuedRun?, cancelRun?, getHistory?, updateState?). +- `CustomStreamEvent` — custom backend event (`adispatch_custom_event()`). +- `StreamEvent` — raw stream event shape. +- `SubagentStreamRef` — reference to a subagent's streaming state (toolCallId, name, status signal, values signal, messages signal). +- `MockLangGraphAgent` — type for the mock agent test double. +- `LangGraphThreadsConfig` — config shape for `LangGraphThreadsAdapter`. +- Re-exported from SDK: `BagTemplate`, `InferBag`, `Interrupt`, `ThreadState`, `SubmitOptions`. +- `ResourceStatus` — const-object shim (Idle, Loading, Reloading, Resolved, Error, Local) for runtime comparisons. + +**Shipped capabilities:** All LangGraph agent capabilities via the `agent()` function and `LangGraphAgent` surface: +- `messages` (Signal), `status` (Signal), `isLoading` (Signal), `error` (Signal) ✓ +- `interrupt` (Signal), `langGraphInterrupts` (raw Signal) ✓ +- `toolCalls` (Signal), `langGraphToolCalls` (raw) ✓ +- `subagents` (Signal>), `getSubagent(toolCallId)`, `getSubagentsByType(type)`, `getSubagentsByMessage(msg)`, `activeSubagents` ✓ +- `queue` (Signal) ✓ +- `branch` (Signal), `setBranch(b)` ✓ +- `history` (Signal), `langGraphHistory` (raw Signal) ✓ +- `experimentalBranchTree` (Signal) ✓ +- `regenerate(assistantMessageIndex)` ✓ +- `reload()` ✓ +- `submit(input, opts?)`, `stop()` ✓ +- `switchThread(threadId)`, `joinStream(runId, lastEventId?)` ✓ +- `lifecycle` (AgentLifecycle — 8 signals) ✓ + +**Confirmed present vs. plan's symbol list:** +- `messages`, `status`, `isLoading`, `error` ✓ +- `interrupt` / `interrupts` — `interrupt` (singular, runtime-neutral) ✓; `langGraphInterrupts` (raw plural) ✓ +- `toolCalls` ✓ +- `subagents` / `getSubagent` ✓ +- `queue` ✓ +- `branch` / `history` / `experimentalBranchTree` ✓ +- `regenerate`, `reload`, `submit`, `stop` ✓ +- `MockAgentTransport` ✓ (in langgraph only; NOT in chat) +- `extractCitations` ✓ +- `provideAgent` ✓ +- `AG_UI_AGENT` — NOT in langgraph (lives in `@threadplane/ag-ui`) +- `bridgeCitationsState` — NOT in langgraph (lives in `@threadplane/ag-ui`) +- `provideAgUiAgent` — NOT in langgraph (lives in `@threadplane/ag-ui`) +- `provideChat` — NOT in langgraph (lives in `@threadplane/chat`) +- `Citation` — NOT in langgraph (lives in `@threadplane/chat`) +- `CitationsResolverService` — NOT in langgraph (lives in `@threadplane/chat`) + +--- + +#### `@threadplane/licensing` + +**Version:** 0.0.47 | **License:** MIT + +**peerDependencies:** +```json +{ + "@noble/ed25519": "^2.2.3" +} +``` + +No sub-path exports. + +Entry point: `libs/licensing/src/index.ts` + +**Exported symbols:** + +Functions: +- `verifyLicense(token, publicKey?)` — verify a signed JWT license token; returns `VerifyResult`. +- `evaluateLicense(claims, options?)` — evaluate license claims against usage context; returns `EvaluateResult` (allowed / nag / blocked). +- `emitNag(options)` — emit a console warning/nag for noncommercial-license over-use. +- `runLicenseCheck(options)` — orchestrates verify + evaluate + nag in one call; used internally by `provideChat()`. +- `signLicense(claims, privateKey)` — sign a license claims object into a JWT token (server/tooling use only). +- `inferNoncommercial(context)` — heuristically determine if the runtime context is noncommercial. + +Constants: +- `LICENSE_PUBLIC_KEY` — the Ed25519 public key used to verify Threadplane license tokens. + +Types: +- `LicenseClaims` — shape of the decoded JWT payload (tier, expiry, etc.). +- `LicenseTier` — `'developer_seat' | 'team' | 'enterprise'` (verified from `libs/licensing/src/lib/license-token.ts:4`). +- `VerifyResult` — result of `verifyLicense()` (valid, invalid, expired, etc.). +- `VerifyReason` — reason code for a `VerifyResult`. +- `LicenseStatus` — runtime license state enum/union. +- `EvaluateResult` — result of `evaluateLicense()`. +- `EvaluateOptions` — options for `evaluateLicense()`. +- `EmitNagOptions` — options for `emitNag()`. +- `RunLicenseCheckOptions` — options for `runLicenseCheck()`. + +**Shipped capabilities:** Browser-safe license verification/evaluation library. Ed25519 JWT signature verification, tier evaluation, noncommercial inference, license nag emission. No `Buffer`, no bare `process` references (Angular bundle–safe). + +--- + +#### `@threadplane/render` + +**Version:** 0.0.47 | **License:** MIT + +**peerDependencies:** +```json +{ + "@angular/core": "^20.0.0 || ^21.0.0", + "@angular/common": "^20.0.0 || ^21.0.0", + "@json-render/core": "^0.16.0" +} +``` + +No sub-path exports. + +Entry point: `libs/render/src/public-api.ts` + +**Exported symbols:** + +Types: +- `AngularComponentInputs` — mapped type extracting `@Input()` properties from an Angular component class. +- `AngularComponentRenderer` — type for a function that renders an Angular component into a view. +- `AngularRegistry` — type for the Angular-specific component registry. +- `RenderConfig` — config shape for `provideRender()`. +- `RenderContext` — context object injected into render view components. +- `RepeatScope` — scope object for repeated (list) render contexts. +- `ViewRegistry` — map from `@json-render/core` node types to Angular components. +- `RenderEvent` — base render lifecycle event type. +- `RenderHandlerEvent` — event from a user action on a rendered element. +- `RenderStateChangeEvent` — event when rendered element state changes. +- `RenderLifecycleEvent` — event for mount/unmount lifecycle. +- `RenderLifecycle` — lifecycle hook interface for `RENDER_LIFECYCLE`. +- `RenderViewEntry` — single entry in the view registry (component + metadata). + +Functions: +- `defineAngularRegistry(entries)` — create an Angular component registry from an entries map. +- `signalStateStore(initial)` — create a signal-based state store for render view components. +- `provideRender(config)` — DI provider; registers `RenderConfig` in the injector. +- `views(...)` — build a `ViewRegistry` from a list of view entries. +- `withViews(base, overrides)` — merge view registries, with overrides taking precedence. +- `withoutViews(base, keys)` — produce a view registry with specified keys removed. +- `toRenderRegistry(views)` — convert a `ViewRegistry` to a `@json-render/core` Registry. +- `provideViews(registry)` — DI provider; registers a `ViewRegistry` in the injector. + +Angular Components: +- `RenderElementComponent` [``] — renders a single JSON-render spec node. +- `RenderSpecComponent` [``] — renders a full JSON-render spec tree. +- `DefaultFallbackComponent` — fallback component when no view matches a node type. + +Injection tokens: +- `RENDER_CONTEXT` — context for the current render element. +- `REPEAT_SCOPE` — scope for a repeat (list iteration) render context. +- `RENDER_CONFIG` — global render configuration. +- `RENDER_LIFECYCLE` — lifecycle hooks token. +- `VIEW_REGISTRY` — the active view registry. + +**Shipped capabilities:** `@json-render/core`-backed Angular render engine. Renders JSON specs to Angular components via a registry pattern. Signal-based state store, view composition helpers (`views`, `withViews`, `withoutViews`), DI providers. Used internally by `@threadplane/chat` for A2UI generative UI rendering. + +--- + +#### `@threadplane/telemetry` + +**Version:** 0.0.47 | **License:** MIT + +**peerDependencies** (both optional): +```json +{ + "@angular/core": "^20.0.0 || ^21.0.0", + "posthog-js": "^1.372.0" +} +``` + +**Sub-path exports:** +- `.` — main entry (`index.js`): re-exports shared utilities. +- `./shared` — shared/public-api (browser + node isomorphic utilities). +- `./node` — Node.js telemetry adapter (captureEvent, captureRuntimeInstanceCreated, etc.). +- `./node/postinstall` — postinstall script entry (run by npm lifecycle). +- `./browser` — Angular browser telemetry service (`ThreadplaneTelemetryService`, `provideThreadplaneTelemetry`). +- `./README.md` — package README. + +**Bin:** `threadplane-telemetry-postinstall` → `./node/postinstall.js` (runs on npm install). + +Entry point: `libs/telemetry/src/index.ts` + +**Exported symbols (main `.` entry):** + +- `isTelemetryDisabled(env?)` — returns true if telemetry is disabled (via env var, CI, or DO_NOT_TRACK). +- `getDisableReason(env?)` — returns the specific disable reason: `'DO_NOT_TRACK' | 'NGAF_TELEMETRY_DISABLED' | 'CI' | null`. +- `sha256(input)` — hash utility for anonymous ID derivation. +- `getAnonId()` — returns the persistent anonymous install ID. +- `shouldSample(rate)` — returns true if this event should be sampled at the given rate. +- Types: `ThreadplaneEvent`, `ThreadplaneNodeEvent`, `ThreadplaneBrowserEvent` — event name type unions. + +**`./browser` sub-path exports:** + +- `provideThreadplaneTelemetry(config)` — Angular DI provider; registers the PostHog-backed telemetry service. +- `ThreadplaneTelemetryService` — Angular service; receives `AgentRuntimeTelemetryEvent` payloads and forwards to PostHog. +- `THREADPLANE_TELEMETRY_CONFIG` — injection token for `ThreadplaneTelemetryConfig`. +- `isLocalAnalyticsHost(url)`, `shouldCaptureAnalytics(config)` — helpers to gate PostHog capture in local/dev environments. +- Types: `ThreadplaneTelemetryConfig`, `ThreadplaneTelemetryEvent`, `ThreadplaneTelemetryEventPayload`, `ThreadplaneTelemetrySink`, `ThreadplaneBrowserEvent`, `ThreadplaneBrowserRuntimeTelemetry`, `ThreadplaneBrowserStreamErrorTelemetry`, `ThreadplaneBrowserStreamTelemetry`, `CaptureConfig`. + +**`./node` sub-path exports:** + +- `disableTelemetry()` — programmatically disable telemetry for the current process. +- `capturePostinstall(config?)` — capture a postinstall telemetry event (called by the postinstall bin). +- `captureEvent(event, payload)` — capture a generic telemetry event. +- `captureRuntimeInstanceCreated(payload)` — capture `ngaf:runtime_instance_created`. +- `captureRuntimeRequestCreated(payload)` — capture `ngaf:runtime_request_created`. +- `captureStreamStarted(payload)` — capture `ngaf:stream_started`. +- `captureStreamEnded(payload)` — capture `ngaf:stream_ended`. +- `captureStreamErrored(payload)` — capture `ngaf:stream_errored`. +- Types: `CaptureResult`, `RuntimeInstanceTelemetry`, `RuntimeRequestTelemetry`, `StreamTelemetry`. + +**`ngaf:*` event names** (from `libs/telemetry/src/shared/events.ts` — these are the wire-format strings): + +Node events (`ThreadplaneNodeEvent`): +- `ngaf:postinstall` — fired by the postinstall bin on package install. +- `ngaf:runtime_instance_created` — fired when a LangGraph agent instance is created. +- `ngaf:runtime_request_created` — fired when a run/request is initiated. +- `ngaf:stream_started` — fired when the first stream event arrives. +- `ngaf:stream_ended` — fired when a stream completes normally. +- `ngaf:stream_errored` — fired when a stream terminates with an error. + +Browser events (`ThreadplaneBrowserEvent`): +- `ngaf:browser_provided` — fired when `provideThreadplaneTelemetry()` is called. +- `ngaf:browser_chat_init` — fired when a chat component initializes. + +**Environment variable controls** (confirmed in source): +- `NGAF_TELEMETRY_DISABLED=1` — disables all telemetry. +- `NGAF_TELEMETRY_SAMPLE_RATE` — float 0–1 controlling sampling rate (in `node/client.ts`). +- `NGAF_TELEMETRY_INGEST_URL` — override ingest endpoint URL (in `node/client.ts`). +- `DO_NOT_TRACK=1` (or `npm_config_do_not_track`, `NPM_CONFIG_DO_NOT_TRACK`) — standard opt-out; also disables telemetry. +- `CI=1`, `GITHUB_ACTIONS=1`, `CONTINUOUS_INTEGRATION=1`, `BUILDKITE=1`, `CIRCLECI=1` — CI environments auto-disable telemetry. + +**Shipped capabilities:** Transparent, opt-out anonymous usage telemetry. Node.js postinstall capture, browser Angular service (PostHog-backed), shared isomorphic utilities. All event names are public wire-format strings (`ngaf:*`). Fully opt-outable via env vars or DO_NOT_TRACK. + +## Success Criteria + +- All 8 READMEs rewritten; `a2ui` has a README. +- Every factual claim traces to a verified inventory entry (no unverified API + references). +- Each README follows the loose-convention template, scaled to package size. +- Trust signals (version/Angular, test/CI, production framing) present where + applicable. +- No stale branding; `ngaf:*` event names preserved as wire format. +- Private libs and app READMEs untouched. diff --git a/libs/a2ui/README.md b/libs/a2ui/README.md new file mode 100644 index 00000000..6c8bd254 --- /dev/null +++ b/libs/a2ui/README.md @@ -0,0 +1,147 @@ +# @threadplane/a2ui + +The A2UI (Agent-to-UI) protocol type system and parsing/resolution utilities for TypeScript. Defines the wire format agents use to drive generative UI surfaces — framework-agnostic, no Angular dependency, runs in any TypeScript environment. + +

+ + npm version + + + MIT + +

+ +## What it does + +- **Protocol type system** — full TypeScript type vocabulary for every A2UI message, surface, component, layout, input, and media element an agent can emit. +- **Streaming message parser** — `createA2uiMessageParser()` returns a stateful parser that accepts JSONL chunks from a streaming agent response and emits typed `A2uiMessage` values as lines complete. +- **Dynamic value resolution** — `resolveDynamic()` resolves a dynamic value (literal wrapper or path-reference) against a client data model; four type guards (`isLiteralString`, `isLiteralNumber`, `isLiteralBoolean`, `isPathRef`) let you narrow dynamic values before passing them. +- **JSON-pointer utilities** — `getByPointer`, `setByPointer`, and `deleteByPointer` navigate and mutate the A2UI client data model using JSON-pointer paths. +- **Runtime-neutral** — pure TypeScript, no runtime dependencies, works in browsers and Node.js alike. Consumed by `@threadplane/chat` to render agent-emitted generative UI. + +## Install + +```bash +npm install @threadplane/a2ui +``` + +No peer dependencies required. + +## Quick start + +### Parse a streaming A2UI response + +```typescript +import { createA2uiMessageParser } from '@threadplane/a2ui'; +import type { A2uiMessage } from '@threadplane/a2ui'; + +const parser = createA2uiMessageParser(); + +// Feed chunks as they arrive from your agent's streaming response: +function onChunk(chunk: string): void { + const messages: A2uiMessage[] = parser.push(chunk); + for (const msg of messages) { + if ('beginRendering' in msg) { + console.log('New surface:', msg.beginRendering.surfaceId); + } else if ('surfaceUpdate' in msg) { + console.log('Surface update for:', msg.surfaceUpdate.surfaceId); + } else if ('dataModelUpdate' in msg) { + console.log('Data model delta:', msg.dataModelUpdate.contents); + } else if ('deleteSurface' in msg) { + console.log('Delete surface:', msg.deleteSurface.surfaceId); + } + } +} +``` + +### Resolve dynamic values against a data model + +```typescript +import { + resolveDynamic, + isPathRef, + isLiteralString, +} from '@threadplane/a2ui'; + +const model = { user: { name: 'Alice' } }; + +// Literal string wrapper +const label = resolveDynamic({ literalString: 'Submit' }, model); +// => 'Submit' + +// Path reference — resolves against the model via JSON pointer +const name = resolveDynamic({ path: '/user/name' }, model); +// => 'Alice' +``` + +### JSON-pointer utilities + +```typescript +import { getByPointer, setByPointer, deleteByPointer } from '@threadplane/a2ui'; + +const model = { items: [{ id: 1 }, { id: 2 }] }; + +getByPointer(model, '/items/0/id'); // => 1 +const updated = setByPointer(model, '/items/0/id', 99); +const removed = deleteByPointer(updated, '/items/0/id'); +``` + +## Capabilities + +### Protocol message parsing + +`createA2uiMessageParser()` returns an `A2uiMessageParser` with a single `push(chunk: string): A2uiMessage[]` method. The parser is stateful — it buffers partial lines between calls and emits complete messages as JSONL lines arrive. Malformed lines are silently skipped, which is safe for mid-stream partial JSON. + +```typescript +const parser = createA2uiMessageParser(); +const messages = parser.push(chunk); // A2uiMessage[] +``` + +### Dynamic value resolution + +`resolveDynamic(value, model, scope?)` unwraps A2UI dynamic values: + +- `{ literalString: string }`, `{ literalNumber: number }`, `{ literalBoolean: boolean }` — unwrap to the inner value. +- `{ path: string }` — looked up in `model` via JSON pointer. If an `A2uiScope` is provided, relative paths resolve against `scope.basePath`. +- Plain literals (string, number, boolean, `null`, plain objects) — passed through unchanged. + +The four exported type guards narrow `unknown` values before use: + +| Guard | Narrows to | +|---|---| +| `isLiteralString(v)` | `{ literalString: string }` | +| `isLiteralNumber(v)` | `{ literalNumber: number }` | +| `isLiteralBoolean(v)` | `{ literalBoolean: boolean }` | +| `isPathRef(v)` | `{ path: string }` | + +### JSON-pointer utilities + +Three immutable helpers operate on `Record` data models: + +| Function | Description | +|---|---| +| `getByPointer(model, pointer)` | Read the value at `pointer`. Returns `undefined` if the path does not exist. | +| `setByPointer(model, pointer, value)` | Return a new model with `value` written at `pointer`. | +| `deleteByPointer(model, pointer)` | Return a new model with the key at `pointer` removed. | + +Pointers follow standard `/segment/segment` syntax. An empty pointer (`''` or `'/'`) targets the root. + +### Type system + +`@threadplane/a2ui` exports the complete A2UI type vocabulary as TypeScript `export type` declarations. Categories include: + +- **Protocol messages** — `A2uiMessage`, `A2uiBeginRendering`, `A2uiSurfaceUpdate`, `A2uiDataModelUpdate`, `A2uiDeleteSurface`, `A2uiSurface`, `A2uiDataModelEntry` +- **Components and layout** — `A2uiComponent`, `A2uiComponentDef`, `A2uiRow`, `A2uiColumn`, `A2uiCard`, `A2uiList`, `A2uiTabs`, `A2uiTabItem`, `A2uiModal`, `A2uiDivider` +- **Input elements** — `A2uiButton`, `A2uiTextField`, `A2uiCheckBox`, `A2uiSlider`, `A2uiMultipleChoice`, `A2uiDateTimeInput` +- **Media and display** — `A2uiText`, `A2uiImage`, `A2uiIcon`, `A2uiVideo`, `A2uiAudioPlayer` +- **Dynamic values** — `DynamicString`, `DynamicNumber`, `DynamicBoolean`, `DynamicStringList` +- **Actions and theming** — `A2uiAction`, `A2uiActionMessage`, `A2uiActionContextEntry`, `A2uiTheme`, `A2uiClientDataModel` +- **Parser and scope** — `A2uiMessageParser`, `A2uiScope`, `A2uiChildren` + +## Reliability + +Pure TypeScript with no runtime dependencies. No `Buffer`, no `process`, no DOM — safe in any TypeScript environment. Follows the repo's patch-only `0.0.x` release policy. The "Library — lint / test / build" CI job runs on every pull request. + +## License + +MIT. See [LICENSE](../../LICENSE). diff --git a/libs/ag-ui/README.md b/libs/ag-ui/README.md index 8a12a6d2..08740b7f 100644 --- a/libs/ag-ui/README.md +++ b/libs/ag-ui/README.md @@ -1,43 +1,96 @@ # @threadplane/ag-ui -AG-UI protocol adapter for Angular. Wraps an [AG-UI](https://github.com/ag-ui-protocol/ag-ui) `AbstractAgent` into the runtime-neutral `Agent` contract from `@threadplane/chat`. Works with any AG-UI-compatible backend — CrewAI, Mastra, Microsoft Agent Framework, AG2, Pydantic AI, AWS Strands, CopilotKit runtime, or LangGraph fronted by AG-UI. - -Part of [Threadplane](https://github.com/cacheplane/angular-agent-framework). MIT licensed. +Adapter that wraps an [AG-UI](https://github.com/ag-ui-protocol/ag-ui) `AbstractAgent` into the runtime-neutral `Agent` contract from `@threadplane/chat`. Works with any AG-UI-compatible backend. + +

+ + npm version + + + Angular 20+ + + + MIT + +

+ +Part of [Threadplane](https://github.com/cacheplane/angular-agent-framework). > Talking to LangGraph Platform directly? See [`@threadplane/langgraph`](https://www.npmjs.com/package/@threadplane/langgraph) — same API shape, LangGraph SDK underneath. +--- + +## What it does + +- Bridges any AG-UI-compatible backend into the Threadplane chat surface via `toAgent()`. +- Supports: LangGraph, CrewAI, Mastra, Microsoft Agent Framework, AG2, Pydantic AI, AWS Strands, CopilotKit runtime. +- Exposes messages, status, tool calls, and raw AG-UI state as Angular Signals, plus `submit()`/`stop()`/`regenerate()` actions — coverage depends on what the AG-UI backend emits. +- Ships `FakeAgent` and `provideFakeAgent` test doubles for unit testing without a live backend. + +--- + ## Install ```bash npm install @threadplane/ag-ui @threadplane/chat @ag-ui/client ``` +**Peer dependencies:** `@threadplane/chat: *`, `@angular/core: ^20.0.0 || ^21.0.0`, `@ag-ui/client: *`, `rxjs: ~7.8.0` + +--- + ## Quick start -```ts -import { provideAgent, injectAgent } from '@threadplane/ag-ui'; -import { ChatComponent } from '@threadplane/chat'; +Register the agent in your `ApplicationConfig`, then inject it into a component and bind it to ``. +```ts // app.config.ts +import { provideAgent } from '@threadplane/ag-ui'; + export const appConfig: ApplicationConfig = { providers: [provideAgent({ url: 'https://your.agent.endpoint' })], }; +``` + +```ts +// app.component.ts +import { Component } from '@angular/core'; +import { ChatComponent } from '@threadplane/chat'; +import { injectAgent } from '@threadplane/ag-ui'; -// component @Component({ imports: [ChatComponent], template: ``, }) -export class App { +export class AppComponent { protected readonly agent = injectAgent(); } ``` -## Citations +Both `@threadplane/langgraph` and `@threadplane/ag-ui` expose `provideAgent`/`injectAgent` with the same shape — consumer code is identical regardless of which adapter is wired in. + +--- + +## Capabilities + +`toAgent()` translates AG-UI events into Angular Signals on the runtime-neutral `Agent` contract: + +| Signal | Description | +|---|---| +| `messages()` | Chat message history | +| `status()` | `'idle' \| 'running' \| 'error'` | +| `isLoading()` | True while a run is active | +| `toolCalls()` | In-progress and completed tool calls | +| `error()` | Last run error, if any | +| `state()` | Raw AG-UI state snapshot | + +Which capabilities populate depends on the events the AG-UI backend emits. `submit()`, `stop()`, and `regenerate()` are supported. + +### Citations -The `bridgeCitationsState()` function populates `Message.citations` from AG-UI STATE_DELTA events. Citations are located at JSON Pointer `/citations/{messageId}`. +`bridgeCitationsState(thread, messages)` populates `Message.citations` from AG-UI state. Citations live under the `citations` key of the agent state, keyed by message ID (`state.citations[messageId]`). -### Example: AG-UI citations state shape +Example state shape: ```json { @@ -57,7 +110,19 @@ The `bridgeCitationsState()` function populates `Message.citations` from AG-UI S } ``` -Each citation object in the array supports `id`, `index`, `title`, `url`, `snippet`, and custom `extra` fields. The messageId key matches the corresponding message in the chat history. +Each citation supports `id`, `index`, `title`, `url`, `snippet`, and custom `extra` fields. The message ID key matches the corresponding message in the chat history. + +### Testing + +`FakeAgent` is a test-only `AbstractAgent` implementation. `provideFakeAgent(config?)` registers it in the Angular injector so unit tests run without a live AG-UI backend. + +--- + +## Reliability + +`@threadplane/ag-ui` shares the same runtime-neutral `Agent` contract as `@threadplane/langgraph`, making it interchangeable at the `` binding. The library follows a patch-only `0.0.x` release policy. The CI job "Library — lint / test / build" runs lint, test, and build on every pull request. + +--- ## Documentation @@ -66,6 +131,8 @@ Each citation object in the array supports `id`, `index`, `title`, `url`, `snipp - [AG-UI protocol](https://github.com/ag-ui-protocol/ag-ui) - [Choosing an adapter (LangGraph vs AG-UI)](https://threadplane.ai/docs/choosing-an-adapter) +--- + ## License -MIT — free for any use. See [LICENSE](../../LICENSE). +MIT. See [LICENSE](../../LICENSE). diff --git a/libs/chat/README.md b/libs/chat/README.md index b05ab6eb..ce4dfe19 100644 --- a/libs/chat/README.md +++ b/libs/chat/README.md @@ -1,28 +1,55 @@ # @threadplane/chat -Drop-in agent chat UI for Angular 20+. Headless primitives that read from a runtime-neutral `Agent` contract, plus opinionated compositions (``, ``, GenUI surfaces) you can ship in days. +Drop-in agent chat UI for Angular 20+. Headless UI primitives plus opinionated compositions that read a runtime-neutral `Agent` contract — ship a production chat surface in days without coupling to a specific backend. Part of [Threadplane](https://github.com/cacheplane/angular-agent-framework). -`@threadplane/chat` is source-available and free for noncommercial use under PolyForm Noncommercial License 1.0.0. Commercial production use requires a Threadplane Commercial license. +

+ + npm version + + Angular 20+ + License +

-This package is not licensed as OSI open source because commercial use requires a license. Threadplane uses a source-available model for `@threadplane/chat` while keeping protocol and ecosystem packages permissively licensed where appropriate. +**Source-available.** Free for noncommercial use under PolyForm Noncommercial License 1.0.0. Commercial production use — SaaS, internal tools, agency work, paid client projects — requires a [Threadplane Commercial license](https://threadplane.ai/pricing). -## Commercial use +--- -Building a commercial product, SaaS application, internal business tool, agency deliverable, or paid client project with `@threadplane/chat` requires a Threadplane Commercial license. +## What it does -Free under PolyForm Noncommercial: +- **Full chat surface in one tag.** `` wires up message history, streaming output, typing indicator, input, interrupts, tool calls, subagents, citations, and generative UI — all from a single binding. +- **Layered architecture.** Use the opinionated compositions for fast shipping, drop down to individual primitives (30+) to build custom layouts, or mix both. +- **Runtime-neutral.** Compositions consume an `Agent` contract. The library has no hard dependency on LangGraph, AG-UI, or any other backend — swap or combine adapters without touching your UI. +- **A2UI generative UI.** Agents emit structured surface specs; `` renders them as interactive Angular components with a themeable `--a2ui-*` token system. -- Personal, hobby, student, academic, nonprofit, public-demo use -- Open-source applications released under an OSI-approved license -- 30 calendar days of commercial evaluation from your first commercial use (good-faith — no tracking, no email required) +--- -See [COMMERCIAL-USE.md](./COMMERCIAL-USE.md) for the definition of commercial use, [LICENSE-COMMERCIAL.md](./LICENSE-COMMERCIAL.md) for the commercial license summary, and the [Threadplane pricing page](https://threadplane.ai/pricing) for plans. +## Install -## Using a commercial license +```bash +npm install @threadplane/chat +``` + +**Peer dependencies:** -After purchase, Threadplane emails a signed license token to the address on your receipt. The license is valid for 12 months and the same email contains the token to paste into your app's `provideChat()` configuration: +``` +@angular/core ^20.0.0 || ^21.0.0 +@angular/common ^20.0.0 || ^21.0.0 +@angular/forms ^20.0.0 || ^21.0.0 +@angular/platform-browser ^20.0.0 || ^21.0.0 +@threadplane/licensing * +@threadplane/render * +@threadplane/a2ui * +@json-render/core ^0.16.0 +@langchain/core ^1.1.33 +rxjs ~7.8.0 +marked ^15.0.0 || ^16.0.0 +``` + +--- + +## Quick start ```typescript // app.config.ts @@ -30,134 +57,247 @@ import { ApplicationConfig } from '@angular/core'; import { provideChat } from '@threadplane/chat'; export const appConfig: ApplicationConfig = { - providers: [ - provideChat({ - license: 'eyJ…', // The token from your purchase email. - }), - ], + providers: [provideChat({ license: 'eyJ…' })], }; ``` -The library verifies the token's signature on boot. A missing, expired, or tampered token logs a `console.warn` advisory but does not block rendering — chat continues to work either way. Tokens are validated offline; no calls to Threadplane are made at runtime. +```typescript +// my.component.ts +import { Component } from '@angular/core'; +import { ChatComponent } from '@threadplane/chat'; +import { agent } from '@threadplane/langgraph'; + +@Component({ + selector: 'app-root', + imports: [ChatComponent], + template: ``, +}) +export class AppComponent { + myAgent = agent({ apiUrl: '/api/langgraph', graphId: 'agent' }); +} +``` -The license string is safe to commit to source control if your repository is private, or to read from a build-time env var for public repositories: +Get the `agent` signal from `@threadplane/langgraph` (for LangGraph Platform backends) or `@threadplane/ag-ui` (for AG-UI-compatible backends). See those packages for setup details. -```typescript -declare const THREADPLANE_LICENSE: string | undefined; +--- -providers: [ - provideChat({ - license: typeof THREADPLANE_LICENSE === 'string' ? THREADPLANE_LICENSE : undefined, - }), -], -``` +## Capabilities -(See `examples/chat/angular/` in the framework repo for a working example.) +### Compositions -## Runtime adapters +Ready-to-use full-feature layouts: -Chat primitives consume a runtime-neutral `Agent` contract. Two adapters ship today: +| Component | Selector | Description | +|---|---|---| +| `ChatComponent` | `` | Full-page chat layout; primary entry point | +| `ChatPopupComponent` | `` | Floating popup with a launcher button | +| `ChatSidebarComponent` | `` | Sidebar-docked layout | +| `ChatSidenavComponent` | `` | Sidenav host with project/thread list panel | +| `ChatTimelineSliderComponent` | `` | Time-travel slider for agent checkpoint history | +| `ChatInterruptPanelComponent` | `` | Full interrupt-handling composition | +| `ChatApprovalCardComponent` | `` | Approval/rejection dialog for HITL flows | +| `ChatToolCallCardComponent` | `` | Rich card for a single tool call | +| `ChatSubagentCardComponent` | `` | Rich card for a subagent delegation | -- **`@threadplane/langgraph`** — for LangGraph / LangGraph Platform backends. -- **`@threadplane/ag-ui`** — for any AG-UI-compatible backend (LangGraph, CrewAI, Mastra, Microsoft Agent Framework, AG2, Pydantic AI, AWS Strands, CopilotKit runtime). +### Primitives -Custom backends can implement `Agent` directly with no library dependency. +30+ standalone components for custom layouts: -See the capability matrix in the docs site for which primitives require which runtime capabilities. +``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``. -## Citations +Custom content templates for message bubbles, tool call rows, and citation cards use structural directives: `MessageTemplateDirective`, `ChatToolCallTemplateDirective`, and `ChatCitationCardTemplateDirective`. -Chat messages can include citations to sources. The `Citation` interface provides structured metadata for each source: +### Human-in-the-loop (interrupts) -```ts -interface Citation { - id: string; // Unique identifier for the citation - index?: number; // Display index (1-based) for inline markers - title?: string; // Source title - url?: string; // Source URL - snippet?: string; // Quoted excerpt - extra?: unknown; // Custom fields per adapter -} +`` surfaces the current `AgentInterrupt` from an agent and renders approve/reject controls. `` composes as a dialog for explicit approval workflows. Both emit typed action results (`InterruptAction`, `ChatApprovalAction`) that the caller submits back to the agent. + +```html + +``` + +### Tool calls and subagents + +`` renders in-progress and completed tool calls. Customize per-call layout with `ChatToolCallTemplateDirective` — the `chatToolCallTemplate` input takes a tool name to match, or `"*"` for all; the template context exposes the `ToolCall` (`$implicit`) and its `status`: + +```html + + + + + ``` -### Message citations +`` and `` track delegated subagent activity with live status. -Adapters populate `Message.citations?: Citation[]` from their respective backends. Messages are rendered with the `` primitive, which displays a collapsible sources panel under assistant messages. +### Citations -### Rendering sources +The `Citation` interface provides structured source metadata for assistant messages: -Use the `` component to render a sources panel. Customize the card layout with the optional `chatCitationCard` ng-template: +```ts +interface Citation { + id: string; + index?: number; // 1-based display index for inline superscript markers + title?: string; + url?: string; + snippet?: string; + extra?: unknown; // adapter-specific fields +} +``` + +Use `` to render a collapsible sources panel under assistant messages. Customize the card layout with the `chatCitationCard` template directive: ```html - - -
- {{ citation.title }} -

{{ citation.snippet }}

-
+ {{ citation.title }} +

{{ citation.snippet }}

``` -### Inline markers +Inline citation markers are rendered automatically by `MarkdownCitationReferenceComponent` inside streaming markdown output — superscript indices link to the corresponding card in the sources panel. + +`CitationsResolverService` resolves raw `Citation` references into `ResolvedCitation` objects with full source metadata. + +**Adapter integration:** +- **LangGraph** — reads from `message.additional_kwargs.citations` (preferred) or `.sources` (fallback). +- **AG-UI** — `bridgeCitationsState` reads `state.citations[messageId]` from the agent state on `STATE_SNAPSHOT` and `STATE_DELTA` events. -Markdown rendering registers `chat-md-citation-reference` in the markdown view registry. Citation indices are rendered as superscript markers inline with the message text. The markers link to the corresponding citation in the sources panel. +### GenUI / A2UI surfaces -### Adapter integration +`` renders A2UI surface specs emitted by agents. `` is the underlying host that maps the spec to Angular catalog components (`A2uiButtonComponent`, `A2uiTextFieldComponent`, `A2uiCheckBoxComponent`, etc.). -Each runtime adapter extracts citations into the `Message.citations` array: +Agents can emit surface specs via `buildA2uiActionMessage(...)`. Actions from catalog components flow back to the agent as structured messages. -- **LangGraph** — reads from `message.additional_kwargs.citations` (preferred) or `message.additional_kwargs.sources` (fallback) -- **AG-UI** — reads from STATE_DELTA at JSON Pointer `/citations/{messageId}` +The built-in catalog ships via `a2uiBasicCatalog`. Compose a custom catalog with `withViews()` and pass it to the surface. -The `CitationsResolverService` is provided to query citations in message-first or markdown-fallback order. +### Streaming markdown -## A2UI surface theming +`` renders markdown token-by-token as the agent streams. The `cacheplaneMarkdownViews` registry maps each CommonMark node type to an Angular component. -`` declares ~50 internal `--a2ui-*` tokens at `:host` with dark-theme defaults (spacing, typography, shape radius, focus ring, motion, elevation, color). Catalog components consume them via `var(--a2ui-*)`. Override at `:root` to retheme. +Override individual node renderers: -### Agent-driven theming (v1 wire format) +```typescript +import { withViews, cacheplaneMarkdownViews, provideViews } from '@threadplane/chat'; +import { MyCodeBlockComponent } from './my-code-block.component'; -Agents control exactly two knobs per the canonical A2UI v1-compatible spec, set via `beginRendering.styles`: +providers: [ + provideViews( + withViews(cacheplaneMarkdownViews, { code: MyCodeBlockComponent }) + ), +], +``` -- `font` — primary font family -- `primaryColor` — hex `#RRGGBB` +The `renderMarkdown(md, options?)` function produces a parse tree for use outside streaming contexts. -Both flow through to the rendered surface as inline styles on `` (highest specificity), overriding any consumer `:root` defaults for the lifetime of that surface. +### Theming -### Built-in theme presets +`` declares ~50 `--a2ui-*` CSS custom properties at `:host` with dark-theme defaults covering color, spacing, typography, shape radius, focus ring, motion, and elevation. Catalog components consume them via `var(--a2ui-*)`. -Four CSS files ship in the package, each declaring `:root` overrides for the relevant tokens: +**Built-in presets** — import one in your global stylesheet: ```css -/* In your global stylesheet */ -@import '@threadplane/chat/themes/default-dark.css'; /* lib defaults, explicit */ -@import '@threadplane/chat/themes/default-light.css'; /* neutral light, blue accent */ -@import '@threadplane/chat/themes/material-dark.css'; /* Material Design 3 dark */ -@import '@threadplane/chat/themes/material-light.css'; /* Material Design 3 light */ +@import '@threadplane/chat/themes/default-dark.css'; /* lib defaults, explicit */ +@import '@threadplane/chat/themes/default-light.css'; /* neutral light, blue accent */ +@import '@threadplane/chat/themes/material-dark.css'; /* Material Design 3 dark */ +@import '@threadplane/chat/themes/material-light.css'; /* Material Design 3 light */ ``` -Material presets map [Material Design 3 color tokens](https://m3.material.io/styles/color/the-color-system/tokens) to the `--a2ui-*` vocabulary — no `@angular/material` runtime dep, just CSS custom-property declarations. +Material presets map M3 color tokens to the `--a2ui-*` vocabulary with no `@angular/material` runtime dependency. -### Custom themes +**Agent-driven theming.** Agents control two knobs per the A2UI v1 wire format, set via `beginRendering.styles`: `font` (font family string) and `primaryColor` (hex `#RRGGBB`). These flow to `` as inline styles and take precedence over `:root` defaults for that surface. -Override any subset of the ~50 tokens at `:root`: +**Custom themes.** Override any token at `:root`: ```css :root { - --a2ui-primary: #FF6B35; /* brand orange */ - --a2ui-shape-medium: 4px; /* sharper corners */ - --a2ui-spacing-3: 16px; /* roomier layouts */ + --a2ui-primary: #FF6B35; + --a2ui-shape-medium: 4px; + --a2ui-spacing-3: 16px; } ``` -The token surface: -- **Color** — `--a2ui-primary`, `--a2ui-on-primary`, `--a2ui-secondary`, `--a2ui-surface`, `--a2ui-on-surface`, `--a2ui-surface-variant`, `--a2ui-on-surface-variant`, `--a2ui-outline`, `--a2ui-outline-variant`, `--a2ui-error`, `--a2ui-on-error`, `--a2ui-scrim` -- **Spacing** — `--a2ui-spacing-1` (4px) through `--a2ui-spacing-7` (40px) -- **Typography** — `--a2ui-typography-{h1..h5,body,caption,label}-{size,weight,line-height}` -- **Shape** — `--a2ui-shape-{extra-small,small,medium,large,extra-large}` -- **Focus ring** — `--a2ui-focus-ring-color`, `--a2ui-focus-ring-width` -- **Motion** — `--a2ui-motion-duration-{short,medium,long}`, `--a2ui-motion-easing-{standard,emphasized}` -- **Elevation** — `--a2ui-elevation-{0,1,2,3,4,5}` (box-shadow tokens) +The full token vocabulary (`--a2ui-primary`, `--a2ui-spacing-1..7`, `--a2ui-typography-*`, `--a2ui-shape-*`, `--a2ui-elevation-*`, etc.) is documented at [threadplane.ai/docs/chat](https://threadplane.ai/docs/chat). + +--- + +## Runtime adapters + +Chat compositions consume the runtime-neutral `Agent` contract. Two adapters ship today: + +- **`@threadplane/langgraph`** — for LangGraph / LangGraph Platform backends. +- **`@threadplane/ag-ui`** — for AG-UI-compatible backends (LangGraph, CrewAI, Mastra, Microsoft Agent Framework, AG2, Pydantic AI, AWS Strands, CopilotKit runtime). + +Custom backends implement the `Agent` (or `AgentWithHistory`) interface directly with no library dependency. + +--- + +## Commercial use + +Building a commercial product, SaaS application, internal business tool, agency deliverable, or paid client project with `@threadplane/chat` requires a Threadplane Commercial license. + +Free under PolyForm Noncommercial: + +- Personal, hobby, student, academic, nonprofit, and public-demo use +- Open-source applications released under an OSI-approved license +- 30 calendar days of commercial evaluation from your first commercial use (good-faith — no tracking, no email required) + +See [COMMERCIAL-USE.md](./COMMERCIAL-USE.md) for the definition of commercial use, [LICENSE-COMMERCIAL.md](./LICENSE-COMMERCIAL.md) for the commercial license summary, and the [Threadplane pricing page](https://threadplane.ai/pricing) for plans. + +## Using a commercial license + +After purchase, Threadplane emails a signed license token to the address on your receipt. The license is valid for 12 months. Pass the token to `provideChat()`: + +```typescript +// app.config.ts +import { ApplicationConfig } from '@angular/core'; +import { provideChat } from '@threadplane/chat'; + +export const appConfig: ApplicationConfig = { + providers: [ + provideChat({ + license: 'eyJ…', // Token from your purchase email. + }), + ], +}; +``` + +The library verifies the token's signature on boot. A missing, expired, or tampered token logs a `console.warn` advisory but does not block rendering — chat continues to work either way. Tokens are validated offline; no calls to Threadplane are made at runtime. + +The license string is safe to commit to source control for private repositories, or read from a build-time env var for public ones: + +```typescript +declare const THREADPLANE_LICENSE: string | undefined; + +providers: [ + provideChat({ + license: typeof THREADPLANE_LICENSE === 'string' ? THREADPLANE_LICENSE : undefined, + }), +], +``` + +--- + +## Reliability + +`@threadplane/chat` follows a patch-only release cadence (`0.0.x`). The runtime-neutral `Agent` contract is a stability boundary: adapter updates do not break chat UI code and vice versa. The package is covered by the monorepo's CI lint, test, and build pipeline on every commit. + +--- + +## Documentation + +Full API reference, capability matrix, and examples: [threadplane.ai/docs/chat](https://threadplane.ai/docs/chat). + +--- + +## License + +`@threadplane/chat` is dual-licensed: + +- **Noncommercial use:** [PolyForm Noncommercial License 1.0.0](./LICENSE.md) +- **Commercial use:** [Threadplane Commercial License](./LICENSE-COMMERCIAL.md) + +See [COMMERCIAL-USE.md](./COMMERCIAL-USE.md) for the definition of commercial use. diff --git a/libs/langgraph/README.md b/libs/langgraph/README.md index bda999bc..77ded38a 100644 --- a/libs/langgraph/README.md +++ b/libs/langgraph/README.md @@ -1,30 +1,49 @@ # @threadplane/langgraph -LangChain/LangGraph adapter for Angular. Wraps a LangGraph agent into the runtime-neutral `Agent` contract from `@threadplane/chat` — signal-driven access to messages, status, tool calls, interrupts, subagents, regenerate, and thread history. The Angular equivalent of LangGraph's React `useStream()` hook. - -Part of [Threadplane](https://github.com/cacheplane/angular-agent-framework). MIT licensed. +Adapter that wraps a LangGraph agent into the runtime-neutral `Agent` contract from `@threadplane/chat`. The Angular equivalent of LangGraph's React `useStream()` hook — signal-driven access to messages, status, tool calls, interrupts, subagents, branch history, and thread persistence. + +

+ + npm version + + + Angular 20+ + + + MIT + +

> Talking to a non-LangGraph backend? See [`@threadplane/ag-ui`](https://www.npmjs.com/package/@threadplane/ag-ui) — same API shape, AG-UI protocol underneath. +## What it does + +- **`provideAgent()`** — wire the LangGraph adapter into Angular DI. Provided at the root injector or at any component subtree (multi-thread UIs work via Angular's hierarchical DI). +- **`injectAgent()`** — retrieve the configured `LangGraphAgent` in any component. Returns a `LangGraphAgent` whose entire state surface (`messages`, `status`, `isLoading`, `error`, `interrupt`, `toolCalls`, `subagents`, `queue`, `branch`, `history`, and more) is exposed as Angular Signals. No subscriptions, no `async` pipe, no zone.js required. +- **Human-in-the-loop** — `interrupt()` delivers a runtime-neutral interrupt value; `langGraphInterrupts()` exposes the raw LangGraph interrupt list when you need it. +- **Subagent streaming** — `subagents()` + `getSubagent(toolCallId)`, `getSubagentsByType(type)`, `getSubagentsByMessage(msg)`, and `activeSubagents()` surface streaming subgraph state without extra bookkeeping. +- **Time-travel and thread persistence** — `branch()` / `history()` / `experimentalBranchTree()` enable checkpoint navigation; `LangGraphThreadsAdapter` provides SDK-backed thread CRUD so you never have to hand-roll thread management. + ## Install ```bash npm install @threadplane/langgraph @threadplane/chat ``` -**Peer dependencies:** `@angular/core ^20.0.0 || ^21.0.0`, `@langchain/core ^1.1.0`, `@langchain/langgraph-sdk ^1.7.0`, `rxjs ~7.8.0` - -## What it does +**Peer dependencies:** -- **`provideAgent()`** — wire the LangGraph adapter into Angular DI. Provided at the root injector or at any component subtree (multi-thread UIs work via Angular's hierarchical DI). -- **`injectAgent()`** — retrieve the configured `LangGraphAgent` in any component. No arguments — config flows through DI. -- **Signal-driven runtime** — `messages()`, `status()`, `isLoading()`, `error()`, `interrupt()`, `toolCalls()`, plus actions (`submit`, `stop`, `regenerate`, `reload`). -- **Thread persistence** — pass `threadId: signal(...)` + `onThreadId` in the config to round-trip thread IDs through your own storage (localStorage, URL, etc.). -- **`MockAgentTransport`** — deterministic in-memory transport for tests. Never mock `injectAgent()` itself; swap the transport instead. -- **`extractCitations()`** — populates `Message.citations` from LangGraph message metadata. Reads from `additional_kwargs.citations` (preferred) or `additional_kwargs.sources` (fallback). +``` +@threadplane/chat * +@angular/core ^20.0.0 || ^21.0.0 +@langchain/core ^1.1.33 +@langchain/langgraph-sdk ^1.7.4 +rxjs ~7.8.0 +``` ## Quick start +Configure the LangGraph endpoint once in `app.config.ts`: + ```ts // app.config.ts import { provideAgent } from '@threadplane/langgraph'; @@ -39,6 +58,8 @@ export const appConfig: ApplicationConfig = { }; ``` +Then call `injectAgent()` in any component and pass the result to ``: + ```ts // chat.component.ts import { Component } from '@angular/core'; @@ -47,39 +68,126 @@ import { ChatComponent } from '@threadplane/chat'; @Component({ imports: [ChatComponent], - template: ``, + template: ``, }) export class ChatComponentHost { - protected readonly agent = injectAgent(); + protected readonly chat = injectAgent(); } ``` +> `injectAgent()` must be called within an Angular injection context — a component field initializer or constructor. Calling it in `ngOnInit` or any async context throws `NG0203: inject() must be called from an injection context`. + > Need a different agent for a specific component subtree (e.g., a sidebar showing a separate conversation)? Re-provide `provideAgent({...})` in that component's `providers: []` array — Angular's hierarchical DI takes care of the rest. -## Citations example +## Capabilities + +### Messages, status, and errors + +| Signal | Type | Description | +|---|---|---| +| `messages()` | `Message[]` | Accumulated chat messages from the stream | +| `status()` | `'idle' \| 'running' \| 'error'` | Runtime-neutral run status | +| `isLoading()` | `boolean` | `true` while a run is streaming | +| `error()` | `unknown \| null` | Last error, if any | + +### Human-in-the-loop (interrupts) ```ts -// In your LangGraph node: -const response = await llm.invoke([...]); - -return new AIMessage({ - content: response.content, - additional_kwargs: { - citations: [ - { - id: 'doc-1', - index: 1, - title: 'Example Article', - url: 'https://example.com/article', - snippet: 'Relevant excerpt...', - }, - ], - }, -}); - -// Message.citations auto-populates in @threadplane/chat via extractCitations() +const pending = chat.interrupt(); // runtime-neutral interrupt value +const raw = chat.langGraphInterrupts(); // raw LangGraph Interrupt[] ``` +Resume by calling `chat.submit(response)`. + +### Tool calls + +`toolCalls()` is a Signal of all tool call entries observed in the current run, updated incrementally as the stream progresses. + +### Subagents + +```ts +chat.subagents() // Signal> of all subagents +chat.activeSubagents() // currently streaming subagents (SubagentStreamRef[]) +chat.getSubagent(toolCallId) // look up by tool call ID +chat.getSubagentsByType(type) // filter by subagent type +chat.getSubagentsByMessage(msg) // filter by parent message +``` + +### Queue + +`queue()` exposes pending run entries when the agent is configured with a multitask strategy that queues concurrent submissions. + +### Branch, history, and time-travel + +```ts +chat.branch() // current branch identifier Signal +chat.setBranch(b) // switch to a checkpoint branch +chat.history() // runtime-neutral history entries +chat.langGraphHistory() // raw LangGraph ThreadState[] +chat.experimentalBranchTree() // full branching tree for time-travel UI +``` + +### Actions + +```ts +chat.submit(input, opts?) // send a new message +chat.stop() // cancel the active run +chat.regenerate(assistantMessageIndex) // re-run from a prior assistant turn +chat.reload() // re-run the last submission +chat.switchThread(threadId) // load a different thread +chat.joinStream(runId, lastEventId?) // reconnect to an in-flight run +``` + +### Thread persistence + +`LangGraphThreadsAdapter` is a drop-in, SDK-backed thread store. Provide it alongside the agent config: + +```ts +import { provideAgent, LangGraphThreadsAdapter, LANGGRAPH_THREADS_CONFIG } from '@threadplane/langgraph'; + +export const appConfig: ApplicationConfig = { + providers: [ + provideAgent({ apiUrl: 'https://your-langgraph-platform.com' }), + { provide: LANGGRAPH_THREADS_CONFIG, useValue: { apiUrl: 'https://your-langgraph-platform.com' } }, + LangGraphThreadsAdapter, + ], +}; +``` + +Pair it with the lifecycle helpers to keep your thread list fresh: + +```ts +import { refreshOnRunEnd, refreshOnTransition } from '@threadplane/langgraph'; + +refreshOnRunEnd(chat, () => threadsAdapter.loadThreads()); +``` + +### Citations + +`extractCitations(msg)` reads citation metadata from a LangGraph message's `additional_kwargs`, returning `Citation[] | undefined` (`undefined` when no citation metadata is present). It checks `additional_kwargs.citations` first, falling back to `additional_kwargs.sources`. + +```ts +import { extractCitations } from '@threadplane/langgraph'; + +const citations = extractCitations(message); +``` + +`Citation` is a type from `@threadplane/chat`; `CitationsResolverService` and `provideChat` also live there. + +## Reliability + +**Testing.** `MockAgentTransport` is a deterministic in-memory transport that replays scripted stream events. The rule is: swap the transport, never mock `injectAgent()` itself. `mockLangGraphAgent(options?)` is a convenience factory for unit tests. + +```ts +import { MockAgentTransport, mockLangGraphAgent } from '@threadplane/langgraph'; +``` + +**Runtime-neutral contract.** `LangGraphAgent` implements the `Agent` contract from `@threadplane/chat`. Components that depend only on that contract are portable across adapters (`@threadplane/ag-ui`, future adapters) without modification. + +**Release policy.** Patch-only `0.0.x` releases — every change, including breaking ones, increments the patch version until the library reaches `1.0.0`. + +**CI.** The "Library — lint / test / build" job runs lint, tests, and build on every pull request. + ## Documentation - [Quickstart](https://threadplane.ai/docs/langgraph/getting-started/quickstart) @@ -92,4 +200,4 @@ return new AIMessage({ ## License -MIT — free for any use. See [LICENSE](../../LICENSE). +MIT. See [LICENSE](../../LICENSE). diff --git a/libs/licensing/README.md b/libs/licensing/README.md index a46a2042..772af99c 100644 --- a/libs/licensing/README.md +++ b/libs/licensing/README.md @@ -1,19 +1,106 @@ # @threadplane/licensing -Offline Ed25519 license verification for the Threadplane Angular framework -libraries. +Browser-safe Ed25519 license-token verification and evaluation — the engine behind `@threadplane/chat`'s commercial-license check. -## Status +

+ + npm version + + + MIT + +

-Private, pre-1.0. Consumed by `@threadplane/langgraph`, `@threadplane/render`, and -`@threadplane/chat`. Not intended as a standalone import. +--- -## Behavior +## What it does -- `verifyLicense(token, publicKey)` — pure Ed25519 verification, no I/O. -- `evaluateLicense(result, { nowSec })` — returns one of - `licensed | grace | expired | missing | tampered | noncommercial`. -- `runLicenseCheck(options)` — runs verification and emits a single - `console.warn` with the `[threadplane]` prefix when unlicensed. -- **Never throws from init** — every failure mode is reported via warn, never - by throwing or blocking the host application's startup. +- Verifies compact Ed25519-signed license tokens offline — no network calls, no server round-trips. +- Evaluates claims against a usage context and returns a structured status (`licensed`, `grace`, `expired`, `missing`, `tampered`, `noncommercial`). +- Emits a single `console.warn` nag when a commercial context lacks a valid license; never throws from initialization. + +--- + +## Install + +```bash +npm install @threadplane/licensing +``` + +**Peer dependencies:** + +```bash +npm install @noble/ed25519@^2.2.3 +``` + +--- + +## Usage + +Most consumers do not call this library directly. `@threadplane/chat` calls `runLicenseCheck` internally when you pass `license` to `provideChat({ license: 'your-token' })`. + +If you need to verify or evaluate a token yourself: + +```typescript +import { + verifyLicense, + evaluateLicense, + LICENSE_PUBLIC_KEY, +} from '@threadplane/licensing'; + +// Verify the token's Ed25519 signature (async, no network). +const verifyResult = await verifyLicense(myToken, LICENSE_PUBLIC_KEY); + +// Evaluate the verified result against the current time. +const { status, claims } = evaluateLicense(verifyResult, { + nowSec: Math.floor(Date.now() / 1000), +}); + +// status: 'licensed' | 'grace' | 'expired' | 'missing' | 'tampered' | 'noncommercial' +``` + +For a full orchestrated check (verify + evaluate + nag in one call): + +```typescript +import { runLicenseCheck, LICENSE_PUBLIC_KEY } from '@threadplane/licensing'; + +const status = await runLicenseCheck({ + package: '@threadplane/chat', + token: myToken, // undefined → 'missing' or 'noncommercial' + publicKey: LICENSE_PUBLIC_KEY, +}); +``` + +--- + +## API + +| Symbol | Kind | Description | +|---|---|---| +| `verifyLicense(token, publicKey)` | `async function` | Verify a signed token; returns `VerifyResult`. | +| `evaluateLicense(verifyResult, options)` | `function` | Evaluate claims against time/context; returns `EvaluateResult`. | +| `runLicenseCheck(options)` | `async function` | Orchestrate verify + evaluate + nag; returns `LicenseStatus`. | +| `emitNag(result, options)` | `function` | Emit a one-time `console.warn` for non-licensed statuses. | +| `signLicense(claims, privateKey)` | `async function` | Sign claims into a token (server / tooling only). | +| `inferNoncommercial()` | `function` | Heuristically determine whether the runtime is noncommercial. | +| `LICENSE_PUBLIC_KEY` | `Uint8Array` | Ed25519 public key for verifying Threadplane tokens. | +| `LicenseTier` | `type` | `'developer_seat' \| 'team' \| 'enterprise'` | +| `LicenseClaims`, `VerifyResult`, `VerifyReason` | `type` | Token payload and verification result shapes. | +| `LicenseStatus`, `EvaluateResult`, `EvaluateOptions` | `type` | Evaluation result and options shapes. | +| `EmitNagOptions`, `RunLicenseCheckOptions` | `type` | Options for `emitNag` and `runLicenseCheck`. | + +--- + +## Reliability + +- **Browser-safe** — no Node built-ins (`Buffer`, `process`). Ships into Angular browser bundles without polyfills. +- **Offline** — all verification is done in-process via `@noble/ed25519`; no network calls. +- **Never throws from init** — every failure mode surfaces as a `LicenseStatus` string or a single `console.warn`. +- **Patch-only `0.0.x`** — no breaking version bumps until 1.0. +- CI: Library — lint / test / build. + +--- + +## License + +MIT. See [LICENSE](../../LICENSE). diff --git a/libs/render/README.md b/libs/render/README.md index b9fd6f2e..6d5f2b7b 100644 --- a/libs/render/README.md +++ b/libs/render/README.md @@ -1,8 +1,24 @@ # @threadplane/render -Generative UI for Angular. Agents emit structured JSON specs; this library renders them into Angular components you already own. Supports the Vercel `json-render` and Google A2UI v1-compatible protocols out of the box. +`@json-render/core`-backed Angular render engine — maps JSON specs to Angular components via a registry, used internally by `@threadplane/chat` for generative-UI rendering. -Part of [Threadplane](https://github.com/cacheplane/angular-agent-framework). MIT licensed. +

+ + npm version + + + Angular 20+ + + + MIT + +

+ +## What it does + +- Renders a JSON spec tree to Angular components via a named view registry (``) or a single node (``). +- Registry composition utilities (`views`, `withViews`, `withoutViews`) let you build, extend, and trim registries without mutation. +- Signal-based state store (`signalStateStore`) and per-component fallback support keep UI consistent during streaming. ## Install @@ -10,21 +26,65 @@ Part of [Threadplane](https://github.com/cacheplane/angular-agent-framework). MI npm install @threadplane/render ``` -## What it does +**Peer dependencies:** `@angular/core ^20.0.0 || ^21.0.0`, `@angular/common ^20.0.0 || ^21.0.0`, `@json-render/core ^0.16.0` + +## Quick start + +**1. Define your view registry and provide it.** + +```typescript +// app.config.ts +import { ApplicationConfig } from '@angular/core'; +import { provideRender, provideViews, views, toRenderRegistry } from '@threadplane/render'; +import { CardComponent } from './card.component'; +import { HeroComponent } from './hero.component'; + +const myRegistry = toRenderRegistry( + views({ card: CardComponent, hero: HeroComponent }) +); + +export const appConfig: ApplicationConfig = { + providers: [ + provideRender({ registry: myRegistry }), + ], +}; +``` + +**2. Render a spec in your component.** + +```typescript +import { Component, signal } from '@angular/core'; +import { RenderSpecComponent } from '@threadplane/render'; +import type { Spec } from '@json-render/core'; + +@Component({ + selector: 'app-agent-ui', + imports: [RenderSpecComponent], + template: ``, +}) +export class AgentUiComponent { + spec = signal(null); + + onAgentMessage(incoming: Spec) { + this.spec.set(incoming); + } +} +``` + +## Capabilities + +**View registry composition** — `views(map)` creates a frozen registry; `withViews(base, additions)` extends it non-destructively; `withoutViews(base, ...keys)` prunes entries. Convert to an `AngularRegistry` with `toRenderRegistry` and supply it app-wide via `provideRender({ registry })`, or pass one directly as the `[registry]` input on `` / ``. + +**Signal state store** — `signalStateStore(initialState?)` provides a `StateStore` backed by Angular Signals, suitable for two-way bindings declared in a spec. + +**DI providers** — `provideRender(config)` registers `RenderConfig` (registry, store, functions, handlers) as environment-scoped defaults read by the render components; `provideViews(registry)` publishes a `ViewRegistry` under the `VIEW_REGISTRY` token for consumers to inject directly. -- **Spec-driven rendering** — agents return JSON; you map each node type to one of your Angular components via a registry -- **Two protocols supported** — Vercel `json-render` and Google A2UI v1-compatible -- **Per-component fallback API** — when a spec node has no registered component, you control what renders (and surface it to your observability layer) -- **Readiness gate** — holds renders until the surface is real, so users never see mystery partial UI -- **Streaming partial renders** — works with `@cacheplane/partial-json` to render progressive JSON as it streams +**Fallback** — `DefaultFallbackComponent` renders when no component is registered for a spec node; individual entries in a `ViewRegistry` can supply their own `fallback` component via `RenderViewEntry`. -## Documentation +## Reliability -- [Quickstart](https://threadplane.ai/docs/render/getting-started/quickstart) -- [Component registry](https://threadplane.ai/docs/render/guides/registry) -- [Fallback patterns](https://threadplane.ai/docs/render/guides/fallback) -- [A2UI v1-compatible protocol](https://threadplane.ai/docs/render/a2ui/overview) +Powers `@threadplane/chat` generative-UI rendering in production. Patch-only `0.0.x` releases. Validated by the CI job "Library — lint / test / build" on every commit. ## License -MIT — free for any use. See [LICENSE](../../LICENSE). +MIT. See [LICENSE](../../LICENSE). diff --git a/libs/telemetry/README.md b/libs/telemetry/README.md index e38e2cb2..7e6dd7dc 100644 --- a/libs/telemetry/README.md +++ b/libs/telemetry/README.md @@ -1,143 +1,224 @@ # @threadplane/telemetry -This README is the public trust contract for `@threadplane/*` telemetry. It is linked -from package install notices and should stay aligned with implementation. +Transparent, opt-out anonymous usage telemetry for the Threadplane framework. Isomorphic — a Node path (server adapters, postinstall) and a browser path (Angular DI). This README is the public trust contract; it is linked from package install notices and stays aligned with implementation. -## Imports +

+ + npm version + + + Angular 20+ | 21 + + + MIT + +

-```typescript -// Browser (Angular DI provider) -import { provideThreadplaneTelemetry } from '@threadplane/telemetry/browser'; +## What it does -// Node (server adapters) -import { - captureRuntimeInstanceCreated, - captureRuntimeRequestCreated, - captureStreamStarted, - captureStreamEnded, - captureStreamErrored, - disableTelemetry, -} from '@threadplane/telemetry/node'; +`@threadplane/telemetry` is the single telemetry surface for all `@threadplane/*` packages. It exists so the project can answer "how is Threadplane being used?" without instrumenting browser bundles that ship to end-users. -// Shared utilities (events, env detection, hashing) -import { isTelemetryDisabled, sha256, getAnonId } from '@threadplane/telemetry'; -``` +- **Node telemetry** — on by default, opt-out. Fires on install and on server-adapter lifecycle events. +- **Browser telemetry** — off by default. Never fires unless the consumer explicitly calls `provideThreadplaneTelemetry({ enabled: true })` in their Angular app. +- **Full opt-out** — one env var or one function call silences everything before any network call occurs. + +## What is and is not collected + +### Node events (opt-out, on by default) + +| Event | What is sent | +|-------|-------------| +| `ngaf:postinstall` | Package name, package version, Node version, OS, CPU architecture, package manager name/version, workspace/global install flags when npm exposes them, sample weight. Per-process anonymous id. No project path, no raw environment variables, no dependency tree, no installer IP address. | +| `ngaf:runtime_instance_created` | Which transport, which model provider (string), Angular peer version. No API keys, no endpoint hostnames, no user data. | +| `ngaf:runtime_request_created` | Transport, request type, provider, model. No prompts, thread IDs, assistant IDs, endpoint URLs, or headers. | +| `ngaf:stream_started` | Provider, model name. No prompts, no message content. | +| `ngaf:stream_ended` | Provider, model name, duration. No prompts, no completions, no message content. | +| `ngaf:stream_errored` | Provider, model name, error class. No prompts, no completions, no message content. | -## What this package is +### Browser events (opt-in, off by default) -The single telemetry surface for `@threadplane/*`. It exists so we can answer "how is Threadplane being used?" without instrumenting browser packages that ship to end-users. +Nothing fires unless `provideThreadplaneTelemetry({ enabled: true, ... })` is called in root providers. -## What is and isn't telemetered +| Event | What is sent | +|-------|-------------| +| `ngaf:browser_provided` | Telemetry initialized; surface name, sample weight. Anonymous, no user data. | +| `ngaf:browser_chat_init` | Chat component initialized; surface name, sample weight. Anonymous, no message content. | -**Telemetered by default (Node, opt-out):** -- `ngaf:postinstall` — fires once per dependency/global install of a published `@threadplane/*` package. Properties: package name, package version, Node version, OS, CPU architecture, package manager name/version, installer-reported Node/OS/architecture, workspace/global install flags when npm exposes them, sample weight. It uses a per-process anonymous id. No project path, no raw environment variables, no dependency tree, no installer IP address. -- `ngaf:runtime_instance_created` — server adapters (LangGraph, AG-UI) call this when they spin up. Properties: which transport, which model provider (string), Angular peer version. **No API keys**, no endpoint hostnames, no user data. -- `ngaf:runtime_request_created` — server adapters call this when they create a transport request. Properties: transport, request type, provider, model. No prompts, thread IDs, assistant IDs, endpoint URLs, or headers. -- `ngaf:stream_started` / `ngaf:stream_ended` / `ngaf:stream_errored` — per-request lifecycle on server adapters. Properties: provider, model name, duration, error class. No prompts, no completions, no message content. +Browser-side runtime lifecycle events (`ngaf:runtime_instance_created`, `ngaf:runtime_request_created`, `ngaf:stream_started`, `ngaf:stream_ended`, `ngaf:stream_errored`) may also be sent when the app captures them explicitly. Same payload constraints as Node. -**Telemetered only on explicit opt-in (Browser):** -- Nothing fires unless the consumer calls `provideThreadplaneTelemetry({ enabled: true, sink })` or `provideThreadplaneTelemetry({ enabled: true, endpoint })` in their root providers. -- When opted in: `ngaf:browser_provided`, `ngaf:browser_chat_init`, and browser-side runtime lifecycle events explicitly captured by the app (`ngaf:runtime_instance_created`, `ngaf:runtime_request_created`, `ngaf:stream_started`, `ngaf:stream_ended`, `ngaf:stream_errored`). Anonymous, no message content. +### Never collected — by anyone, at any time -**Never telemetered (by anyone, at any time):** - Message content (user prompts, model completions, tool call inputs/outputs). -- Personally identifiable information beyond `email_domain` on explicit server conversion events on the website. +- Personally identifiable information. - API keys, vendor credentials, project paths, environment variables. +## Install + +```bash +npm install @threadplane/telemetry +``` + +Both peer dependencies are optional: + +``` +@angular/core ^20.0.0 || ^21.0.0 # required only for the ./browser Angular service +posthog-js ^1.372.0 # required only when using PostHog capture +``` + ## Opt-out -Node telemetry is on by default. Three ways to opt out — any one turns it off. +Node telemetry is on by default. Any one of the following turns it off entirely. + +### Environment variables -| Method | How | -|--------|-----| -| Cross-vendor env var | `DO_NOT_TRACK=1` or `DO_NOT_TRACK=true` | -| npm config env var | `npm_config_do_not_track=true` | -| Package env var | `NGAF_TELEMETRY_DISABLED=1` or `NGAF_TELEMETRY_DISABLED=true` | -| Programmatic | `import { disableTelemetry } from '@threadplane/telemetry/node'; disableTelemetry();` before any other `@threadplane/*` import | +| Variable | Value | Notes | +|----------|-------|-------| +| `NGAF_TELEMETRY_DISABLED` | `1` or `true` | Package-level kill-switch | +| `DO_NOT_TRACK` | `1` or `true` | Cross-vendor standard; `npm_config_do_not_track` and `NPM_CONFIG_DO_NOT_TRACK` are also respected | -CI environments (`CI=true`, `GITHUB_ACTIONS=true`, etc.) are auto-detected and treated as opt-out by default. +### CI auto-disable -Local top-level installs are skipped by default. Dependency installs and global installs are eligible unless opted out. +The following CI environment variables are detected automatically — no configuration needed: -The postinstall script prints a single line on stdout only when the install ping was actually accepted by the ingest endpoint. The line is suppressed in CI. +`CI=1`, `GITHUB_ACTIONS=1`, `CONTINUOUS_INTEGRATION=1`, `BUILDKITE=1`, `CIRCLECI=1` -To inspect the install payload locally, run with `DEBUG=ngaf:telemetry`. +Any of these being set (truthy) is treated as an opt-out. -## Opt-in (browser) +### Programmatic opt-out -Browser telemetry is **off by default** and never fires from the library itself. To enable in your Angular app: +Call `disableTelemetry()` before any other `@threadplane/*` import in your Node process: ```ts -// app.config.ts (or wherever you bootstrap) +import { disableTelemetry } from '@threadplane/telemetry/node'; +disableTelemetry(); +``` + +### Sampling + +Default sample rate is **1.0** (100%). Reduce it via: + +```bash +NGAF_TELEMETRY_SAMPLE_RATE=0.1 # sample 10% of events +``` + +Every event carries a `sample_weight` property so de-sampling at query time works correctly. + +### Ingest endpoint override + +Redirect all Node telemetry to your own endpoint: + +```bash +NGAF_TELEMETRY_INGEST_URL=https://telemetry.acme-internal.example.com/api/ingest +``` + +The default ingest (when unset) is a thin proxy at `https://threadplane.ai/api/ingest` that accepts the `@threadplane/telemetry` JSON payload, forwards `ngaf:*` events to the project PostHog instance, and does not forward installer IP addresses. Source lives in `apps/website/src/app/api/ingest/`. + +## Usage + +### Node — server adapters + +```ts +import { + captureRuntimeInstanceCreated, + captureRuntimeRequestCreated, + captureStreamStarted, + captureStreamEnded, + captureStreamErrored, + captureEvent, + capturePostinstall, + disableTelemetry, +} from '@threadplane/telemetry/node'; +``` + +### Browser — Angular DI + +Enable browser telemetry in your Angular root providers: + +```ts +// app.config.ts import { provideThreadplaneTelemetry } from '@threadplane/telemetry/browser'; export const appConfig: ApplicationConfig = { providers: [ - // ... provideThreadplaneTelemetry({ enabled: true, - endpoint: '/api/telemetry', + endpoint: '/api/telemetry', // route through your own backend }), ], }; ``` -The endpoint receives neutral JSON: +You can also pass `sink: async ({ event, properties }) => { ... }` to route events through your own analytics client. Legacy `posthogKey` / `posthogHost` options still work for existing adopters, but new code should prefer `sink` or `endpoint` to keep the API vendor-neutral. + +Inject the service directly when you need to capture events: + +```ts +import { ThreadplaneTelemetryService } from '@threadplane/telemetry/browser'; -```json -{ - "event": "ngaf:stream_started", - "distinctId": "browser:", - "properties": { - "surface": "my_app", - "sample_weight": 1 - } +@Injectable() +export class MyService { + private telemetry = inject(ThreadplaneTelemetryService); } ``` -You can also pass `sink: async ({ event, properties }) => { ... }` and route events through your own analytics client. Legacy `posthogKey` / `posthogHost` options still work for existing adopters, but new app code should prefer `sink` or `endpoint` so the public API is vendor-neutral. - -If you don't call `provideThreadplaneTelemetry({ enabled: true })`, every telemetry helper in `@threadplane/*` browser packages no-ops. No network calls, ever. +If `provideThreadplaneTelemetry({ enabled: true })` is never called, every telemetry helper in `@threadplane/*` browser packages no-ops. No network calls, ever. -## Sampling +### Shared utilities -- Default sample rate: **1.0** (100%) at current scale. -- Configurable via `NGAF_TELEMETRY_SAMPLE_RATE` env var (Node). -- Every event carries a `sample_weight` property so future de-sampling at query time works correctly. +```ts +import { + isTelemetryDisabled, + getDisableReason, + getAnonId, + shouldSample, + sha256, +} from '@threadplane/telemetry'; +``` -## Anonymous id strategy +`getDisableReason()` returns `'DO_NOT_TRACK' | 'NGAF_TELEMETRY_DISABLED' | 'CI' | null`. -- Per-process UUID (`anon_`), regenerated every Node process boot. -- No persistence across restarts. No persistent identifier. -- Browser opt-in endpoint delivery uses an ephemeral per-service-instance id (`browser:`). It is not written to localStorage or cookies. +## Reliability and transparency -## Self-hosting +### Debugging -Enterprise users can redirect Node telemetry to their own ingest: +Inspect payloads locally without sending them: ```bash -NGAF_TELEMETRY_INGEST_URL=https://telemetry.acme-internal.example.com/api/ingest +DEBUG=ngaf:telemetry npm install ``` -Default ingest (when env var is unset) is a thin proxy on the Threadplane website (`https://threadplane.ai/api/ingest`) that accepts the `@threadplane/telemetry` JSON payload and forwards `ngaf:*` events to our PostHog project without forwarding installer IP addresses. Source of the proxy lives in `apps/website/src/app/api/ingest/`. +`ngaf:telemetry` is the debug namespace — it is not an event name and is never sent to the ingest endpoint. + +### Anonymous id strategy + +- Node: per-process UUID (`anon_`), regenerated every process boot. No persistence across restarts. +- Browser: ephemeral per-service-instance id (`browser:`). Not written to localStorage or cookies. -## Verifying telemetry is silent +### Trust test The `@threadplane/telemetry/browser` unit test suite includes a permanent trust test: ``` -test('no network call occurs when provideThreadplaneTelemetry is never called', ...) +test('no posthog-js import when provideThreadplaneTelemetry is never called', ...) ``` If this test ever fails, the trust contract has been violated and the build blocks. -## What's intentionally not in this package +### What is intentionally absent -- Session replay. (Not in Phase 0–1.) +- Session replay. - Cross-session identity stitching. -- Heuristic PII detection. (Redaction is explicit and config-driven only.) -- Default browser writes to anyone's PostHog instance — including ours — without explicit configuration. +- Heuristic PII detection. Redaction is explicit and config-driven only. +- Default browser writes to any PostHog instance — including the project's — without explicit configuration. + +### Reporting a security issue + +If you observe telemetry that contradicts this contract, open an issue at https://github.com/cacheplane/angular-agent-framework/issues tagged `security`. It is treated as P0. + +## Release cadence + +This package follows patch-only releases (`0.0.x`). Even breaking changes increment the patch version at current scale. -## Reporting an issue +## License -If you observe telemetry that you believe contradicts this contract, please open an issue at https://github.com/cacheplane/angular-agent-framework/issues — security-tagged. We treat it as a P0. +MIT. See [LICENSE](../../LICENSE).