The LangGraph JS SDK gives you a streaming client. @cacheplane/angular gives you signal-native state, thread persistence, interrupt flows, and a full test harness — all wired together and optimized for Angular's change detection model. See the full comparison on our product page.
+ The LangGraph JS SDK gives you a streaming client. @cacheplane/langgraph gives you signal-native state, thread persistence, interrupt flows, and a full test harness — all wired together and optimized for Angular's change detection model. See the full comparison on our product page.
-
@cacheplane/angular · Enterprise Guide
+
@cacheplane/langgraph · Enterprise Guide
The
Enterprise
Guide
to
Agent
Streaming
in
Angular
Ship LangGraph agents in Angular — without building the plumbing
cacheplane.io · 2026
@@ -91,7 +91,7 @@
The Real Cost
Chapter 2
The agent() API
Chapter 3: The agent() API
-
The core primitive in `@cacheplane/angular` is `agent()` — a factory function that returns a structured ref containing typed signals wired directly to a LangGraph agent stream. You call it once, bind the signals in your template, and the component reacts to every streamed token without a subscription, a zone trigger, or an accumulation buffer in sight.
+
The core primitive in `@cacheplane/langgraph` is `agent()` — a factory function that returns a structured ref containing typed signals wired directly to a LangGraph agent stream. You call it once, bind the signals in your template, and the component reacts to every streamed token without a subscription, a zone trigger, or an accumulation buffer in sight.
What agent() Returns
`agent()` returns an `AgentRef` object with four signals:
interface AgentRef {
@@ -185,12 +185,12 @@ Production Checklist
Chapter 4
Interrupt & Approval Flows
Chapter 7: Interrupt & Approval Flows
-
Agents that interact with external systems — firing off emails, mutating database records, executing privileged queries — cannot operate as fire-and-forget processes in an enterprise context. Execution must be pauseable, inspectable, and resumable with explicit human intent. LangGraph's `interrupt()` primitive and `@cacheplane/angular`'s reactive surface for it give you exactly that, without polling loops or bespoke WebSocket plumbing.
+
Agents that interact with external systems — firing off emails, mutating database records, executing privileged queries — cannot operate as fire-and-forget processes in an enterprise context. Execution must be pauseable, inspectable, and resumable with explicit human intent. LangGraph's `interrupt()` primitive and `@cacheplane/langgraph`'s reactive surface for it give you exactly that, without polling loops or bespoke WebSocket plumbing.
How LangGraph interrupt() Works
When a LangGraph node calls `interrupt(payload)`, graph execution halts at that checkpoint. The payload is an arbitrary object you define — typically the action description, affected resource identifiers, and any data the reviewer needs to make a decision. The graph persists this state to its checkpointer and waits. Nothing proceeds until a resume command arrives with the correct thread ID and checkpoint reference.
The resume payload follows a typed contract: `{ action: "approve" | "edit" | "cancel", data?: unknown }`. On `approve`, the graph continues with the original node inputs. On `edit`, your modified `data` is injected, replacing what the node would have used. On `cancel`, LangGraph short-circuits the remaining path and routes to whatever terminal state you've configured for rejected flows.
The Interrupt Signal in Angular
-
`@cacheplane/angular` exposes this checkpoint state directly on the agent reference. The `interrupt` property is a computed signal that starts as `null` and transitions to an `InterruptState` object the moment the backend checkpoint is written:
+
`@cacheplane/langgraph` exposes this checkpoint state directly on the agent reference. The `interrupt` property is a computed signal that starts as `null` and transitions to an `InterruptState` object the moment the backend checkpoint is written:
const agent = injectAgentRef({ threadId });
const interrupt = agent.interrupt; // Signal
const handleApprove = () =>
@@ -221,16 +221,16 @@
Edge Cases Worth Handling
Chapter 5
Full LangGraph Feature Coverage
Chapter 4: Full LangGraph Feature Coverage
-
Basic chat streaming is a solved problem. The real engineering challenge begins when your graph does something more interesting—calls a tool, delegates to a subagent, or needs to rewind to a prior checkpoint. Most Angular LLM libraries handle the simple case and then quietly stop working. This chapter documents how `@cacheplane/angular` exposes the full LangGraph feature surface through a coherent signal-based API.
+
Basic chat streaming is a solved problem. The real engineering challenge begins when your graph does something more interesting—calls a tool, delegates to a subagent, or needs to rewind to a prior checkpoint. Most Angular LLM libraries handle the simple case and then quietly stop working. This chapter documents how `@cacheplane/langgraph` exposes the full LangGraph feature surface through a coherent signal-based API.
---
Tool Call Streaming
-
When a LangGraph node invokes a tool, the graph emits a structured sequence of events: the tool call intent, intermediate streaming deltas, and the final tool result. Rather than requiring you to parse raw SSE frames and reconstruct this sequence manually, `@cacheplane/angular` surfaces tool invocation lifecycle through the agent ref directly.
+
When a LangGraph node invokes a tool, the graph emits a structured sequence of events: the tool call intent, intermediate streaming deltas, and the final tool result. Rather than requiring you to parse raw SSE frames and reconstruct this sequence manually, `@cacheplane/langgraph` surfaces tool invocation lifecycle through the agent ref directly.
The `toolCalls` signal on the agent ref updates reactively as each tool call progresses. You get the tool name, input arguments as they stream in, execution status, and the final output—all typed, all reactive. Your component never needs to touch the underlying event stream to render a "Searching the web…" indicator or display structured tool output inline.
This matters because tool call parsing is subtle. Argument deltas arrive as partial JSON strings. Parallel tool calls interleave. A naive implementation collapses these into noise. The library handles reconstruction and ordering internally.
---
Subgraph Event Propagation
LangGraph supports nested graphs, and real production agents use them. Events emitted inside a subgraph are namespaced by graph path, which means consuming them correctly requires understanding the event hierarchy.
-
`@cacheplane/angular` flattens subgraph events into the parent stream while preserving their origin metadata. If you care about which subgraph produced a message—for routing UI panels, for logging, or for debugging—you have access to the full event path. If you don't care, the default signal behavior aggregates everything cleanly without requiring you to configure traversal logic.
+
`@cacheplane/langgraph` flattens subgraph events into the parent stream while preserving their origin metadata. If you care about which subgraph produced a message—for routing UI panels, for logging, or for debugging—you have access to the full event path. If you don't care, the default signal behavior aggregates everything cleanly without requiring you to configure traversal logic.
---
Time Travel
LangGraph's checkpoint system allows you to rewind graph state to any prior node execution and re-run from that point. This is not a niche feature—it's the foundation of any agent UI that allows users to correct mistakes or explore alternative paths.
@@ -238,7 +238,7 @@
Time Travel
---
DeepAgent Multi-Agent Coordination
DeepAgent introduces orchestrator-subagent topology at the stream level. An orchestrator dispatches work to specialized agents and aggregates their outputs. From the stream consumer's perspective, this produces interleaved events from multiple graph instances.
-
`@cacheplane/angular` maps each active agent to its own scoped signal context. The orchestrator's agent ref exposes a `subagents` signal—a reactive map of active agent IDs to their respective state signals. You can bind a list of subagent components to this map and let Angular's `@for` loop handle the rest. Agent addition, completion, and failure all propagate as signal updates without manual subscription management.
+
`@cacheplane/langgraph` maps each active agent to its own scoped signal context. The orchestrator's agent ref exposes a `subagents` signal—a reactive map of active agent IDs to their respective state signals. You can bind a list of subagent components to this map and let Angular's `@for` loop handle the rest. Agent addition, completion, and failure all propagate as signal updates without manual subscription management.
---
The `onCustomEvent` Hook
LangGraph nodes can emit arbitrary structured events outside the message channel. These are the primitives for generative UI, analytics instrumentation, and intermediate state reporting.
diff --git a/apps/website/public/whitepapers/chat-preview.html b/apps/website/public/whitepapers/chat-preview.html
index 5f364794d..b3e27b960 100644
--- a/apps/website/public/whitepapers/chat-preview.html
+++ b/apps/website/public/whitepapers/chat-preview.html
@@ -102,7 +102,7 @@
The Headless Tier
---
The Prebuilt Tier
`chat-prebuilt` is a single component that composes all four headless primitives internally, applies a production-quality default theme, and handles responsive layout. Zero configuration is required beyond connecting an agent reference.
-
import { AgentService } from '@cacheplane/angular';
+import { AgentService } from '@cacheplane/langgraph';
import { ChatPrebuiltComponent } from '@cacheplane/chat';
@Component({
selector: 'app-support',
@@ -122,7 +122,7 @@
The Prebuilt Tier
That is a fully functional streaming chat interface. Message history, input handling, tool call display, and interrupt controls are all included.
---
How the Component Model Connects
-Both tiers consume the `AgentRef` produced by `@cacheplane/angular`. The `AgentRef` is a stable reference that exposes an `AIMessage[]` signal, a send method, and a cancellation interface. Components bind to this ref and react to signal emissions via Angular's standard reactivity primitives — no custom change detection strategies required, no zone workarounds.
+Both tiers consume the `AgentRef` produced by `@cacheplane/langgraph`. The `AgentRef` is a stable reference that exposes an `AIMessage[]` signal, a send method, and a cancellation interface. Components bind to this ref and react to signal emissions via Angular's standard reactivity primitives — no custom change detection strategies required, no zone workarounds.
When the agent emits a new token mid-stream, the `AIMessage[]` signal updates, and `chat-messages` incrementally patches the DOM. The mechanism is straightforward signal subscription. There is no proprietary diffing layer to reason about.
---
Composing the Two Tiers
diff --git a/apps/website/scripts/generate-whitepaper.ts b/apps/website/scripts/generate-whitepaper.ts
index 6046293be..c03f65bce 100644
--- a/apps/website/scripts/generate-whitepaper.ts
+++ b/apps/website/scripts/generate-whitepaper.ts
@@ -150,7 +150,7 @@ Tone: Direct, technical, peer-to-peer. No fluff. Audience is senior Angular engi
id: 'angular',
title: 'The Enterprise Guide to Agent Streaming in Angular',
subtitle: 'Ship LangGraph agents in Angular — without building the plumbing',
- eyebrow: '@cacheplane/angular · Enterprise Guide',
+ eyebrow: '@cacheplane/langgraph · Enterprise Guide',
coverGradient: 'linear-gradient(135deg, #eaf3ff 0%, #e6f4ff 45%, #f4f0ff 70%, #fef0f3 100%)',
outputPdf: 'apps/website/public/whitepapers/angular.pdf',
outputHtml: 'apps/website/public/whitepapers/angular-preview.html',
@@ -181,7 +181,7 @@ Tone: Direct, technical, peer-to-peer. No fluff. Audience is senior Angular engi
Chapter topic: The agent() API
-Context: @cacheplane/angular exposes a signal-native API for streaming LangGraph agents into Angular components. The core primitive is agent() — a function that returns reactive signals wired directly to the agent stream, with no manual subscription management, no zone-patching, and no token accumulation logic.
+Context: @cacheplane/langgraph exposes a signal-native API for streaming LangGraph agents into Angular components. The core primitive is agent() — a function that returns reactive signals wired directly to the agent stream, with no manual subscription management, no zone-patching, and no token accumulation logic.
Cover:
- How agent() returns a structured ref with typed signals: messages(), isStreaming(), error(), interrupt()
@@ -220,7 +220,7 @@ Tone: Direct, technical, peer-to-peer. No fluff. Audience is senior Angular engi
Chapter topic: Interrupt & Approval Flows
-Context: Agents that take real-world actions — sending emails, executing queries, modifying records — must pause for human confirmation. LangGraph's interrupt() primitive enables this on the backend. @cacheplane/angular surfaces it as a reactive signal, eliminating the need for polling, websockets, or custom resume endpoints.
+Context: Agents that take real-world actions — sending emails, executing queries, modifying records — must pause for human confirmation. LangGraph's interrupt() primitive enables this on the backend. @cacheplane/langgraph surfaces it as a reactive signal, eliminating the need for polling, websockets, or custom resume endpoints.
Cover:
- How LangGraph interrupt() pauses graph execution and what the resume payload looks like
@@ -239,7 +239,7 @@ Tone: Direct, technical, peer-to-peer. No fluff. Audience is senior Angular engi
Chapter topic: Full LangGraph Feature Coverage
-Context: Most Angular LLM integrations support basic chat. @cacheplane/angular is designed for the full LangGraph feature surface: tool calls, subgraphs, time travel, and DeepAgent multi-agent coordination. Teams shouldn't have to drop down to raw SSE parsing to access advanced graph features.
+Context: Most Angular LLM integrations support basic chat. @cacheplane/langgraph is designed for the full LangGraph feature surface: tool calls, subgraphs, time travel, and DeepAgent multi-agent coordination. Teams shouldn't have to drop down to raw SSE parsing to access advanced graph features.
Cover:
- Tool call streaming: how tool invocation events surface through the agent ref without manual parsing
@@ -424,7 +424,7 @@ Cover:
- The headless tier: chat-messages, chat-input, chat-tool-calls, chat-interrupt — behavior without styling
- The prebuilt tier: chat-prebuilt — a full chat interface in one component, zero configuration
- How the two tiers compose: using prebuilt for 90% of UI, dropping to headless for custom sections
-- The component model: how @cacheplane/chat connects to the agent ref from @cacheplane/angular
+- The component model: how @cacheplane/chat connects to the agent ref from @cacheplane/langgraph
- Message rendering: how AIMessage[] from the agent signal maps to chat message display
- Code example: with an agent ref (6-10 lines)
- When to use headless vs. prebuilt and how to migrate between them
diff --git a/apps/website/src/app/angular/page.tsx b/apps/website/src/app/angular/page.tsx
index db32099e4..be1819f2c 100644
--- a/apps/website/src/app/angular/page.tsx
+++ b/apps/website/src/app/angular/page.tsx
@@ -10,7 +10,7 @@ import { AngularFooterCTA } from '../../components/landing/angular/AngularFooter
import { tokens } from '@cacheplane/design-tokens';
export const metadata = {
- title: '@cacheplane/angular — Agent Streaming for Angular',
+ title: '@cacheplane/langgraph — Agent Streaming for Angular',
description: 'Ship LangGraph agents in Angular. Signal-native streaming, thread persistence, interrupts, and deterministic testing.',
};
diff --git a/apps/website/src/app/llms-full.txt/route.ts b/apps/website/src/app/llms-full.txt/route.ts
index d0f2672fe..86548098f 100644
--- a/apps/website/src/app/llms-full.txt/route.ts
+++ b/apps/website/src/app/llms-full.txt/route.ts
@@ -41,7 +41,7 @@ export async function GET() {
[
'## MCP server',
'',
- 'npx @cacheplane/angular-mcp',
+ 'npx @cacheplane/langgraph-mcp',
'Add to Claude Code settings.json, Cursor .cursor/mcp.json, or any MCP-compatible agent.',
].join('\n'),
];
diff --git a/apps/website/src/app/llms.txt/route.ts b/apps/website/src/app/llms.txt/route.ts
index 6068ea8ef..9e61835e5 100644
--- a/apps/website/src/app/llms.txt/route.ts
+++ b/apps/website/src/app/llms.txt/route.ts
@@ -10,7 +10,7 @@ function buildLlmsTxt(): string {
"Angular Agent Framework — the enterprise streaming library for LangChain/LangGraph. Provides agent() — full parity with React's useStream() hook, built on Angular Signals.",
'',
'## Install',
- 'npm install @cacheplane/angular',
+ 'npm install @cacheplane/langgraph',
'',
'## Key API',
'- agent(options): AgentRef — call in Angular injection context (constructor or field initializer)',
@@ -22,13 +22,13 @@ function buildLlmsTxt(): string {
'- MockAgentTransport — deterministic unit testing without a real server',
'',
'## Minimal example',
- "import { agent } from '@cacheplane/angular';",
+ "import { agent } from '@cacheplane/langgraph';",
"const chat = agent({ assistantId: 'chat_agent', apiUrl: 'http://localhost:2024' });",
'// Template: @for (msg of chat.messages(); track $index) { {{ msg.content }}
}',
"// Submit: chat.submit({ messages: [{ role: 'human', content: input }] })",
'',
'## MCP server',
- 'npx @cacheplane/angular-mcp',
+ 'npx @cacheplane/langgraph-mcp',
'',
'## Full reference',
'https://cacheplane.ai/llms-full.txt',
diff --git a/apps/website/src/components/landing/HeroTwoCol.tsx b/apps/website/src/components/landing/HeroTwoCol.tsx
index 040f9f8a4..8bc8afa90 100644
--- a/apps/website/src/components/landing/HeroTwoCol.tsx
+++ b/apps/website/src/components/landing/HeroTwoCol.tsx
@@ -2,7 +2,7 @@ import { GenerativeUIFrame } from './GenerativeUIFrame';
import { CopyPromptButton } from '../docs/CopyPromptButton';
import { tokens } from '@cacheplane/design-tokens';
-const SETUP_SNIPPET = 'npm install @cacheplane/angular\n\n// app.config.ts\nprovideAgent({ apiUrl: \'http://localhost:2024\' })';
+const SETUP_SNIPPET = 'npm install @cacheplane/langgraph\n\n// app.config.ts\nprovideAgent({ apiUrl: \'http://localhost:2024\' })';
function LangChainBadge() {
return (
@@ -110,7 +110,7 @@ export async function HeroTwoCol() {
fontSize: 12,
color: tokens.colors.textMuted,
}}>
- npm install @cacheplane/angular
+ npm install @cacheplane/langgraph
diff --git a/apps/website/src/components/landing/LibrariesSection.tsx b/apps/website/src/components/landing/LibrariesSection.tsx
index 9da062bc2..d86df0d60 100644
--- a/apps/website/src/components/landing/LibrariesSection.tsx
+++ b/apps/website/src/components/landing/LibrariesSection.tsx
@@ -7,7 +7,7 @@ const LIBRARIES = [
{
id: 'angular',
tag: 'Agent',
- pkg: '@cacheplane/angular',
+ pkg: '@cacheplane/langgraph',
color: tokens.colors.accent,
rgb: '0,64,144',
oneLiner: 'Signal-native streaming for LangGraph agents',
diff --git a/apps/website/src/components/landing/TheStack.tsx b/apps/website/src/components/landing/TheStack.tsx
index 38f06ef1c..1d0c2d4c5 100644
--- a/apps/website/src/components/landing/TheStack.tsx
+++ b/apps/website/src/components/landing/TheStack.tsx
@@ -7,7 +7,7 @@ const LIBRARIES = [
{
id: 'angular',
tag: 'Agent',
- pkg: '@cacheplane/angular',
+ pkg: '@cacheplane/langgraph',
color: tokens.colors.accent,
rgb: '0,64,144',
headline: 'The reactive bridge to LangGraph',
diff --git a/apps/website/src/components/landing/angular/AngularCodeShowcase.tsx b/apps/website/src/components/landing/angular/AngularCodeShowcase.tsx
index b1d871c95..ef0a5098a 100644
--- a/apps/website/src/components/landing/angular/AngularCodeShowcase.tsx
+++ b/apps/website/src/components/landing/angular/AngularCodeShowcase.tsx
@@ -1,7 +1,7 @@
import { tokens } from '@cacheplane/design-tokens';
import { HighlightedCode } from '../HighlightedCode';
-const SNIPPET_1 = `import { agent } from '@cacheplane/angular';
+const SNIPPET_1 = `import { agent } from '@cacheplane/langgraph';
const chat = agent({
graphId: 'my-agent',
@@ -13,7 +13,7 @@ chat.messages(); // Signal
chat.isStreaming(); // Signal
chat.interrupt(); // Signal`;
-const SNIPPET_2 = `import { provideAgent } from '@cacheplane/angular';
+const SNIPPET_2 = `import { provideAgent } from '@cacheplane/langgraph';
provideAgent({
graphId: 'my-agent',
diff --git a/apps/website/src/components/landing/angular/AngularComparison.tsx b/apps/website/src/components/landing/angular/AngularComparison.tsx
index 0b8fb7cf5..ccbfb8978 100644
--- a/apps/website/src/components/landing/angular/AngularComparison.tsx
+++ b/apps/website/src/components/landing/angular/AngularComparison.tsx
@@ -42,7 +42,7 @@ export function AngularComparison() {
fontSize: 'clamp(26px,3.5vw,42px)', fontWeight: 800, lineHeight: 1.1,
color: tokens.colors.textPrimary,
}}>
- @langchain/langgraph-sdk vs @cacheplane/angular
+ @langchain/langgraph-sdk vs @cacheplane/langgraph
@@ -62,7 +62,7 @@ export function AngularComparison() {
display: 'grid', gridTemplateColumns: 'minmax(100px, 1fr) minmax(120px, 1fr) minmax(120px, 1fr)',
background: 'rgba(255,255,255,.3)', borderBottom: `1px solid ${tokens.glass.border}`, padding: '14px 24px',
}}>
- {['Capability', '@langchain/langgraph-sdk', '@cacheplane/angular'].map((h, i) => (
+ {['Capability', '@langchain/langgraph-sdk', '@cacheplane/langgraph'].map((h, i) => (
- @cacheplane/angular
+ @cacheplane/langgraph
diff --git a/apps/website/src/components/landing/angular/AngularProblemSolution.tsx b/apps/website/src/components/landing/angular/AngularProblemSolution.tsx
index 5b0038a4a..9618c7346 100644
--- a/apps/website/src/components/landing/angular/AngularProblemSolution.tsx
+++ b/apps/website/src/components/landing/angular/AngularProblemSolution.tsx
@@ -69,7 +69,7 @@ export function AngularProblemSolution() {
fontSize: '0.58rem', fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.07em',
padding: '2px 9px', borderRadius: 5, color: '#fff', background: '#1a7a40', marginBottom: 16,
}}>
- With @cacheplane/angular
+ With @cacheplane/langgraph
@@ -1216,7 +1216,7 @@ const PLANS = [
features: ['PolyForm Noncommercial 1.0.0', 'Personal projects', 'Academic & research', 'Non-profit internal use'],
highlight: false,
cta: 'Get Started',
- ctaHref: 'https://www.npmjs.com/package/@cacheplane/angular',
+ ctaHref: 'https://www.npmjs.com/package/@cacheplane/langgraph',
},
{
name: 'Developer Seat',
diff --git a/docs/superpowers/plans/2026-04-03-langsmith-angular-runtime.md b/docs/superpowers/plans/2026-04-03-langsmith-angular-runtime.md
index 48d47f2b6..804394f8d 100644
--- a/docs/superpowers/plans/2026-04-03-langsmith-angular-runtime.md
+++ b/docs/superpowers/plans/2026-04-03-langsmith-angular-runtime.md
@@ -2,11 +2,11 @@
> **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 Angular streaming example to use `agent()` from `@cacheplane/angular`, add environment config for LangGraph Cloud, and create a CI workflow that deploys the LangGraph backend on merge to main (with manual dispatch for testing).
+**Goal:** Rewrite the Angular streaming example to use `agent()` from `@cacheplane/langgraph`, add environment config for LangGraph Cloud, and create a CI workflow that deploys the LangGraph backend on merge to main (with manual dispatch for testing).
**Architecture:** Delete the hand-rolled `StreamingService`, replace with `agent()` Signal-based API. Add Angular environment files for dev/prod LangGraph URLs. Create a GitHub Action that runs `langgraph deploy` from the capability's python directory. Update `app.config.ts` to use `provideAgent()`.
-**Tech Stack:** Angular 19+ (standalone), `@cacheplane/angular`, `@langchain/langgraph-sdk`, `@langchain/core`, GitHub Actions, `langgraph-cli`
+**Tech Stack:** Angular 19+ (standalone), `@cacheplane/langgraph`, `@langchain/langgraph-sdk`, `@langchain/core`, GitHub Actions, `langgraph-cli`
---
@@ -96,7 +96,7 @@ git commit -m "refactor(cockpit): remove StreamingService, add environment confi
"version": "0.0.1",
"private": true,
"dependencies": {
- "@cacheplane/angular": "^0.0.1",
+ "@cacheplane/langgraph": "^0.0.1",
"@langchain/core": "^0.3.0",
"@langchain/langgraph-sdk": "^0.0.36"
}
@@ -126,7 +126,7 @@ git commit -m "chore(cockpit): add angular and LangGraph SDK deps to Angular exa
```ts
// cockpit/langgraph/streaming/angular/src/app/app.config.ts
import { ApplicationConfig } from '@angular/core';
-import { provideAgent } from '@cacheplane/angular';
+import { provideAgent } from '@cacheplane/langgraph';
import { environment } from '../environments/environment';
/**
@@ -165,7 +165,7 @@ git commit -m "feat(cockpit): configure provideAgent in Angular app"
// cockpit/langgraph/streaming/angular/src/app/streaming.component.ts
import { Component, computed, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
-import { agent, ResourceStatus } from '@cacheplane/angular';
+import { agent, ResourceStatus } from '@cacheplane/langgraph';
import type { BaseMessage } from '@langchain/core/messages';
import { environment } from '../environments/environment';
diff --git a/docs/superpowers/plans/2026-04-03-narrative-docs.md b/docs/superpowers/plans/2026-04-03-narrative-docs.md
index a8d4ca344..f40b31b82 100644
--- a/docs/superpowers/plans/2026-04-03-narrative-docs.md
+++ b/docs/superpowers/plans/2026-04-03-narrative-docs.md
@@ -425,7 +425,7 @@ git commit -m "feat(cockpit): add four-mode shell with Docs and API modes"
# Streaming with angular
This guide walks through building a real-time streaming chat interface using
-`agent()` from `@cacheplane/angular` connected to a LangGraph
+`agent()` from `@cacheplane/langgraph` connected to a LangGraph
backend on LangSmith Cloud.
## What you'll build
@@ -439,7 +439,7 @@ visual feedback instead of waiting for a complete response.
Set up `provideAgent()` in your app config with the LangGraph Cloud URL:
```typescript
-import { provideAgent } from '@cacheplane/angular';
+import { provideAgent } from '@cacheplane/langgraph';
export const appConfig: ApplicationConfig = {
providers: [
@@ -457,7 +457,7 @@ This makes the API URL available to all `agent()` calls in your app.
In your component, call `agent()` in a field initializer (injection context required):
```typescript
-import { agent } from '@cacheplane/angular';
+import { agent } from '@cacheplane/langgraph';
export class StreamingComponent {
protected readonly stream = agent({
diff --git a/docs/superpowers/plans/2026-04-04-cacheplane-chat.md b/docs/superpowers/plans/2026-04-04-cacheplane-chat.md
index c8ed0625a..f124e9b60 100644
--- a/docs/superpowers/plans/2026-04-04-cacheplane-chat.md
+++ b/docs/superpowers/plans/2026-04-04-cacheplane-chat.md
@@ -4,9 +4,9 @@
**Goal:** Build an Angular chat component library with headless primitives and prebuilt Tailwind compositions for LangGraph, LangChain, and Deep Agent UIs.
-**Architecture:** Two-layer design — headless primitives (unstyled, logic-only, composable via `ng-template`) and prebuilt compositions (Tailwind + shadcn model). All components accept a `AgentRef` from `@cacheplane/angular`. Generative UI hosted via `@cacheplane/render`. Debug component provides agent execution inspection.
+**Architecture:** Two-layer design — headless primitives (unstyled, logic-only, composable via `ng-template`) and prebuilt compositions (Tailwind + shadcn model). All components accept a `AgentRef` from `@cacheplane/langgraph`. Generative UI hosted via `@cacheplane/render`. Debug component provides agent execution inspection.
-**Tech Stack:** Angular 21+, `@cacheplane/angular`, `@cacheplane/render`, Tailwind CSS, Nx 22, ng-packagr, Vitest
+**Tech Stack:** Angular 21+, `@cacheplane/langgraph`, `@cacheplane/render`, Tailwind CSS, Nx 22, ng-packagr, Vitest
**Spec:** `docs/superpowers/specs/2026-04-04-chat-component-library-design.md` — Deliverable 2
@@ -122,7 +122,7 @@ npx nx generate @nx/angular:library chat --directory=libs/chat --publishable --i
"@angular/core": "^20.0.0 || ^21.0.0",
"@angular/common": "^20.0.0 || ^21.0.0",
"@cacheplane/render": "^0.0.1",
- "@cacheplane/angular": "^0.0.1",
+ "@cacheplane/langgraph": "^0.0.1",
"@langchain/core": "^1.1.33"
},
"license": "PolyForm-Noncommercial-1.0.0",
@@ -203,7 +203,7 @@ Create `libs/chat/src/lib/chat.types.ts`:
```typescript
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
import type { Signal } from '@angular/core';
-import type { AgentRef } from '@cacheplane/angular';
+import type { AgentRef } from '@cacheplane/langgraph';
import type { AngularRegistry } from '@cacheplane/render';
import type { BaseMessage } from '@langchain/core/messages';
@@ -234,8 +234,8 @@ Create `libs/chat/src/lib/testing/mock-angular-ref.ts`:
```typescript
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
import { signal, computed } from '@angular/core';
-import type { AgentRef } from '@cacheplane/angular';
-import { ResourceStatus } from '@cacheplane/angular';
+import type { AgentRef } from '@cacheplane/langgraph';
+import { ResourceStatus } from '@cacheplane/langgraph';
import type { BaseMessage } from '@langchain/core/messages';
import { HumanMessage, AIMessage } from '@langchain/core/messages';
@@ -411,7 +411,7 @@ import {
ChangeDetectionStrategy,
} from '@angular/core';
import { NgTemplateOutlet } from '@angular/common';
-import type { AgentRef } from '@cacheplane/angular';
+import type { AgentRef } from '@cacheplane/langgraph';
import type { BaseMessage } from '@langchain/core/messages';
import { MessageTemplateDirective } from './message-template.directive';
import type { MessageTemplateType } from '../../chat.types';
@@ -558,7 +558,7 @@ import {
ChangeDetectionStrategy,
} from '@angular/core';
import { FormsModule } from '@angular/forms';
-import type { AgentRef } from '@cacheplane/angular';
+import type { AgentRef } from '@cacheplane/langgraph';
import { HumanMessage } from '@langchain/core/messages';
@Component({
@@ -650,7 +650,7 @@ import { describe, it, expect } from 'vitest';
import { TestBed } from '@angular/core/testing';
import { ChatTypingIndicatorComponent } from './chat-typing-indicator.component';
import { createMockAgentRef } from '../../testing/mock-angular-ref';
-import { ResourceStatus } from '@cacheplane/angular';
+import { ResourceStatus } from '@cacheplane/langgraph';
describe('ChatTypingIndicatorComponent', () => {
it('should render when loading', () => {
@@ -714,7 +714,7 @@ Create `libs/chat/src/lib/primitives/chat-typing-indicator/chat-typing-indicator
```typescript
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
import { Component, computed, input, ChangeDetectionStrategy } from '@angular/core';
-import type { AgentRef } from '@cacheplane/angular';
+import type { AgentRef } from '@cacheplane/langgraph';
@Component({
selector: 'chat-typing-indicator',
@@ -740,7 +740,7 @@ Create `libs/chat/src/lib/primitives/chat-error/chat-error.component.ts`:
```typescript
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
import { Component, computed, input, ChangeDetectionStrategy } from '@angular/core';
-import type { AgentRef } from '@cacheplane/angular';
+import type { AgentRef } from '@cacheplane/langgraph';
@Component({
selector: 'chat-error',
@@ -855,7 +855,7 @@ import {
ChangeDetectionStrategy,
} from '@angular/core';
import { NgTemplateOutlet } from '@angular/common';
-import type { AgentRef } from '@cacheplane/angular';
+import type { AgentRef } from '@cacheplane/langgraph';
@Component({
selector: 'chat-interrupt',
@@ -975,7 +975,7 @@ import {
ChangeDetectionStrategy,
} from '@angular/core';
import { NgTemplateOutlet } from '@angular/common';
-import type { AgentRef } from '@cacheplane/angular';
+import type { AgentRef } from '@cacheplane/langgraph';
import type { BaseMessage } from '@langchain/core/messages';
@Component({
@@ -1023,7 +1023,7 @@ import {
ChangeDetectionStrategy,
} from '@angular/core';
import { NgTemplateOutlet } from '@angular/common';
-import type { AgentRef } from '@cacheplane/angular';
+import type { AgentRef } from '@cacheplane/langgraph';
@Component({
selector: 'chat-subagents',
@@ -1243,7 +1243,7 @@ import {
ChangeDetectionStrategy,
} from '@angular/core';
import { NgTemplateOutlet } from '@angular/common';
-import type { AgentRef } from '@cacheplane/angular';
+import type { AgentRef } from '@cacheplane/langgraph';
@Component({
selector: 'chat-timeline',
@@ -1443,7 +1443,7 @@ Create `libs/chat/src/lib/compositions/chat/chat.component.ts`:
```typescript
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
import { Component, input, ChangeDetectionStrategy } from '@angular/core';
-import type { AgentRef } from '@cacheplane/angular';
+import type { AgentRef } from '@cacheplane/langgraph';
import { ChatMessagesComponent } from '../../primitives/chat-messages/chat-messages.component';
import { MessageTemplateDirective } from '../../primitives/chat-messages/message-template.directive';
import { ChatInputComponent } from '../../primitives/chat-input/chat-input.component';
@@ -1539,7 +1539,7 @@ Create `libs/chat/src/lib/compositions/chat-interrupt-panel/chat-interrupt-panel
```typescript
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
import { Component, input, output, ChangeDetectionStrategy } from '@angular/core';
-import type { AgentRef } from '@cacheplane/angular';
+import type { AgentRef } from '@cacheplane/langgraph';
export type InterruptAction = 'accept' | 'edit' | 'respond' | 'ignore';
@@ -1638,7 +1638,7 @@ Create `libs/chat/src/lib/compositions/chat-subagent-card/chat-subagent-card.com
```typescript
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
import { Component, computed, input, signal, ChangeDetectionStrategy } from '@angular/core';
-import type { SubagentStreamRef } from '@cacheplane/angular';
+import type { SubagentStreamRef } from '@cacheplane/langgraph';
@Component({
selector: 'chat-subagent-card',
@@ -1952,7 +1952,7 @@ Create `libs/chat/src/lib/compositions/chat-debug/chat-debug.component.ts`:
```typescript
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
import { Component, computed, input, signal, ChangeDetectionStrategy } from '@angular/core';
-import type { AgentRef } from '@cacheplane/angular';
+import type { AgentRef } from '@cacheplane/langgraph';
import { ChatMessagesComponent } from '../../primitives/chat-messages/chat-messages.component';
import { MessageTemplateDirective } from '../../primitives/chat-messages/message-template.directive';
import { ChatInputComponent } from '../../primitives/chat-input/chat-input.component';
diff --git a/docs/superpowers/plans/2026-04-04-cockpit-chat-integration.md b/docs/superpowers/plans/2026-04-04-cockpit-chat-integration.md
index e1bc1a3de..befa42dc9 100644
--- a/docs/superpowers/plans/2026-04-04-cockpit-chat-integration.md
+++ b/docs/superpowers/plans/2026-04-04-cockpit-chat-integration.md
@@ -4,9 +4,9 @@
**Goal:** Update cockpit capability examples to consume `@cacheplane/chat` components, validating the library's API against real LangGraph and Deep Agent use cases.
-**Architecture:** Each capability example is a standalone Angular app with its own backend and LangSmith deployment. Examples import `@cacheplane/chat` and `@cacheplane/angular`. The cockpit (React/Next.js) embeds them via the existing embed strategy.
+**Architecture:** Each capability example is a standalone Angular app with its own backend and LangSmith deployment. Examples import `@cacheplane/chat` and `@cacheplane/langgraph`. The cockpit (React/Next.js) embeds them via the existing embed strategy.
-**Tech Stack:** Angular 21+, `@cacheplane/chat`, `@cacheplane/angular`, Nx 22
+**Tech Stack:** Angular 21+, `@cacheplane/chat`, `@cacheplane/langgraph`, Nx 22
**Spec:** `docs/superpowers/specs/2026-04-04-chat-component-library-design.md` — Deliverable 3
@@ -64,7 +64,7 @@ This is the reference example — all others follow this pattern.
```typescript
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
import { ApplicationConfig } from '@angular/core';
-import { provideAgent } from '@cacheplane/angular';
+import { provideAgent } from '@cacheplane/langgraph';
import { provideChat } from '@cacheplane/chat';
export const appConfig: ApplicationConfig = {
@@ -83,7 +83,7 @@ export const appConfig: ApplicationConfig = {
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
import { Component, inject, Injector, OnInit } from '@angular/core';
import { runInInjectionContext } from '@angular/core';
-import { agent } from '@cacheplane/angular';
+import { agent } from '@cacheplane/langgraph';
import { ChatComponent } from '@cacheplane/chat';
import type { BaseMessage } from '@langchain/core/messages';
@@ -113,7 +113,7 @@ export class AppComponent implements OnInit {
- [ ] **Step 3: Create main.ts and package.json**
-Standard Angular bootstrap + package.json with peer deps on `@cacheplane/chat` and `@cacheplane/angular`.
+Standard Angular bootstrap + package.json with peer deps on `@cacheplane/chat` and `@cacheplane/langgraph`.
- [ ] **Step 4: Verify example builds**
diff --git a/docs/superpowers/plans/2026-04-04-deep-agents-examples.md b/docs/superpowers/plans/2026-04-04-deep-agents-examples.md
index b6a2bc838..aa942d72a 100644
--- a/docs/superpowers/plans/2026-04-04-deep-agents-examples.md
+++ b/docs/superpowers/plans/2026-04-04-deep-agents-examples.md
@@ -6,7 +6,7 @@
**Architecture:** Each capability follows the LangGraph example pattern but with richer sidebar content. The Python graphs use LangGraph tool-calling patterns (agents that invoke tools). The Angular components leverage `stream.toolCalls()`, `stream.toolProgress()`, and `stream.subagents()` signals for real-time agent activity visualization.
-**Tech Stack:** Angular 21, `@cacheplane/angular`, `@cacheplane/chat`, LangGraph (Python), Playwright
+**Tech Stack:** Angular 21, `@cacheplane/langgraph`, `@cacheplane/chat`, LangGraph (Python), Playwright
---
diff --git a/docs/superpowers/plans/2026-04-04-docs-comprehensive-overhaul.md b/docs/superpowers/plans/2026-04-04-docs-comprehensive-overhaul.md
index 750904dfa..f4fe71008 100644
--- a/docs/superpowers/plans/2026-04-04-docs-comprehensive-overhaul.md
+++ b/docs/superpowers/plans/2026-04-04-docs-comprehensive-overhaul.md
@@ -18,11 +18,11 @@
- [ ] **Step 1: Fix import path inconsistency**
-Search all MDX and TSX files for `@ngxp/angular` and `@angular/angular`. Replace ALL with `@cacheplane/angular`.
+Search all MDX and TSX files for `@ngxp/angular` and `@angular/angular`. Replace ALL with `@cacheplane/langgraph`.
Run: `grep -rn "@ngxp/angular\|@angular/angular" apps/website/content/docs-v2/ apps/website/src/`
-Replace all occurrences with `@cacheplane/angular`.
+Replace all occurrences with `@cacheplane/langgraph`.
- [ ] **Step 2: Fix API method inconsistency**
@@ -158,7 +158,7 @@ New content needed:
### Task 9: Polish `guides/streaming.mdx` (206 lines — fix issues)
Fix:
-- Import path: `@angular/angular` → `@cacheplane/angular`
+- Import path: `@angular/angular` → `@cacheplane/langgraph`
- `.stream()` → `.submit()`
- `'streaming'` status → `'loading'`
- Add Python agent showing `stream_mode` configuration
@@ -203,7 +203,7 @@ Fix:
### Task 14: Expand 4 API Reference Pages
-Fix import path `@ngxp/angular` → `@cacheplane/angular` in all 4.
+Fix import path `@ngxp/angular` → `@cacheplane/langgraph` in all 4.
Add "What's Next" CardGroup to all 4.
Expand intros with more context about when/why to use each.
@@ -224,7 +224,7 @@ Total: 15 tasks, ~14 files rewritten.
- [ ] Python LangGraph code showing the agent/server pattern
- [ ] Angular agent code showing the frontend consumption
- [ ] Both paired together to tell the product story
-- [ ] All imports use `@cacheplane/angular`
+- [ ] All imports use `@cacheplane/langgraph`
- [ ] All Tab components use `
` syntax
- [ ] `ChangeDetectionStrategy.OnPush` in component examples
- [ ] At least 2 Callouts (tip, info, or warning)
diff --git a/docs/superpowers/plans/2026-04-04-docs-content-authoring.md b/docs/superpowers/plans/2026-04-04-docs-content-authoring.md
index 42f8fadc9..8a1863855 100644
--- a/docs/superpowers/plans/2026-04-04-docs-content-authoring.md
+++ b/docs/superpowers/plans/2026-04-04-docs-content-authoring.md
@@ -33,7 +33,7 @@ Angular 20+ project with Node.js 18+. If you need setup help, see the [Installat
## 1. Install
```bash
-npm install @cacheplane/angular
+npm install @cacheplane/langgraph
```
## 2. Configure the provider
@@ -42,7 +42,7 @@ Add `provideAgent()` to your application config with your LangGraph Platform URL
```typescript
// app.config.ts
-import { provideAgent } from '@cacheplane/angular';
+import { provideAgent } from '@cacheplane/langgraph';
export const appConfig: ApplicationConfig = {
providers: [
@@ -63,7 +63,7 @@ Use `agent()` in a component field initializer. Every property on the returned r
```typescript
// chat.component.ts
import { Component, signal, computed } from '@angular/core';
-import { agent } from '@cacheplane/angular';
+import { agent } from '@cacheplane/langgraph';
import type { BaseMessage } from '@langchain/core/messages';
@Component({
@@ -192,7 +192,7 @@ A running LangGraph agent accessible via HTTP. Can be local (langgraph dev) or d
## Install the package
```bash
-npm install @cacheplane/angular
+npm install @cacheplane/langgraph
```
This installs the library and its peer dependencies including `@langchain/langgraph-sdk`.
@@ -204,7 +204,7 @@ Add `provideAgent()` to your application configuration. This sets global default
```typescript
// app.config.ts
import { ApplicationConfig } from '@angular/core';
-import { provideAgent } from '@cacheplane/angular';
+import { provideAgent } from '@cacheplane/langgraph';
export const appConfig: ApplicationConfig = {
providers: [
@@ -252,7 +252,7 @@ provideAgent({
Create a minimal test to verify the setup works:
```typescript
-import { agent } from '@cacheplane/angular';
+import { agent } from '@cacheplane/langgraph';
// In a component
const test = agent({
@@ -759,8 +759,8 @@ Create a MockAgentTransport with scripted events and pass it to agent.
```typescript
import { TestBed } from '@angular/core/testing';
-import { MockAgentTransport } from '@cacheplane/angular';
-import type { StreamEvent } from '@cacheplane/angular';
+import { MockAgentTransport } from '@cacheplane/langgraph';
+import type { StreamEvent } from '@cacheplane/langgraph';
describe('ChatComponent', () => {
it('should display agent messages', () => {
diff --git a/docs/superpowers/plans/2026-04-04-langgraph-examples.md b/docs/superpowers/plans/2026-04-04-langgraph-examples.md
index be6b886ad..88f15eaaa 100644
--- a/docs/superpowers/plans/2026-04-04-langgraph-examples.md
+++ b/docs/superpowers/plans/2026-04-04-langgraph-examples.md
@@ -6,7 +6,7 @@
**Architecture:** Each capability follows the established streaming example pattern: Angular standalone component using `agent()`, Python LangGraph `StateGraph`, tutorial guide.md with component tags, Playwright e2e tests. The `@cacheplane/chat` library extracts the shared chat UI. All capabilities register in `route-resolution.ts` for cockpit rendering.
-**Tech Stack:** Angular 21, `@cacheplane/angular`, `@cacheplane/chat` (new), LangGraph (Python), Shiki, Playwright, Vitest
+**Tech Stack:** Angular 21, `@cacheplane/langgraph`, `@cacheplane/chat` (new), LangGraph (Python), Shiki, Playwright, Vitest
---
@@ -295,7 +295,7 @@ This is the first full example after Streaming. It demonstrates `stream.switchTh
#### Angular app: `cockpit/langgraph/persistence/angular/`
- `project.json` — same pattern as streaming, port 4301
-- `package.json` — deps: `@cacheplane/angular`, `@cacheplane/chat`, `@langchain/core`, `@langchain/langgraph-sdk`
+- `package.json` — deps: `@cacheplane/langgraph`, `@cacheplane/chat`, `@langchain/core`, `@langchain/langgraph-sdk`
- `tsconfig.json`, `tsconfig.app.json` — same as streaming
- `proxy.conf.json` — proxy /api to localhost:8124
- `src/index.html`, `src/main.ts`, `src/styles.css` — same pattern
diff --git a/docs/superpowers/plans/2026-04-05-chat-ui-redesign.md b/docs/superpowers/plans/2026-04-05-chat-ui-redesign.md
index 2e9ebf127..2529cff89 100644
--- a/docs/superpowers/plans/2026-04-05-chat-ui-redesign.md
+++ b/docs/superpowers/plans/2026-04-05-chat-ui-redesign.md
@@ -216,7 +216,7 @@ import {
output,
ChangeDetectionStrategy,
} from '@angular/core';
-import type { AgentRef } from '@cacheplane/angular';
+import type { AgentRef } from '@cacheplane/langgraph';
import { ChatMessagesComponent } from '../../primitives/chat-messages/chat-messages.component';
import { MessageTemplateDirective } from '../../primitives/chat-messages/message-template.directive';
import { ChatInputComponent } from '../../primitives/chat-input/chat-input.component';
diff --git a/docs/superpowers/plans/2026-04-05-cockpit-examples-chat-integration.md b/docs/superpowers/plans/2026-04-05-cockpit-examples-chat-integration.md
index 231903c0e..42aa3bb1f 100644
--- a/docs/superpowers/plans/2026-04-05-cockpit-examples-chat-integration.md
+++ b/docs/superpowers/plans/2026-04-05-cockpit-examples-chat-integration.md
@@ -6,7 +6,7 @@
**Architecture:** Each example follows the streaming reference pattern: standalone Angular app with `bootstrapApplication()`, `provideAgent()` + `provideChat()`, Angular CLI build/serve targets, proxy to LangGraph backend. LangGraph examples use ``, deep-agents examples use ``.
-**Tech Stack:** Angular 21, `@cacheplane/chat`, `@cacheplane/angular`, `@angular-devkit/build-angular`
+**Tech Stack:** Angular 21, `@cacheplane/chat`, `@cacheplane/langgraph`, `@angular-devkit/build-angular`
**Spec:** `docs/superpowers/specs/2026-04-05-cockpit-examples-chat-integration.md`
@@ -64,7 +64,7 @@ Each component follows this template (replace `{Topic}`, `{topic}`, `{selector}`
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
import { Component } from '@angular/core';
import { ChatComponent } from '@cacheplane/chat';
-import { agent } from '@cacheplane/angular';
+import { agent } from '@cacheplane/langgraph';
import { environment } from '../environments/environment';
@Component({
@@ -88,7 +88,7 @@ Where each example uses its existing environment config keys (e.g., `environment
```typescript
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
import { ApplicationConfig } from '@angular/core';
-import { provideAgent } from '@cacheplane/angular';
+import { provideAgent } from '@cacheplane/langgraph';
import { provideChat } from '@cacheplane/chat';
import { environment } from '../environments/environment';
diff --git a/docs/superpowers/plans/2026-04-05-streaming-example-chat-integration.md b/docs/superpowers/plans/2026-04-05-streaming-example-chat-integration.md
index 5d8aa6500..f0b626c03 100644
--- a/docs/superpowers/plans/2026-04-05-streaming-example-chat-integration.md
+++ b/docs/superpowers/plans/2026-04-05-streaming-example-chat-integration.md
@@ -2,11 +2,11 @@
> **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:** Wire the cockpit `langgraph/streaming/angular` example to correctly consume `@cacheplane/chat` and `@cacheplane/angular`, making it buildable and serveable as a standalone Angular app against a real LangGraph backend.
+**Goal:** Wire the cockpit `langgraph/streaming/angular` example to correctly consume `@cacheplane/chat` and `@cacheplane/langgraph`, making it buildable and serveable as a standalone Angular app against a real LangGraph backend.
**Architecture:** Standalone Angular app bootstrapped with `bootstrapApplication()`. Uses `provideAgent()` for global API URL, `provideChat()` for chat config. `StreamingComponent` creates a `agent()` ref and passes it to ``. Proxied to LangGraph dev server on port 8123 via `/api`.
-**Tech Stack:** Angular 21, `@cacheplane/chat`, `@cacheplane/angular`, `@angular-devkit/build-angular`, Tailwind CSS
+**Tech Stack:** Angular 21, `@cacheplane/chat`, `@cacheplane/langgraph`, `@angular-devkit/build-angular`, Tailwind CSS
---
@@ -81,7 +81,7 @@ The component must use `chat-ui` selector (the actual ChatComponent selector) wi
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
import { Component } from '@angular/core';
import { ChatComponent } from '@cacheplane/chat';
-import { agent } from '@cacheplane/angular';
+import { agent } from '@cacheplane/langgraph';
import { environment } from '../environments/environment';
/**
@@ -129,7 +129,7 @@ git commit -m "feat(cockpit): rewrite streaming component with correct chat-ui A
```typescript
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
import { ApplicationConfig } from '@angular/core';
-import { provideAgent } from '@cacheplane/angular';
+import { provideAgent } from '@cacheplane/langgraph';
import { provideChat } from '@cacheplane/chat';
import { environment } from '../environments/environment';
diff --git a/docs/superpowers/plans/2026-04-06-chat-polish.md b/docs/superpowers/plans/2026-04-06-chat-polish.md
index bcdd613a6..dbdb4de6e 100644
--- a/docs/superpowers/plans/2026-04-06-chat-polish.md
+++ b/docs/superpowers/plans/2026-04-06-chat-polish.md
@@ -321,7 +321,7 @@ import {
ViewEncapsulation,
} from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
-import type { AgentRef } from '@cacheplane/angular';
+import type { AgentRef } from '@cacheplane/langgraph';
import { ChatMessagesComponent } from '../../primitives/chat-messages/chat-messages.component';
import { MessageTemplateDirective } from '../../primitives/chat-messages/message-template.directive';
import { ChatInputComponent } from '../../primitives/chat-input/chat-input.component';
@@ -655,7 +655,7 @@ import {
ChangeDetectionStrategy,
} from '@angular/core';
import { FormsModule } from '@angular/forms';
-import type { AgentRef } from '@cacheplane/angular';
+import type { AgentRef } from '@cacheplane/langgraph';
import { ICON_SEND } from '../../styles/chat-icons';
export function submitMessage(
@@ -761,7 +761,7 @@ import {
input,
ChangeDetectionStrategy,
} from '@angular/core';
-import type { AgentRef } from '@cacheplane/angular';
+import type { AgentRef } from '@cacheplane/langgraph';
export function isTyping(ref: AgentRef): boolean {
return ref.isLoading();
@@ -823,7 +823,7 @@ import {
input,
ChangeDetectionStrategy,
} from '@angular/core';
-import type { AgentRef } from '@cacheplane/angular';
+import type { AgentRef } from '@cacheplane/langgraph';
export function extractErrorMessage(error: unknown): string | null {
if (!error) return null;
diff --git a/docs/superpowers/plans/2026-04-06-cockpit-examples-validation.md b/docs/superpowers/plans/2026-04-06-cockpit-examples-validation.md
index e6cf5ed91..6a50ad8e4 100644
--- a/docs/superpowers/plans/2026-04-06-cockpit-examples-validation.md
+++ b/docs/superpowers/plans/2026-04-06-cockpit-examples-validation.md
@@ -6,7 +6,7 @@
**Architecture:** Each example follows the pattern: `agent()` → `` + custom sidebar derived from `stream.value()` or `stream.messages()`. Sidebars are built directly in each example's component using Tailwind classes and the chat theme CSS vars. No new library components needed — the sidebars are example-specific UI, not reusable primitives.
-**Tech Stack:** Angular 20+, `@cacheplane/chat`, `@cacheplane/angular`, Tailwind CSS v4
+**Tech Stack:** Angular 20+, `@cacheplane/chat`, `@cacheplane/langgraph`, Tailwind CSS v4
**Parallelism:** Tasks 1-4 (LangGraph examples) are independent. Tasks 5-8 (Deep Agent examples) are independent. All can run in parallel within their group.
@@ -66,7 +66,7 @@ template: `
Key rules:
- Import `ChatComponent` from `@cacheplane/chat`
-- Use `agent()` from `@cacheplane/angular`
+- Use `agent()` from `@cacheplane/langgraph`
- Derive sidebar state with `computed()` from `stream.value()` or `stream.messages()`
- Use Tailwind + chat theme CSS vars for styling
- The `` component handles all message rendering, input, typing, errors internally
@@ -91,7 +91,7 @@ Replace the component with a layout that has chat + thread sidebar:
```typescript
import { Component, signal, computed } from '@angular/core';
import { ChatComponent } from '@cacheplane/chat';
-import { agent } from '@cacheplane/angular';
+import { agent } from '@cacheplane/langgraph';
import { environment } from '../environments/environment';
interface ThreadEntry {
@@ -209,7 +209,7 @@ Read `cockpit/langgraph/time-travel/angular/src/app/time-travel.component.ts`.
```typescript
import { Component, computed } from '@angular/core';
import { ChatComponent } from '@cacheplane/chat';
-import { agent } from '@cacheplane/angular';
+import { agent } from '@cacheplane/langgraph';
import { environment } from '../environments/environment';
@Component({
@@ -320,7 +320,7 @@ Read `cockpit/langgraph/durable-execution/angular/src/app/durable-execution.comp
```typescript
import { Component, computed } from '@angular/core';
import { ChatComponent } from '@cacheplane/chat';
-import { agent } from '@cacheplane/angular';
+import { agent } from '@cacheplane/langgraph';
import { environment } from '../environments/environment';
const PIPELINE_STEPS = ['analyze', 'plan', 'generate'] as const;
@@ -429,7 +429,7 @@ Read `cockpit/deep-agents/planning/angular/src/app/planning.component.ts`.
```typescript
import { Component, computed } from '@angular/core';
import { ChatComponent } from '@cacheplane/chat';
-import { agent } from '@cacheplane/angular';
+import { agent } from '@cacheplane/langgraph';
import { environment } from '../environments/environment';
interface PlanStep {
@@ -527,7 +527,7 @@ Derive file operations from `stream.messages()` by filtering for tool call messa
```typescript
import { Component, computed } from '@angular/core';
import { ChatComponent } from '@cacheplane/chat';
-import { agent } from '@cacheplane/angular';
+import { agent } from '@cacheplane/langgraph';
import { environment } from '../environments/environment';
interface FileOp {
@@ -635,7 +635,7 @@ Read `cockpit/deep-agents/subagents/angular/src/app/subagents.component.ts`.
```typescript
import { Component, computed } from '@angular/core';
import { ChatComponent } from '@cacheplane/chat';
-import { agent } from '@cacheplane/angular';
+import { agent } from '@cacheplane/langgraph';
import { environment } from '../environments/environment';
interface Delegation {
@@ -737,7 +737,7 @@ Same pattern as the LangGraph memory example but using `agent_memory` state fiel
```typescript
import { Component, computed } from '@angular/core';
import { ChatComponent } from '@cacheplane/chat';
-import { agent } from '@cacheplane/angular';
+import { agent } from '@cacheplane/langgraph';
import { environment } from '../environments/environment';
@Component({
@@ -814,7 +814,7 @@ Read `cockpit/deep-agents/skills/angular/src/app/skills.component.ts`.
```typescript
import { Component, computed } from '@angular/core';
import { ChatComponent } from '@cacheplane/chat';
-import { agent } from '@cacheplane/angular';
+import { agent } from '@cacheplane/langgraph';
import { environment } from '../environments/environment';
interface SkillInvocation {
@@ -919,7 +919,7 @@ Read `cockpit/deep-agents/sandboxes/angular/src/app/sandboxes.component.ts`.
```typescript
import { Component, computed } from '@angular/core';
import { ChatComponent } from '@cacheplane/chat';
-import { agent } from '@cacheplane/angular';
+import { agent } from '@cacheplane/langgraph';
import { environment } from '../environments/environment';
interface CodeExecution {
diff --git a/docs/superpowers/plans/2026-04-06-website-audit-mobile-design.md b/docs/superpowers/plans/2026-04-06-website-audit-mobile-design.md
index 93bb38484..3168ffe4e 100644
--- a/docs/superpowers/plans/2026-04-06-website-audit-mobile-design.md
+++ b/docs/superpowers/plans/2026-04-06-website-audit-mobile-design.md
@@ -302,7 +302,7 @@ import { tokens } from '../../../lib/design-tokens';
const BADGES = [
{ icon: '★', label: 'GitHub Stars', value: 'Open Source' },
- { icon: '↓', label: 'npm', value: '@cacheplane/angular' },
+ { icon: '↓', label: 'npm', value: '@cacheplane/langgraph' },
{ icon: '⚖', label: 'License', value: 'Source Available' },
];
diff --git a/docs/superpowers/plans/2026-04-07-generative-ui-views.md b/docs/superpowers/plans/2026-04-07-generative-ui-views.md
index deb3698b5..6f73efa81 100644
--- a/docs/superpowers/plans/2026-04-07-generative-ui-views.md
+++ b/docs/superpowers/plans/2026-04-07-generative-ui-views.md
@@ -548,7 +548,7 @@ export class CheckboxRowComponent {
import { Component } from '@angular/core';
import { ChatComponent, views } from '@cacheplane/chat';
import { signalStateStore } from '@cacheplane/render';
-import { agent } from '@cacheplane/angular';
+import { agent } from '@cacheplane/langgraph';
import { environment } from '../environments/environment';
import { PlanChecklistComponent } from './views/plan-checklist.component';
import { CheckboxRowComponent } from './views/checkbox-row.component';
diff --git a/docs/superpowers/plans/2026-04-07-views-examples-docs.md b/docs/superpowers/plans/2026-04-07-views-examples-docs.md
index f43ddf093..128027849 100644
--- a/docs/superpowers/plans/2026-04-07-views-examples-docs.md
+++ b/docs/superpowers/plans/2026-04-07-views-examples-docs.md
@@ -374,7 +374,7 @@ Views are Angular components that the agent can render inline in the chat. Regis
```typescript
import { views } from '@cacheplane/render';
import { ChatComponent } from '@cacheplane/chat';
-import { agent } from '@cacheplane/angular';
+import { agent } from '@cacheplane/langgraph';
const ui = views({
'plan-checklist': PlanChecklistComponent,
diff --git a/docs/superpowers/plans/2026-04-08-generative-ui-spike.md b/docs/superpowers/plans/2026-04-08-generative-ui-spike.md
index d05fde8ef..9746a7877 100644
--- a/docs/superpowers/plans/2026-04-08-generative-ui-spike.md
+++ b/docs/superpowers/plans/2026-04-08-generative-ui-spike.md
@@ -463,7 +463,7 @@ Create `cockpit/langgraph/generative-ui/angular/src/app/generative-ui.component.
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
import { Component } from '@angular/core';
import { ChatComponent, views } from '@cacheplane/chat';
-import { agent } from '@cacheplane/angular';
+import { agent } from '@cacheplane/langgraph';
import { environment } from '../environments/environment';
import { WeatherCardComponent } from './views/weather-card.component';
import { StatCardComponent } from './views/stat-card.component';
@@ -498,7 +498,7 @@ Create `cockpit/langgraph/generative-ui/angular/src/app/app.config.ts`:
```typescript
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
import { ApplicationConfig } from '@angular/core';
-import { provideAgent } from '@cacheplane/angular';
+import { provideAgent } from '@cacheplane/langgraph';
import { provideChat } from '@cacheplane/chat';
import { environment } from '../environments/environment';
@@ -619,7 +619,7 @@ Create `cockpit/langgraph/generative-ui/angular/package.json`:
"peerDependencies": {
"@cacheplane/chat": "^0.0.1",
"@cacheplane/render": "^0.0.1",
- "@cacheplane/angular": "^0.0.1",
+ "@cacheplane/langgraph": "^0.0.1",
"@json-render/core": "^0.16.0",
"@langchain/langgraph-sdk": "^0.0.36"
},
diff --git a/docs/superpowers/plans/2026-04-08-streaming-generative-ui-docs.md b/docs/superpowers/plans/2026-04-08-streaming-generative-ui-docs.md
index 4821ff2f3..0d0c11728 100644
--- a/docs/superpowers/plans/2026-04-08-streaming-generative-ui-docs.md
+++ b/docs/superpowers/plans/2026-04-08-streaming-generative-ui-docs.md
@@ -186,7 +186,7 @@ Pass a `ViewRegistry` via the `[views]` input on `ChatComponent`:
```typescript
import { Component, signal } from '@angular/core';
-import { agent } from '@cacheplane/angular';
+import { agent } from '@cacheplane/langgraph';
import { ChatComponent, views } from '@cacheplane/chat';
import type { BaseMessage } from '@langchain/core/messages';
import { WeatherCardComponent } from './weather-card.component';
@@ -675,7 +675,7 @@ The full table should be:
```
| Input | Type | Default | Description |
|-------|------|---------|-------------|
-| `ref` | `AgentRef` | **Required** | The agent ref providing streaming state. Created by `agent()` from `@cacheplane/angular`. |
+| `ref` | `AgentRef` | **Required** | The agent ref providing streaming state. Created by `agent()` from `@cacheplane/langgraph`. |
| `views` | `ViewRegistry \| undefined` | `undefined` | View registry for generative UI. Maps spec type names to Angular components. Created with `views()` from `@cacheplane/chat`. |
| `store` | `StateStore \| undefined` | `undefined` | Optional state store for interactive generative UI specs. |
| `threads` | `Thread[]` | `[]` | List of threads to display in the sidebar. Each thread must have an `id` property. |
@@ -793,7 +793,7 @@ Update the "Application-Wide Configuration" example: remove `createAngularRegist
```typescript
// app.config.ts
-import { provideAgent } from '@cacheplane/angular';
+import { provideAgent } from '@cacheplane/langgraph';
import { provideChat } from '@cacheplane/chat';
export const appConfig: ApplicationConfig = {
diff --git a/docs/superpowers/plans/2026-04-08-streaming-generative-ui.md b/docs/superpowers/plans/2026-04-08-streaming-generative-ui.md
index 4ca81ba67..c93ae91a3 100644
--- a/docs/superpowers/plans/2026-04-08-streaming-generative-ui.md
+++ b/docs/superpowers/plans/2026-04-08-streaming-generative-ui.md
@@ -1863,7 +1863,7 @@ git commit -m "feat(chat): add ContentClassifier for streaming content type dete
- Modify: `libs/chat/src/lib/compositions/chat/chat.component.spec.ts`
- Modify: `libs/chat/src/public-api.ts`
-**Context:** The current `ChatComponent` uses `AgentRef` (from `@cacheplane/angular`), has `views` input of type `ViewRegistry`, and `store` input of type `StateStore`. The AI message template uses `flex gap-3` with inline avatar (ChatGPT pattern, no "Assistant" label). `ChatGenerativeUiComponent` takes `AngularRegistry` — use `toRenderRegistry()` to convert from `ViewRegistry`.
+**Context:** The current `ChatComponent` uses `AgentRef` (from `@cacheplane/langgraph`), has `views` input of type `ViewRegistry`, and `store` input of type `StateStore`. The AI message template uses `flex gap-3` with inline avatar (ChatGPT pattern, no "Assistant" label). `ChatGenerativeUiComponent` takes `AngularRegistry` — use `toRenderRegistry()` to convert from `ViewRegistry`.
- [ ] **Step 1: Write failing tests for classified rendering**
diff --git a/docs/superpowers/plans/2026-04-09-a2ui-cockpit.md b/docs/superpowers/plans/2026-04-09-a2ui-cockpit.md
index 32723b37e..86346b9f4 100644
--- a/docs/superpowers/plans/2026-04-09-a2ui-cockpit.md
+++ b/docs/superpowers/plans/2026-04-09-a2ui-cockpit.md
@@ -6,7 +6,7 @@
**Architecture:** Modify `ChatComponent.onA2uiEvent()` to auto-route `a2ui:event` actions via `this.ref().submit()`. Then scaffold a new `cockpit/chat/a2ui/` example with Angular app (using `a2uiBasicCatalog()`) and Python LangGraph agent that emits A2UI JSONL for a contact form.
-**Tech Stack:** Angular 20+, `@cacheplane/angular`, `@cacheplane/chat`, `@cacheplane/a2ui`, LangGraph Python, Vitest, Playwright
+**Tech Stack:** Angular 20+, `@cacheplane/langgraph`, `@cacheplane/chat`, `@cacheplane/a2ui`, LangGraph Python, Vitest, Playwright
---
@@ -261,7 +261,7 @@ export const environment = {
```typescript
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
import { ApplicationConfig } from '@angular/core';
-import { provideAgent } from '@cacheplane/angular';
+import { provideAgent } from '@cacheplane/langgraph';
import { provideChat } from '@cacheplane/chat';
import { environment } from '../environments/environment';
@@ -279,7 +279,7 @@ export const appConfig: ApplicationConfig = {
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
import { Component } from '@angular/core';
import { ChatComponent, a2uiBasicCatalog } from '@cacheplane/chat';
-import { agent } from '@cacheplane/angular';
+import { agent } from '@cacheplane/langgraph';
import { environment } from '../environments/environment';
@Component({
@@ -636,7 +636,7 @@ Import `a2uiBasicCatalog()` and pass it via the `[views]` input:
```typescript
import { ChatComponent, a2uiBasicCatalog } from '@cacheplane/chat';
-import { agent } from '@cacheplane/angular';
+import { agent } from '@cacheplane/langgraph';
@Component({
selector: 'app-a2ui',
@@ -828,7 +828,7 @@ or log events without intercepting the routing.
```typescript
import { ChatComponent, a2uiBasicCatalog } from '@cacheplane/chat';
-import { agent } from '@cacheplane/angular';
+import { agent } from '@cacheplane/langgraph';
@Component({
template: ``,
diff --git a/docs/superpowers/plans/2026-04-09-library-landing-pages.md b/docs/superpowers/plans/2026-04-09-library-landing-pages.md
index daaab52a8..996c30f57 100644
--- a/docs/superpowers/plans/2026-04-09-library-landing-pages.md
+++ b/docs/superpowers/plans/2026-04-09-library-landing-pages.md
@@ -29,7 +29,7 @@ const LIBRARIES = [
{
id: 'angular',
tag: 'Agent',
- pkg: '@cacheplane/angular',
+ pkg: '@cacheplane/langgraph',
color: tokens.colors.accent,
rgb: '0,64,144',
oneLiner: 'Signal-native streaming for LangGraph agents',
@@ -466,7 +466,7 @@ import { AngularFooterCTA } from '../../components/landing/angular/AngularFooter
import { tokens } from '@cacheplane/design-tokens';
export const metadata = {
- title: '@cacheplane/angular — Agent Streaming for Angular',
+ title: '@cacheplane/langgraph — Agent Streaming for Angular',
description: 'Ship LangGraph agents in Angular. Signal-native streaming, thread persistence, interrupts, and deterministic testing.',
};
@@ -507,7 +507,7 @@ export function AngularHero() {
fontFamily: "'JetBrains Mono', monospace", fontSize: 11, letterSpacing: '0.08em',
color: tokens.colors.accent, textTransform: 'uppercase', display: 'inline-block', marginBottom: '1.5rem',
}}>
- @cacheplane/angular
+ @cacheplane/langgraph
@@ -616,7 +616,7 @@ export function AngularProblemSolution() {
fontSize: '0.58rem', fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.07em',
padding: '2px 9px', borderRadius: 5, color: '#fff', background: '#b71c1c', marginBottom: 16,
}}>
- Without @cacheplane/angular
+ Without @cacheplane/langgraph
- With @cacheplane/angular
+ With @cacheplane/langgraph
chat.isStreaming(); // Signal
chat.interrupt(); // Signal`;
-const SNIPPET_2 = `import { provideAgent } from '@cacheplane/angular';
+const SNIPPET_2 = `import { provideAgent } from '@cacheplane/langgraph';
provideAgent({
graphId: 'my-agent',
@@ -894,7 +894,7 @@ export function AngularComparison() {
fontSize: 'clamp(26px,3.5vw,42px)', fontWeight: 800, lineHeight: 1.1,
color: tokens.colors.textPrimary,
}}>
- LangGraph Angular SDK vs @cacheplane/angular
+ LangGraph Angular SDK vs @cacheplane/langgraph
@@ -914,7 +914,7 @@ export function AngularComparison() {
display: 'grid', gridTemplateColumns: '1fr 1fr 1fr',
background: 'rgba(255,255,255,.3)', borderBottom: `1px solid ${tokens.glass.border}`, padding: '14px 24px',
}}>
- {['Capability', 'LangGraph Angular SDK', '@cacheplane/angular'].map((h, i) => (
+ {['Capability', 'LangGraph Angular SDK', '@cacheplane/langgraph'].map((h, i) => (
Guide Follow-up
Did you read Chapter 2 on the agent() API?
-
Chapter 2 covers the agent() API — signal-native streaming, provideAgent(), and reactive state management. It's the fastest way to understand what @cacheplane/angular does differently.
+
Chapter 2 covers the agent() API — signal-native streaming, provideAgent(), and reactive state management. It's the fastest way to understand what @cacheplane/langgraph does differently.
Explore the Docs →
`,
showUnsubscribe: true,
@@ -1531,12 +1531,12 @@ export function dripAngularFollowupHtml(day: number): { subject: string; html: s
if (day === 5) {
return {
- subject: 'LangGraph Angular SDK vs @cacheplane/angular',
+ subject: 'LangGraph Angular SDK vs @cacheplane/langgraph',
html: wrapEmail({
body: `
Comparison
-
LangGraph Angular SDK vs @cacheplane/angular
-
The official LangGraph Angular SDK gives you basic SSE wiring. @cacheplane/angular gives you signal-native streaming, thread persistence, interrupt handling, time travel, DeepAgent support, and deterministic testing — production patterns your team can own.
+
LangGraph Angular SDK vs @cacheplane/langgraph
+
The official LangGraph Angular SDK gives you basic SSE wiring. @cacheplane/langgraph gives you signal-native streaming, thread persistence, interrupt handling, time travel, DeepAgent support, and deterministic testing — production patterns your team can own.
See the Full Comparison →
`,
showUnsubscribe: true,
diff --git a/docs/superpowers/plans/2026-04-10-homepage-narrative-funnel.md b/docs/superpowers/plans/2026-04-10-homepage-narrative-funnel.md
index 8511cdba3..ba737366b 100644
--- a/docs/superpowers/plans/2026-04-10-homepage-narrative-funnel.md
+++ b/docs/superpowers/plans/2026-04-10-homepage-narrative-funnel.md
@@ -211,7 +211,7 @@ const LIBRARIES = [
{
id: 'angular',
tag: 'Agent',
- pkg: '@cacheplane/angular',
+ pkg: '@cacheplane/langgraph',
color: tokens.colors.accent,
rgb: '0,64,144',
headline: 'The reactive bridge to LangGraph',
diff --git a/docs/superpowers/plans/2026-04-13-solutions-landing-pages.md b/docs/superpowers/plans/2026-04-13-solutions-landing-pages.md
index da2775811..279877772 100644
--- a/docs/superpowers/plans/2026-04-13-solutions-landing-pages.md
+++ b/docs/superpowers/plans/2026-04-13-solutions-landing-pages.md
@@ -97,7 +97,7 @@ export const SOLUTIONS: SolutionConfig[] = [
architectureLayers: [
{
library: 'Agent',
- pkg: '@cacheplane/angular',
+ pkg: '@cacheplane/langgraph',
role: 'Signal-native streaming with first-class interrupt support. Every agent action can require human approval before execution. Thread persistence gives you a complete, immutable history of every decision.',
},
{
@@ -146,7 +146,7 @@ export const SOLUTIONS: SolutionConfig[] = [
architectureLayers: [
{
library: 'Agent',
- pkg: '@cacheplane/angular',
+ pkg: '@cacheplane/langgraph',
role: 'Streams query results token-by-token as the LangGraph agent reasons over your data. Thread persistence means users can refine questions without re-running expensive queries.',
},
{
@@ -195,7 +195,7 @@ export const SOLUTIONS: SolutionConfig[] = [
architectureLayers: [
{
library: 'Agent',
- pkg: '@cacheplane/angular',
+ pkg: '@cacheplane/langgraph',
role: 'LangGraph interrupts let the agent pause before sensitive actions — refunds, account changes, escalations. Thread persistence preserves the full conversation across bot-to-human handoffs.',
},
{
diff --git a/docs/superpowers/plans/2026-04-14-landing-page-alignment.md b/docs/superpowers/plans/2026-04-14-landing-page-alignment.md
index 9ffcb485d..228616d84 100644
--- a/docs/superpowers/plans/2026-04-14-landing-page-alignment.md
+++ b/docs/superpowers/plans/2026-04-14-landing-page-alignment.md
@@ -59,7 +59,7 @@ export function AngularHero() {
fontFamily: "'JetBrains Mono', monospace", fontSize: 11, letterSpacing: '0.08em',
color: tokens.colors.accent, textTransform: 'uppercase', display: 'inline-block', marginBottom: '1.5rem',
}}>
- @cacheplane/angular
+ @cacheplane/langgraph
@@ -1037,7 +1037,7 @@ import { tokens } from '@cacheplane/design-tokens';
const SIBLINGS = [
{
tag: 'Agent',
- pkg: '@cacheplane/angular',
+ pkg: '@cacheplane/langgraph',
color: tokens.colors.accent,
rgb: '0,64,144',
headline: 'The reactive bridge to LangGraph',
@@ -1194,7 +1194,7 @@ import { tokens } from '@cacheplane/design-tokens';
const SIBLINGS = [
{
tag: 'Agent',
- pkg: '@cacheplane/angular',
+ pkg: '@cacheplane/langgraph',
color: tokens.colors.accent,
rgb: '0,64,144',
headline: 'The reactive bridge to LangGraph',
diff --git a/docs/superpowers/plans/2026-04-18-release-infrastructure.md b/docs/superpowers/plans/2026-04-18-release-infrastructure.md
index a388431c5..a4a54e2b2 100644
--- a/docs/superpowers/plans/2026-04-18-release-infrastructure.md
+++ b/docs/superpowers/plans/2026-04-18-release-infrastructure.md
@@ -2,7 +2,7 @@
> **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:** Make `nx release` a one-command release pipeline for `@cacheplane/angular`, `@cacheplane/render`, `@cacheplane/chat` — independent per-package semver, Keep-a-Changelog changelogs, enforced conventional commits, GitHub Actions publish with npm provenance.
+**Goal:** Make `nx release` a one-command release pipeline for `@cacheplane/langgraph`, `@cacheplane/render`, `@cacheplane/chat` — independent per-package semver, Keep-a-Changelog changelogs, enforced conventional commits, GitHub Actions publish with npm provenance.
**Architecture:** Nx Release handles version bumps + changelog generation from conventional commits. Local workflow: developer runs `nx release version` + `nx release changelog` → commits + tags are pushed → GitHub Actions `release.yml` triggers on `
@v*` tag pattern → runs `nx release publish` with npm provenance via OIDC. Commitlint enforces the conventional commit contract via a husky `commit-msg` hook locally and a CI job on PRs. Verdaccio provides a local dry-run smoke test so we catch config errors without burning a version number.
@@ -14,7 +14,7 @@
### In scope
- Nx Release configured for independent per-package versioning of agent / render / chat
-- Tag format: `@v` (e.g., `@cacheplane/angular@v1.0.0`)
+- Tag format: `@v` (e.g., `@cacheplane/langgraph@v1.0.0`)
- `CHANGELOG.md` per library in Keep-a-Changelog format, auto-updated by Nx Release
- Conventional commits enforced locally (husky) and on PRs (GitHub Action)
- GitHub Actions workflow `release.yml` that publishes with `--provenance` via OIDC
@@ -35,7 +35,7 @@
- `.husky/commit-msg` — husky hook for commitlint
- `.github/workflows/release.yml` — new release workflow
- `.github/workflows/commitlint.yml` — PR commitlint CI
-- `libs/agent/CHANGELOG.md` — Keep-a-Changelog seed
+- `libs/langgraph/CHANGELOG.md` — Keep-a-Changelog seed
- `libs/render/CHANGELOG.md` — seed
- `libs/chat/CHANGELOG.md` — seed
- `scripts/verify-release-local.sh` — Verdaccio dry-run smoke
@@ -43,7 +43,7 @@
**Modify:**
- `nx.json` — expand `release` block (projects, version, changelog, tag format)
-- `libs/agent/package.json` — add `publishConfig`
+- `libs/langgraph/package.json` — add `publishConfig`
- `libs/render/package.json` — add `publishConfig`
- `libs/chat/package.json` — add `publishConfig`
- `package.json` — add devDeps (`@commitlint/cli`, `@commitlint/config-conventional`, `husky`) + release scripts + `prepare` hook
@@ -209,16 +209,16 @@ git commit -m "ci(release): enforce conventional commits on PRs"
## Task 3: Seed CHANGELOG.md for each library
**Files:**
-- Create: `libs/agent/CHANGELOG.md`
+- Create: `libs/langgraph/CHANGELOG.md`
- Create: `libs/render/CHANGELOG.md`
- Create: `libs/chat/CHANGELOG.md`
-- [ ] **Step 1: Create `libs/agent/CHANGELOG.md`**
+- [ ] **Step 1: Create `libs/langgraph/CHANGELOG.md`**
```markdown
# Changelog
-All notable changes to `@cacheplane/angular` will be documented in this file.
+All notable changes to `@cacheplane/langgraph` will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
@@ -256,7 +256,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
Run:
```bash
-git add libs/agent/CHANGELOG.md libs/render/CHANGELOG.md libs/chat/CHANGELOG.md
+git add libs/langgraph/CHANGELOG.md libs/render/CHANGELOG.md libs/chat/CHANGELOG.md
git commit -m "docs(release): seed Keep-a-Changelog files for v1 packages"
```
@@ -265,18 +265,18 @@ git commit -m "docs(release): seed Keep-a-Changelog files for v1 packages"
## Task 4: Add `publishConfig` to each library's `package.json`
**Files:**
-- Modify: `libs/agent/package.json`
+- Modify: `libs/langgraph/package.json`
- Modify: `libs/render/package.json`
- Modify: `libs/chat/package.json`
The `publishConfig.provenance` flag tells npm to use the OIDC token from GitHub Actions to sign the published package.
-- [ ] **Step 1: Edit `libs/agent/package.json`**
+- [ ] **Step 1: Edit `libs/langgraph/package.json`**
Add a `publishConfig` key before `peerDependencies`:
```json
{
- "name": "@cacheplane/angular",
+ "name": "@cacheplane/langgraph",
"version": "0.0.1",
"publishConfig": {
"access": "public",
@@ -300,7 +300,7 @@ Add the same `publishConfig` block.
Run:
```bash
-for f in libs/agent/package.json libs/render/package.json libs/chat/package.json; do
+for f in libs/langgraph/package.json libs/render/package.json libs/chat/package.json; do
node -e "JSON.parse(require('fs').readFileSync('$f', 'utf8'))" && echo "$f OK"
done
```
@@ -311,7 +311,7 @@ Expected: three lines of `... OK`.
Run:
```bash
-git add libs/agent/package.json libs/render/package.json libs/chat/package.json
+git add libs/langgraph/package.json libs/render/package.json libs/chat/package.json
git commit -m "feat(release): enable npm provenance for v1 packages"
```
@@ -360,7 +360,7 @@ Replace with:
**Key behaviors:**
- `projectsRelationship: "independent"` — each package versions on its own schedule
-- `releaseTagPattern: "{projectName}@v{version}"` — produces e.g. `agent@v1.0.0`. Note: the Nx project name is `agent` (not `@cacheplane/angular`); the tag uses the project name.
+- `releaseTagPattern: "{projectName}@v{version}"` — produces e.g. `agent@v1.0.0`. Note: the Nx project name is `agent` (not `@cacheplane/langgraph`); the tag uses the project name.
- `conventionalCommits: true` — analyze commits since last tag to determine bump
- `currentVersionResolver: "git-tag"` — reads current version from latest matching tag, falls back to `package.json` on first release
- `projectChangelogs` writes per-library `CHANGELOG.md`; `createRelease: "github"` publishes GitHub Releases
@@ -636,7 +636,7 @@ Create `docs/release-runbook.md`:
```markdown
# Release Runbook
-How to cut a new release of `@cacheplane/angular`, `@cacheplane/render`, or `@cacheplane/chat`.
+How to cut a new release of `@cacheplane/langgraph`, `@cacheplane/render`, or `@cacheplane/chat`.
## Prerequisites
@@ -673,7 +673,7 @@ How to cut a new release of `@cacheplane/angular`, `@cacheplane/render`, or `@ca
git tag --contains HEAD
```
- Expected: one commit per bumped package (e.g., `chore(release): publish @cacheplane/angular@1.0.0`) and matching tags like `agent@v1.0.0`.
+ Expected: one commit per bumped package (e.g., `chore(release): publish @cacheplane/langgraph@1.0.0`) and matching tags like `agent@v1.0.0`.
4. **Push commits + tags:**
@@ -685,7 +685,7 @@ How to cut a new release of `@cacheplane/angular`, `@cacheplane/render`, or `@ca
5. **Verify on npm:**
- - https://www.npmjs.com/package/@cacheplane/angular
+ - https://www.npmjs.com/package/@cacheplane/langgraph
- https://www.npmjs.com/package/@cacheplane/render
- https://www.npmjs.com/package/@cacheplane/chat
@@ -726,7 +726,7 @@ Provide `version-spec` (e.g., `patch`, `1.0.1`) or leave empty to use convention
## Version policy
-- `@cacheplane/angular`, `@cacheplane/render`, `@cacheplane/chat` follow semver independently.
+- `@cacheplane/langgraph`, `@cacheplane/render`, `@cacheplane/chat` follow semver independently.
- Breaking changes to any public export = major bump.
- New exports or non-breaking API additions = minor bump.
- Bug fixes with no API change = patch bump.
diff --git a/docs/superpowers/plans/2026-04-19-license-verification-library.md b/docs/superpowers/plans/2026-04-19-license-verification-library.md
index 69c99d282..b0dd4bafc 100644
--- a/docs/superpowers/plans/2026-04-19-license-verification-library.md
+++ b/docs/superpowers/plans/2026-04-19-license-verification-library.md
@@ -2,7 +2,7 @@
> **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:** Ship `@cacheplane/licensing` — a pure-TS library that performs offline Ed25519 license verification, surfaces a grace-period + nag UX, and sends non-blocking anonymous telemetry — then wire it into `@cacheplane/angular`, `@cacheplane/render`, and `@cacheplane/chat` so a licensed app initializes silently and an unlicensed app sees a console nudge but still runs.
+**Goal:** Ship `@cacheplane/licensing` — a pure-TS library that performs offline Ed25519 license verification, surfaces a grace-period + nag UX, and sends non-blocking anonymous telemetry — then wire it into `@cacheplane/langgraph`, `@cacheplane/render`, and `@cacheplane/chat` so a licensed app initializes silently and an unlicensed app sees a console nudge but still runs.
**Architecture:** `@cacheplane/licensing` is a framework-agnostic TypeScript library (built with `@nx/js:tsc`, same pattern as `libs/a2ui`) that exposes four seams:
@@ -48,8 +48,8 @@ Each Angular library receives an optional `license` field on its existing config
**Modified Angular libraries** — integrate license check into providers:
-- `libs/agent/src/lib/agent.provider.ts` — add `license?: string` to `AgentConfig`; call `runLicenseCheck` inside `provideAgent`
-- `libs/agent/src/lib/agent.provider.spec.ts` — tests that config is still honored and license check is invoked
+- `libs/langgraph/src/lib/agent.provider.ts` — add `license?: string` to `AgentConfig`; call `runLicenseCheck` inside `provideAgent`
+- `libs/langgraph/src/lib/agent.provider.spec.ts` — tests that config is still honored and license check is invoked
- `libs/render/src/lib/provide-render.ts` — add `license?: string` to `RenderConfig`; call `runLicenseCheck`
- `libs/render/src/lib/render.types.ts` — add `license?: string` on `RenderConfig`
- `libs/render/src/lib/provide-render.spec.ts` — **create**; license check wired
@@ -59,7 +59,7 @@ Each Angular library receives an optional `license` field on its existing config
**Workspace** — wiring:
- `tsconfig.base.json` — add `"@cacheplane/licensing": ["libs/licensing/src/index.ts"]` path alias
-- `libs/agent/package.json` — add `@cacheplane/licensing` to `peerDependencies` with `^0.0.1`
+- `libs/langgraph/package.json` — add `@cacheplane/licensing` to `peerDependencies` with `^0.0.1`
- `libs/render/package.json` — add `@cacheplane/licensing` to `peerDependencies` with `^0.0.1`
- `libs/chat/package.json` — add `@cacheplane/licensing` to `peerDependencies` with `^0.0.1`
- `package.json` — add `@noble/ed25519` to root `dependencies`
@@ -747,26 +747,26 @@ describe('emitNag', () => {
});
it('is silent when status is licensed', () => {
- emitNag({ status: 'licensed' }, { package: '@cacheplane/angular', warn });
+ emitNag({ status: 'licensed' }, { package: '@cacheplane/langgraph', warn });
expect(warn).not.toHaveBeenCalled();
});
it('is silent when status is noncommercial', () => {
- emitNag({ status: 'noncommercial' }, { package: '@cacheplane/angular', warn });
+ emitNag({ status: 'noncommercial' }, { package: '@cacheplane/langgraph', warn });
expect(warn).not.toHaveBeenCalled();
});
it('warns with a stable prefix when status is missing', () => {
- emitNag({ status: 'missing' }, { package: '@cacheplane/angular', warn });
+ emitNag({ status: 'missing' }, { package: '@cacheplane/langgraph', warn });
expect(warn).toHaveBeenCalledTimes(1);
const message = warn.mock.calls[0][0] as string;
expect(message).toContain('[cacheplane]');
- expect(message).toContain('@cacheplane/angular');
+ expect(message).toContain('@cacheplane/langgraph');
expect(message).toContain('cacheplane.dev/pricing');
});
it('warns differently for grace / expired / tampered', () => {
- emitNag({ status: 'grace' }, { package: '@cacheplane/angular', warn });
+ emitNag({ status: 'grace' }, { package: '@cacheplane/langgraph', warn });
emitNag({ status: 'expired' }, { package: '@cacheplane/render', warn });
emitNag({ status: 'tampered' }, { package: '@cacheplane/chat', warn });
expect(warn).toHaveBeenCalledTimes(3);
@@ -776,13 +776,13 @@ describe('emitNag', () => {
});
it('dedupes repeated calls for the same package + status', () => {
- emitNag({ status: 'missing' }, { package: '@cacheplane/angular', warn });
- emitNag({ status: 'missing' }, { package: '@cacheplane/angular', warn });
+ emitNag({ status: 'missing' }, { package: '@cacheplane/langgraph', warn });
+ emitNag({ status: 'missing' }, { package: '@cacheplane/langgraph', warn });
expect(warn).toHaveBeenCalledTimes(1);
});
it('does not dedupe across different packages', () => {
- emitNag({ status: 'missing' }, { package: '@cacheplane/angular', warn });
+ emitNag({ status: 'missing' }, { package: '@cacheplane/langgraph', warn });
emitNag({ status: 'missing' }, { package: '@cacheplane/render', warn });
expect(warn).toHaveBeenCalledTimes(2);
});
@@ -803,7 +803,7 @@ Expected: FAIL — `emitNag is not a function`.
import type { EvaluateResult } from './evaluate-license';
export interface EmitNagOptions {
- /** Fully-qualified npm package name, e.g. "@cacheplane/angular". */
+ /** Fully-qualified npm package name, e.g. "@cacheplane/langgraph". */
package: string;
/** Injected warn channel; defaults to `console.warn`. */
warn?: (message: string) => void;
@@ -896,7 +896,7 @@ describe('createTelemetryClient', () => {
});
await client.send({
- package: '@cacheplane/angular',
+ package: '@cacheplane/langgraph',
version: '1.0.0',
licenseId: 'cus_123',
});
@@ -906,7 +906,7 @@ describe('createTelemetryClient', () => {
expect(url).toBe('https://telemetry.example.com/v1/ping');
expect(init.method).toBe('POST');
const body = JSON.parse(init.body as string);
- expect(body.package).toBe('@cacheplane/angular');
+ expect(body.package).toBe('@cacheplane/langgraph');
expect(body.version).toBe('1.0.0');
expect(body.license_id).toBe('cus_123');
expect(typeof body.anon_instance_id).toBe('string');
@@ -919,8 +919,8 @@ describe('createTelemetryClient', () => {
endpoint: 'https://telemetry.example.com/v1/ping',
fetch: fetchMock,
});
- await client.send({ package: '@cacheplane/angular', version: '1.0.0' });
- await client.send({ package: '@cacheplane/angular', version: '1.0.0' });
+ await client.send({ package: '@cacheplane/langgraph', version: '1.0.0' });
+ await client.send({ package: '@cacheplane/langgraph', version: '1.0.0' });
const id1 = JSON.parse(fetchMock.mock.calls[0][1].body as string).anon_instance_id;
const id2 = JSON.parse(fetchMock.mock.calls[1][1].body as string).anon_instance_id;
@@ -934,7 +934,7 @@ describe('createTelemetryClient', () => {
endpoint: 'https://telemetry.example.com/v1/ping',
fetch: fetchMock,
});
- await client.send({ package: '@cacheplane/angular', version: '1.0.0' });
+ await client.send({ package: '@cacheplane/langgraph', version: '1.0.0' });
expect(fetchMock).not.toHaveBeenCalled();
});
@@ -945,7 +945,7 @@ describe('createTelemetryClient', () => {
endpoint: 'https://telemetry.example.com/v1/ping',
fetch: fetchMock,
});
- await client.send({ package: '@cacheplane/angular', version: '1.0.0' });
+ await client.send({ package: '@cacheplane/langgraph', version: '1.0.0' });
expect(fetchMock).not.toHaveBeenCalled();
});
@@ -956,7 +956,7 @@ describe('createTelemetryClient', () => {
fetch: fetchMock,
});
await expect(
- client.send({ package: '@cacheplane/angular', version: '1.0.0' }),
+ client.send({ package: '@cacheplane/langgraph', version: '1.0.0' }),
).resolves.toBeUndefined();
});
});
@@ -1279,7 +1279,7 @@ describe('runLicenseCheck', () => {
it('does not warn with a valid token and still fires telemetry', async () => {
const status = await runLicenseCheck({
- package: '@cacheplane/angular',
+ package: '@cacheplane/langgraph',
version: '1.0.0',
token: validToken,
publicKey: kp.publicKey,
@@ -1297,7 +1297,7 @@ describe('runLicenseCheck', () => {
it('warns when token is missing', async () => {
const status = await runLicenseCheck({
- package: '@cacheplane/angular',
+ package: '@cacheplane/langgraph',
version: '1.0.0',
publicKey: kp.publicKey,
nowSec: 1_900_000_000,
@@ -1311,7 +1311,7 @@ describe('runLicenseCheck', () => {
it('is idempotent per (package, token) pair', async () => {
await runLicenseCheck({
- package: '@cacheplane/angular',
+ package: '@cacheplane/langgraph',
version: '1.0.0',
token: validToken,
publicKey: kp.publicKey,
@@ -1321,7 +1321,7 @@ describe('runLicenseCheck', () => {
fetch: fetchMock,
});
await runLicenseCheck({
- package: '@cacheplane/angular',
+ package: '@cacheplane/langgraph',
version: '1.0.0',
token: validToken,
publicKey: kp.publicKey,
@@ -1338,7 +1338,7 @@ describe('runLicenseCheck', () => {
it('re-runs when token changes (e.g., after key rotation in the host)', async () => {
const otherToken = await signLicense({ ...BASE, sub: 'cus_xyz' }, kp.privateKey);
await runLicenseCheck({
- package: '@cacheplane/angular',
+ package: '@cacheplane/langgraph',
version: '1.0.0',
token: validToken,
publicKey: kp.publicKey,
@@ -1348,7 +1348,7 @@ describe('runLicenseCheck', () => {
fetch: fetchMock,
});
await runLicenseCheck({
- package: '@cacheplane/angular',
+ package: '@cacheplane/langgraph',
version: '1.0.0',
token: otherToken,
publicKey: kp.publicKey,
@@ -1539,7 +1539,7 @@ Angular framework libraries.
## Status
-Private, pre-1.0. Consumed by `@cacheplane/angular`, `@cacheplane/render`, and
+Private, pre-1.0. Consumed by `@cacheplane/langgraph`, `@cacheplane/render`, and
`@cacheplane/chat`. Not intended as a standalone import.
## Behavior
@@ -1565,17 +1565,17 @@ git commit -m "docs(licensing): add README and shared test fixtures"
---
-## Task 10: Integrate license check into `@cacheplane/angular`
+## Task 10: Integrate license check into `@cacheplane/langgraph`
**Files:**
- Create: `libs/licensing/src/testing.ts`
- Modify: `tsconfig.base.json` (add `@cacheplane/licensing/testing` path)
- Modify: `libs/licensing/tsconfig.lib.json` (exclude `src/testing.ts`)
-- Modify: `libs/agent/tsconfig.json` (remove `baseUrl: "."` override)
-- Modify: `libs/agent/src/test-setup.ts` (scoped sha512 patch)
-- Modify: `libs/agent/src/lib/agent.provider.ts`
-- Modify: `libs/agent/src/lib/agent.provider.spec.ts`
-- Modify: `libs/agent/package.json`
+- Modify: `libs/langgraph/tsconfig.json` (remove `baseUrl: "."` override)
+- Modify: `libs/langgraph/src/test-setup.ts` (scoped sha512 patch)
+- Modify: `libs/langgraph/src/lib/agent.provider.ts`
+- Modify: `libs/langgraph/src/lib/agent.provider.spec.ts`
+- Modify: `libs/langgraph/package.json`
**Guardrails (read before starting):**
@@ -1616,11 +1616,11 @@ In `libs/licensing/tsconfig.lib.json`, add `"src/testing.ts"` to the existing ex
"exclude": ["src/**/*.spec.ts", "src/lib/testing/**", "src/testing.ts"]
```
-- [ ] **Step 1d: Remove the `baseUrl` override from `libs/agent/tsconfig.json`**
+- [ ] **Step 1d: Remove the `baseUrl` override from `libs/langgraph/tsconfig.json`**
-`libs/agent/tsconfig.json` currently sets `"baseUrl": "."`, which shifts path resolution relative to the agent dir and prevents `@cacheplane/licensing` from resolving. Delete that line — the `chat` and `render` tsconfigs don't have it either.
+`libs/langgraph/tsconfig.json` currently sets `"baseUrl": "."`, which shifts path resolution relative to the agent dir and prevents `@cacheplane/licensing` from resolving. Delete that line — the `chat` and `render` tsconfigs don't have it either.
-- [ ] **Step 1e: Patch `sha512Async` in `libs/agent/src/test-setup.ts`**
+- [ ] **Step 1e: Patch `sha512Async` in `libs/langgraph/src/test-setup.ts`**
`@noble/ed25519` defaults to `crypto.subtle.digest('sha-512', ...)`. jsdom's SubtleCrypto rejects TypedArrays from the Node realm with "2nd argument is not instance of ArrayBuffer" (cross-realm instanceof). Scope a Node-crypto replacement to the agent test env only:
@@ -1649,7 +1649,7 @@ getTestBed().initTestEnvironment(
);
```
-- [ ] **Step 2: Replace `libs/agent/src/lib/agent.provider.spec.ts` with the updated test suite**
+- [ ] **Step 2: Replace `libs/langgraph/src/lib/agent.provider.spec.ts` with the updated test suite**
```ts
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
@@ -1729,7 +1729,7 @@ describe('provideAgent', () => {
TestBed.inject(AGENT_CONFIG);
await new Promise((r) => setTimeout(r, 0));
const calls = warn.mock.calls.map((c) => String(c[0]));
- expect(calls.some((m) => m.includes('[cacheplane] @cacheplane/angular'))).toBe(true);
+ expect(calls.some((m) => m.includes('[cacheplane] @cacheplane/langgraph'))).toBe(true);
});
});
```
@@ -1741,7 +1741,7 @@ Expected: FAIL — `license` / `__licensePublicKey` not known properties of `Age
- [ ] **Step 4: Implement provider changes**
-Replace `libs/agent/src/lib/agent.provider.ts` with:
+Replace `libs/langgraph/src/lib/agent.provider.ts` with:
```ts
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
@@ -1749,7 +1749,7 @@ import { InjectionToken, Provider } from '@angular/core';
import { runLicenseCheck, LICENSE_PUBLIC_KEY } from '@cacheplane/licensing';
import { AgentTransport } from './agent.types';
-const PACKAGE_NAME = '@cacheplane/angular';
+const PACKAGE_NAME = '@cacheplane/langgraph';
// Wired up by the release pipeline — imported lazily to avoid a hard build-time
// dependency on package.json.
declare const __CACHEPLANE_AGENT_VERSION__: string | undefined;
@@ -1817,7 +1817,7 @@ export function provideAgent(config: AgentConfig): Provider {
- [ ] **Step 5: Add `@cacheplane/licensing` as a peer dependency**
-Edit `libs/agent/package.json` — add to the `peerDependencies` block:
+Edit `libs/langgraph/package.json` — add to the `peerDependencies` block:
```json
"@cacheplane/licensing": "^0.0.1",
@@ -1828,7 +1828,7 @@ Edit `libs/agent/package.json` — add to the `peerDependencies` block:
Run: `npx nx test agent`
Expected: PASS — all 4 tests green.
-**If tests fail:** stop and report the exact failure to the controller. Do not create a dev private-key fixture, do not alter `keypair.ts`, do not monkeypatch `sha512Async`, do not edit `tsconfig.base.json` or `libs/agent/tsconfig.json`.
+**If tests fail:** stop and report the exact failure to the controller. Do not create a dev private-key fixture, do not alter `keypair.ts`, do not monkeypatch `sha512Async`, do not edit `tsconfig.base.json` or `libs/langgraph/tsconfig.json`.
- [ ] **Step 7: Verify agent still builds**
@@ -1840,9 +1840,9 @@ Expected: build succeeds.
```bash
git add libs/licensing/src/testing.ts libs/licensing/tsconfig.lib.json \
tsconfig.base.json \
- libs/agent/tsconfig.json libs/agent/src/test-setup.ts \
- libs/agent/src/lib/agent.provider.ts libs/agent/src/lib/agent.provider.spec.ts \
- libs/agent/package.json
+ libs/langgraph/tsconfig.json libs/langgraph/src/test-setup.ts \
+ libs/langgraph/src/lib/agent.provider.ts libs/langgraph/src/lib/agent.provider.spec.ts \
+ libs/langgraph/package.json
git commit -m "feat(agent): run license check at provider init"
```
diff --git a/docs/superpowers/plans/2026-04-21-chat-runtime-decoupling-phase-1.md b/docs/superpowers/plans/2026-04-21-chat-runtime-decoupling-phase-1.md
index 0948a0595..ffc985cc4 100644
--- a/docs/superpowers/plans/2026-04-21-chat-runtime-decoupling-phase-1.md
+++ b/docs/superpowers/plans/2026-04-21-chat-runtime-decoupling-phase-1.md
@@ -2,9 +2,9 @@
> **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:** Introduce a runtime-neutral `ChatAgent` contract in `@cacheplane/chat`, adapt LangGraph to it, rename `@cacheplane/angular` → `@cacheplane/langgraph`, ship an optional `@cacheplane/ag-ui` adapter, and migrate the core chat primitives to consume `ChatAgent`. Website and docs aligned in lockstep.
+**Goal:** Introduce a runtime-neutral `ChatAgent` contract in `@cacheplane/chat`, adapt LangGraph to it, rename `@cacheplane/langgraph` → `@cacheplane/langgraph`, ship an optional `@cacheplane/ag-ui` adapter, and migrate the core chat primitives to consume `ChatAgent`. Website and docs aligned in lockstep.
-**Architecture:** Chat owns `ChatAgent` and its data types (AG-UI-shaped but chat-owned). Two adapter packages produce `ChatAgent`: `@cacheplane/langgraph` (wrapping LangGraph SDK, was `@cacheplane/angular`) and new `@cacheplane/ag-ui` (wrapping `@ag-ui/client`'s `AbstractAgent`). Chat primitives depend only on `ChatAgent`. Clean break pre-1.0.
+**Architecture:** Chat owns `ChatAgent` and its data types (AG-UI-shaped but chat-owned). Two adapter packages produce `ChatAgent`: `@cacheplane/langgraph` (wrapping LangGraph SDK, was `@cacheplane/langgraph`) and new `@cacheplane/ag-ui` (wrapping `@ag-ui/client`'s `AbstractAgent`). Chat primitives depend only on `ChatAgent`. Clean break pre-1.0.
**Tech Stack:** TypeScript, Angular 20+, Nx monorepo, Jest/Vitest for unit tests, RxJS (AG-UI adapter only), `@langchain/langgraph-sdk`, `@ag-ui/client`, `@ag-ui/core`.
@@ -16,9 +16,9 @@
- **A — Contract** (`@cacheplane/chat`): define `ChatAgent` and data types. Additive; no consumer changes yet.
- **B — Test harness** (`@cacheplane/chat`): `mockChatAgent()` and conformance suite. Enables TDD for migrations.
-- **C — LangGraph adapter** (`@cacheplane/angular`): add `toChatAgent()`. Additive; existing API untouched.
+- **C — LangGraph adapter** (`@cacheplane/langgraph`): add `toChatAgent()`. Additive; existing API untouched.
- **D — Primitive migration** (`@cacheplane/chat`): switch 6 primitives + `chat` composition from `AgentRef` to `ChatAgent`. Breaking.
-- **E — Package rename** (`@cacheplane/angular` → `@cacheplane/langgraph`). Breaking.
+- **E — Package rename** (`@cacheplane/langgraph` → `@cacheplane/langgraph`). Breaking.
- **F — AG-UI adapter** (new `@cacheplane/ag-ui`): reducer + signals wrapper for `AbstractAgent`.
- **G — Website & docs**: arch diagram, migration guide, capability matrix, AG-UI demo.
@@ -714,17 +714,17 @@ git commit -m "feat(chat): add ChatAgent conformance test suite"
---
-## Workstream C — LangGraph adapter (`toChatAgent()` in current `@cacheplane/angular`)
+## Workstream C — LangGraph adapter (`toChatAgent()` in current `@cacheplane/langgraph`)
### Task C1: Add `toChatAgent()` translation function
**Files:**
-- Create: `libs/agent/src/lib/to-chat-agent.ts`
-- Test: `libs/agent/src/lib/to-chat-agent.spec.ts`
+- Create: `libs/langgraph/src/lib/to-chat-agent.ts`
+- Test: `libs/langgraph/src/lib/to-chat-agent.spec.ts`
- [ ] **Step 1: Add peer/runtime dep on `@cacheplane/chat`**
-Modify `libs/agent/package.json` — add to `peerDependencies`:
+Modify `libs/langgraph/package.json` — add to `peerDependencies`:
```json
"@cacheplane/chat": "^0.0.1"
@@ -735,7 +735,7 @@ Note: this introduces a reverse coupling which we accept temporarily; the produc
- [ ] **Step 2: Write the failing test**
```ts
-// libs/agent/src/lib/to-chat-agent.spec.ts
+// libs/langgraph/src/lib/to-chat-agent.spec.ts
import { signal } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { HumanMessage, AIMessage } from '@langchain/core/messages';
@@ -837,14 +837,14 @@ describe('toChatAgent (LangGraph adapter)', () => {
- [ ] **Step 3: Run test to confirm failure**
```bash
-npx nx test agent --test-path-pattern=to-chat-agent
+npx nx test langgraph --test-path-pattern=to-chat-agent
```
Expected: FAIL.
- [ ] **Step 4: Implement `toChatAgent()`**
```ts
-// libs/agent/src/lib/to-chat-agent.ts
+// libs/langgraph/src/lib/to-chat-agent.ts
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
import { computed, Signal } from '@angular/core';
import type { BaseMessage } from '@langchain/core/messages';
@@ -996,14 +996,14 @@ Note: the exact `__resume__` key used to carry interrupt resumption through `Age
- [ ] **Step 5: Run tests and verify pass**
```bash
-npx nx test agent --test-path-pattern=to-chat-agent
+npx nx test langgraph --test-path-pattern=to-chat-agent
```
Expected: PASS (6 tests).
- [ ] **Step 6: Commit**
```bash
-git add libs/agent/src/lib/to-chat-agent.ts libs/agent/src/lib/to-chat-agent.spec.ts libs/agent/package.json
+git add libs/langgraph/src/lib/to-chat-agent.ts libs/langgraph/src/lib/to-chat-agent.spec.ts libs/langgraph/package.json
git commit -m "feat(agent): add toChatAgent() adapter to runtime-neutral ChatAgent contract"
```
@@ -1012,11 +1012,11 @@ git commit -m "feat(agent): add toChatAgent() adapter to runtime-neutral ChatAge
### Task C2: Export `toChatAgent` from agent package
**Files:**
-- Modify: `libs/agent/src/public-api.ts`
+- Modify: `libs/langgraph/src/public-api.ts`
- [ ] **Step 1: Add export**
-Append to `libs/agent/src/public-api.ts`:
+Append to `libs/langgraph/src/public-api.ts`:
```ts
// Chat adapter
@@ -1026,14 +1026,14 @@ export { toChatAgent } from './lib/to-chat-agent';
- [ ] **Step 2: Build**
```bash
-npx nx build agent
+npx nx build langgraph
```
Expected: SUCCESS.
- [ ] **Step 3: Commit**
```bash
-git add libs/agent/src/public-api.ts
+git add libs/langgraph/src/public-api.ts
git commit -m "feat(agent): export toChatAgent"
```
@@ -1042,12 +1042,12 @@ git commit -m "feat(agent): export toChatAgent"
### Task C3: Run conformance suite against `toChatAgent()`
**Files:**
-- Create: `libs/agent/src/lib/to-chat-agent.conformance.spec.ts`
+- Create: `libs/langgraph/src/lib/to-chat-agent.conformance.spec.ts`
- [ ] **Step 1: Write the conformance test**
```ts
-// libs/agent/src/lib/to-chat-agent.conformance.spec.ts
+// libs/langgraph/src/lib/to-chat-agent.conformance.spec.ts
import { TestBed } from '@angular/core/testing';
import { runChatAgentConformance } from '@cacheplane/chat';
import { toChatAgent } from './to-chat-agent';
@@ -1096,14 +1096,14 @@ runChatAgentConformance('toChatAgent', () => {
- [ ] **Step 2: Run test**
```bash
-npx nx test agent --test-path-pattern=to-chat-agent.conformance
+npx nx test langgraph --test-path-pattern=to-chat-agent.conformance
```
Expected: PASS (8 tests).
- [ ] **Step 3: Commit**
```bash
-git add libs/agent/src/lib/to-chat-agent.conformance.spec.ts
+git add libs/langgraph/src/lib/to-chat-agent.conformance.spec.ts
git commit -m "test(agent): verify toChatAgent satisfies ChatAgent conformance"
```
@@ -1136,7 +1136,7 @@ Replace imports and input in `chat-input.component.ts`:
```ts
import type { ChatAgent } from '@cacheplane/chat';
-// remove: import type { AgentRef } from '@cacheplane/angular';
+// remove: import type { AgentRef } from '@cacheplane/langgraph';
export function submitMessage(agent: ChatAgent, text: string): string | null {
const trimmed = text.trim();
@@ -1382,11 +1382,11 @@ npx nx lint chat
Then:
```bash
-rg --no-heading "@cacheplane/angular|AgentRef|BaseMessage" libs/chat/src/lib/primitives/chat-input libs/chat/src/lib/primitives/chat-messages libs/chat/src/lib/primitives/chat-tool-calls libs/chat/src/lib/primitives/chat-typing-indicator libs/chat/src/lib/primitives/chat-error libs/chat/src/lib/compositions/chat
+rg --no-heading "@cacheplane/langgraph|AgentRef|BaseMessage" libs/chat/src/lib/primitives/chat-input libs/chat/src/lib/primitives/chat-messages libs/chat/src/lib/primitives/chat-tool-calls libs/chat/src/lib/primitives/chat-typing-indicator libs/chat/src/lib/primitives/chat-error libs/chat/src/lib/compositions/chat
```
Expected: no matches.
-If any match is found, extract it into a follow-up task before proceeding. Interrupt, subagents, timeline, debug, generative-ui primitives are out of Phase 1 and may still import `@cacheplane/angular` — document this in the composition's file header with a `TODO(phase-2)` or `TODO(phase-3)` comment.
+If any match is found, extract it into a follow-up task before proceeding. Interrupt, subagents, timeline, debug, generative-ui primitives are out of Phase 1 and may still import `@cacheplane/langgraph` — document this in the composition's file header with a `TODO(phase-2)` or `TODO(phase-3)` comment.
- [ ] **Step 2: Full chat test run**
@@ -1411,15 +1411,15 @@ git commit -m "chore(chat): mark remaining primitives as phase-2/phase-3 migrati
---
-## Workstream E — Rename `@cacheplane/angular` → `@cacheplane/langgraph`
+## Workstream E — Rename `@cacheplane/langgraph` → `@cacheplane/langgraph`
### Task E1: Rename the package identifier
**Files:**
-- Modify: `libs/agent/package.json`
-- Modify: `libs/agent/project.json`
-- Modify: `libs/agent/ng-package.json`
-- Modify: `libs/agent/README.md`
+- Modify: `libs/langgraph/package.json`
+- Modify: `libs/langgraph/project.json`
+- Modify: `libs/langgraph/ng-package.json`
+- Modify: `libs/langgraph/README.md`
- [ ] **Step 1: Update `package.json`** — set `"name": "@cacheplane/langgraph"`.
@@ -1427,7 +1427,7 @@ git commit -m "chore(chat): mark remaining primitives as phase-2/phase-3 migrati
- [ ] **Step 3: Update `ng-package.json`** — adjust `dest` path to match renamed package.
-- [ ] **Step 4: Update `libs/agent/src/lib/agent.provider.ts`**
+- [ ] **Step 4: Update `libs/langgraph/src/lib/agent.provider.ts`**
```ts
const PACKAGE_NAME = '@cacheplane/langgraph';
@@ -1435,15 +1435,15 @@ const PACKAGE_NAME = '@cacheplane/langgraph';
And update `__CACHEPLANE_AGENT_VERSION__` define if present in `vite.config.mts` to `__CACHEPLANE_LANGGRAPH_VERSION__`.
-- [ ] **Step 5: Update `libs/agent/README.md`** — rename heading and all `@cacheplane/angular` references.
+- [ ] **Step 5: Update `libs/langgraph/README.md`** — rename heading and all `@cacheplane/langgraph` references.
-- [ ] **Step 6: Do NOT rename the directory** (`libs/agent/`) in this task — Nx project paths are stable. Directory rename, if desired, is a separate cleanup follow-up.
+- [ ] **Step 6: Do NOT rename the directory** (`libs/langgraph/`) in this task — Nx project paths are stable. Directory rename, if desired, is a separate cleanup follow-up.
- [ ] **Step 7: Commit**
```bash
-git add libs/agent/package.json libs/agent/project.json libs/agent/ng-package.json libs/agent/README.md libs/agent/src/lib/agent.provider.ts
-git commit -m "refactor(langgraph): rename @cacheplane/angular to @cacheplane/langgraph"
+git add libs/langgraph/package.json libs/langgraph/project.json libs/langgraph/ng-package.json libs/langgraph/README.md libs/langgraph/src/lib/agent.provider.ts
+git commit -m "refactor(langgraph): rename @cacheplane/langgraph to @cacheplane/langgraph"
```
---
@@ -1457,50 +1457,50 @@ git commit -m "refactor(langgraph): rename @cacheplane/angular to @cacheplane/la
Replace:
```json
-"@cacheplane/angular": ["libs/agent/src/public-api.ts"]
+"@cacheplane/langgraph": ["libs/langgraph/src/public-api.ts"]
```
with:
```json
-"@cacheplane/langgraph": ["libs/agent/src/public-api.ts"]
+"@cacheplane/langgraph": ["libs/langgraph/src/public-api.ts"]
```
-Keep `@cacheplane/angular` as an alias pointing to the same entry for one migration tick, OR remove immediately for a clean break (preferred). Choose clean break:
+Keep `@cacheplane/langgraph` as an alias pointing to the same entry for one migration tick, OR remove immediately for a clean break (preferred). Choose clean break:
```bash
-rg -n '"@cacheplane/angular"' tsconfig.base.json
+rg -n '"@cacheplane/langgraph"' tsconfig.base.json
```
- [ ] **Step 2: Commit**
```bash
git add tsconfig.base.json
-git commit -m "build: retarget @cacheplane/angular alias to @cacheplane/langgraph"
+git commit -m "build: retarget @cacheplane/langgraph alias to @cacheplane/langgraph"
```
---
### Task E3: Update all internal imports
-**Files:** everything that imports `@cacheplane/angular`.
+**Files:** everything that imports `@cacheplane/langgraph`.
- [ ] **Step 1: Find all internal callers**
```bash
-rg -l "@cacheplane/angular" libs apps
+rg -l "@cacheplane/langgraph" libs apps
```
- [ ] **Step 2: Perform the replacement** (manually or via sed with review)
-For each matching file, replace `@cacheplane/angular` with `@cacheplane/langgraph`:
+For each matching file, replace `@cacheplane/langgraph` with `@cacheplane/langgraph`:
```bash
-rg -l "@cacheplane/angular" libs apps | xargs sed -i '' 's|@cacheplane/angular|@cacheplane/langgraph|g'
+rg -l "@cacheplane/langgraph" libs apps | xargs sed -i '' 's|@cacheplane/langgraph|@cacheplane/langgraph|g'
```
- [ ] **Step 3: Verify no matches remain**
```bash
-rg "@cacheplane/angular" libs apps
+rg "@cacheplane/langgraph" libs apps
```
Expected: no matches (outside of spec/plan docs and historical CHANGELOG entries).
@@ -1522,7 +1522,7 @@ Expected: PASS.
```bash
git add -u
-git commit -m "refactor: replace @cacheplane/angular imports with @cacheplane/langgraph"
+git commit -m "refactor: replace @cacheplane/langgraph imports with @cacheplane/langgraph"
```
---
@@ -1532,7 +1532,7 @@ git commit -m "refactor: replace @cacheplane/angular imports with @cacheplane/la
**Files:**
- Modify: `libs/licensing/README.md`
-- [ ] **Step 1: Replace `@cacheplane/angular` mentions with `@cacheplane/langgraph`** in the README.
+- [ ] **Step 1: Replace `@cacheplane/langgraph` mentions with `@cacheplane/langgraph`** in the README.
- [ ] **Step 2: Note** — licensing specs test arbitrary package name strings and do not need code changes.
@@ -1555,7 +1555,7 @@ git commit -m "docs(licensing): update consumer list to @cacheplane/langgraph"
Create or append to `docs/migrations/2026-04-21-cacheplane-angular-to-langgraph.md`:
```markdown
-# Migration: `@cacheplane/angular` → `@cacheplane/langgraph`
+# Migration: `@cacheplane/langgraph` → `@cacheplane/langgraph`
**Date:** 2026-04-21
@@ -1563,13 +1563,13 @@ The LangGraph adapter package has been renamed. Replace all imports:
```ts
// before
-import { agent, provideAgent } from '@cacheplane/angular';
+import { agent, provideAgent } from '@cacheplane/langgraph';
// after
import { agent, provideAgent } from '@cacheplane/langgraph';
```
-`@cacheplane/angular` is no longer published. The rename reflects the package's actual role (LangGraph SDK adapter) and makes room for additional framework-level packages.
+`@cacheplane/langgraph` is no longer published. The rename reflects the package's actual role (LangGraph SDK adapter) and makes room for additional framework-level packages.
In the same release, chat primitives migrated from `AgentRef` to the runtime-neutral `ChatAgent` contract. Use `toChatAgent(agentRef)` to adapt:
@@ -1587,7 +1587,7 @@ const chatAgent = toChatAgent(ref);
```bash
git add docs/migrations/
-git commit -m "docs: add migration guide for @cacheplane/angular rename"
+git commit -m "docs: add migration guide for @cacheplane/langgraph rename"
```
---
@@ -2242,7 +2242,7 @@ git commit -m "docs(ag-ui): add README"
- [ ] **Step 1: Locate existing architecture pages**
```bash
-rg -l "architecture|@cacheplane/angular" apps/website/src
+rg -l "architecture|@cacheplane/langgraph" apps/website/src
```
- [ ] **Step 2: Replace or add the three-box diagram** (see spec) as SVG or Mermaid in the architecture page. Include the three packages (chat / langgraph / ag-ui) and the `ChatAgent` contract in the center.
@@ -2429,5 +2429,5 @@ Description: link to spec and plan; summarize workstreams A–G; include migrati
- Every file path above is absolute to repo root. Don't introduce new path aliases beyond the one `@cacheplane/ag-ui` entry.
- `toChatAgent()` must be called inside an Angular injection context because it uses `computed()`; tests use `TestBed.runInInjectionContext`.
- Do not modify the not-yet-migrated primitives (`chat-interrupt`, `chat-subagents`, `chat-timeline`, `chat-debug`, `chat-generative-ui`, `chat-thread-list`) in Phase 1 — they continue to import from `@cacheplane/langgraph` until Phase 2/3.
-- The `@cacheplane/angular` → `@cacheplane/langgraph` rename does not rename the `libs/agent/` directory; that's a mechanical cleanup deferred to keep this PR reviewable.
+- The `@cacheplane/langgraph` → `@cacheplane/langgraph` rename does not rename the `libs/langgraph/` directory; that's a mechanical cleanup deferred to keep this PR reviewable.
- If `@ag-ui/client` / `@ag-ui/core` versions drift during implementation, pin the adapter to the versions you tested against and update the `peerDependencies` range accordingly.
diff --git a/docs/superpowers/specs/2026-03-19-licensing-model.md b/docs/superpowers/specs/2026-03-19-licensing-model.md
index 549269723..89f456d44 100644
--- a/docs/superpowers/specs/2026-03-19-licensing-model.md
+++ b/docs/superpowers/specs/2026-03-19-licensing-model.md
@@ -148,7 +148,7 @@ Source-available licensing. Free for noncommercial use under PolyForm Noncommerc
### README license section
```markdown
-`@cacheplane/angular` is source-available software dual-licensed:
+`@cacheplane/langgraph` is source-available software dual-licensed:
- **PolyForm Noncommercial 1.0.0** — free for noncommercial use. See LICENSE.
- **Angular Agent Framework Commercial License** — required for any for-profit or revenue-generating use. See LICENSE-COMMERCIAL and COMMERCIAL.md.
diff --git a/docs/superpowers/specs/2026-04-03-docs-mode-redesign.md b/docs/superpowers/specs/2026-04-03-docs-mode-redesign.md
index 2f15aa881..b37052c0e 100644
--- a/docs/superpowers/specs/2026-04-03-docs-mode-redesign.md
+++ b/docs/superpowers/specs/2026-04-03-docs-mode-redesign.md
@@ -58,7 +58,7 @@ Set up `provideAgent()` in your app config:
```typescript
// app.config.ts
-import { provideAgent } from '@cacheplane/angular';
+import { provideAgent } from '@cacheplane/langgraph';
export const appConfig: ApplicationConfig = {
providers: [provideAgent({ apiUrl: '...' })],
@@ -90,7 +90,7 @@ Never expose your LangSmith API key in client-side code.
Add real-time LLM streaming to this Angular component using
-`agent()` from `@cacheplane/angular`. Configure
+`agent()` from `@cacheplane/langgraph`. Configure
`provideAgent({ apiUrl })` in the app config, then call
`stream.submit()` to send messages. Bind `stream.messages()` in the
template using `@for` — all Signals, no subscriptions needed.
diff --git a/docs/superpowers/specs/2026-04-03-langsmith-deployment-angular-runtime-design.md b/docs/superpowers/specs/2026-04-03-langsmith-deployment-angular-runtime-design.md
index efdae8430..465bc051c 100644
--- a/docs/superpowers/specs/2026-04-03-langsmith-deployment-angular-runtime-design.md
+++ b/docs/superpowers/specs/2026-04-03-langsmith-deployment-angular-runtime-design.md
@@ -6,7 +6,7 @@ The cockpit's streaming example uses a hand-rolled `EventSource` service instead
## Goal
-Rewrite the Angular streaming example to use `agent()` from `@cacheplane/angular`. Deploy the LangGraph backend to LangGraph Cloud via CI. Make the cockpit's Run mode show a real working streaming chat powered by the library.
+Rewrite the Angular streaming example to use `agent()` from `@cacheplane/langgraph`. Deploy the LangGraph backend to LangGraph Cloud via CI. Make the cockpit's Run mode show a real working streaming chat powered by the library.
## Architecture
@@ -30,7 +30,7 @@ Angular Component
- `cockpit/langgraph/streaming/angular/src/environments/environment.development.ts` — Local dev URL (`http://localhost:8000`).
**Dependencies added to cockpit Angular app:**
-- `@cacheplane/angular` (the library)
+- `@cacheplane/langgraph` (the library)
- `@langchain/langgraph-sdk` (peer dep)
- `@langchain/core` (peer dep)
diff --git a/docs/superpowers/specs/2026-04-04-chat-component-library-design.md b/docs/superpowers/specs/2026-04-04-chat-component-library-design.md
index 377b8b665..6ab2f358f 100644
--- a/docs/superpowers/specs/2026-04-04-chat-component-library-design.md
+++ b/docs/superpowers/specs/2026-04-04-chat-component-library-design.md
@@ -13,7 +13,7 @@ Build a rich, extensible Angular chat component library for LangGraph, LangChain
### Deliverables
1. **`@cacheplane/render`** (`libs/render`) — Angular rendering layer for `@json-render/core`
-2. **`@cacheplane/chat`** (`libs/chat`) — Chat UI component library built on `@cacheplane/angular`
+2. **`@cacheplane/chat`** (`libs/chat`) — Chat UI component library built on `@cacheplane/langgraph`
3. **Cockpit integration** — Update capability examples to consume `@cacheplane/chat`
### Architecture: Layered Stack
@@ -27,7 +27,7 @@ Build a rich, extensible Angular chat component library for LangGraph, LangChain
↓
cockpit examples (standalone Angular apps, independently deployed)
↑
-@cacheplane/angular (peer dep — existing library)
+@cacheplane/langgraph (peer dep — existing library)
```
---
@@ -100,7 +100,7 @@ Angular chat component library providing headless primitives and prebuilt Tailwi
### Peer Dependencies
- `@cacheplane/render`
-- `@cacheplane/angular`
+- `@cacheplane/langgraph`
- `@angular/core`
- `@angular/common`
- `@langchain/core` (for `BaseMessage` types)
diff --git a/docs/superpowers/specs/2026-04-04-expanded-introduction-design.md b/docs/superpowers/specs/2026-04-04-expanded-introduction-design.md
index ca6932315..69f189f44 100644
--- a/docs/superpowers/specs/2026-04-04-expanded-introduction-design.md
+++ b/docs/superpowers/specs/2026-04-04-expanded-introduction-design.md
@@ -33,7 +33,7 @@ The introduction becomes a multi-section tutorial covering the complete workflow
- Test in LangGraph Studio
### Section 4: Connect with Angular
-- Install agent: `npm install @cacheplane/angular`
+- Install agent: `npm install @cacheplane/langgraph`
- Configure provider in `app.config.ts`
- Create a chat component with `agent()`
- Show TypeScript + Template code with Tabs
diff --git a/docs/superpowers/specs/2026-04-05-narrative-redesign.md b/docs/superpowers/specs/2026-04-05-narrative-redesign.md
index 0c477bb69..ecbfb7582 100644
--- a/docs/superpowers/specs/2026-04-05-narrative-redesign.md
+++ b/docs/superpowers/specs/2026-04-05-narrative-redesign.md
@@ -99,7 +99,7 @@ Dark card (`#1a1b26`), centered, width 220px. Shows "LangGraph Cloud" label and
### Layer cards
Three cards with the established color scheme:
-- `@cacheplane/angular` — blue (`#004090`), tag "Primitives"
+- `@cacheplane/langgraph` — blue (`#004090`), tag "Primitives"
- `@cacheplane/chat` — purple (`#5a00c8`), tag "UI Layer"
- `@cacheplane/render` — green (`#1a7a40`), tag "Gen UI"
diff --git a/docs/superpowers/specs/2026-04-06-fullstack-section-redesign.md b/docs/superpowers/specs/2026-04-06-fullstack-section-redesign.md
index b4aa0048e..2db79e716 100644
--- a/docs/superpowers/specs/2026-04-06-fullstack-section-redesign.md
+++ b/docs/superpowers/specs/2026-04-06-fullstack-section-redesign.md
@@ -25,7 +25,7 @@ Primary reader: Engineering managers and CTOs evaluating whether to purchase an
### Copy per layer
-**@cacheplane/angular (Primitives)**
+**@cacheplane/langgraph (Primitives)**
- Outcome: "Ship streaming agents without building the plumbing."
- Problem: Wiring SSE into Angular requires weeks of zone patching, manual subscription management, and custom thread-persistence code — most of which breaks under load or after a page refresh.
- Solution: agent() gives your team production-ready streaming, thread persistence, interrupt handling, and a deterministic test transport on day one.
diff --git a/docs/superpowers/specs/2026-04-09-a2ui-cockpit-design.md b/docs/superpowers/specs/2026-04-09-a2ui-cockpit-design.md
index 4ee42410b..f609d2c32 100644
--- a/docs/superpowers/specs/2026-04-09-a2ui-cockpit-design.md
+++ b/docs/superpowers/specs/2026-04-09-a2ui-cockpit-design.md
@@ -92,7 +92,7 @@ cockpit/chat/a2ui/
```typescript
import { Component } from '@angular/core';
import { ChatComponent, a2uiBasicCatalog } from '@cacheplane/chat';
-import { agent } from '@cacheplane/angular';
+import { agent } from '@cacheplane/langgraph';
import { environment } from '../environments/environment';
@Component({
diff --git a/docs/superpowers/specs/2026-04-09-library-consolidation-design.md b/docs/superpowers/specs/2026-04-09-library-consolidation-design.md
index 153d37e32..5ef636d38 100644
--- a/docs/superpowers/specs/2026-04-09-library-consolidation-design.md
+++ b/docs/superpowers/specs/2026-04-09-library-consolidation-design.md
@@ -5,7 +5,7 @@
## Overview
-`@cacheplane/stream-resource` was merged into `@cacheplane/angular` during the rebrand (PR #39). The source code, Nx project, and tsconfig path mapping are already gone. What remains is the empty `libs/stream-resource/` directory and stale references across README, LICENSE-COMMERCIAL, and cockpit docs. This spec covers removing the directory and updating all references so the monorepo reflects 3 libraries (angular, render, chat) — not 4.
+`@cacheplane/stream-resource` was merged into `@cacheplane/langgraph` during the rebrand (PR #39). The source code, Nx project, and tsconfig path mapping are already gone. What remains is the empty `libs/stream-resource/` directory and stale references across README, LICENSE-COMMERCIAL, and cockpit docs. This spec covers removing the directory and updating all references so the monorepo reflects 3 libraries (angular, render, chat) — not 4.
## Scope
diff --git a/docs/superpowers/specs/2026-04-09-library-landing-pages-design.md b/docs/superpowers/specs/2026-04-09-library-landing-pages-design.md
index d32ca8b86..a973d7576 100644
--- a/docs/superpowers/specs/2026-04-09-library-landing-pages-design.md
+++ b/docs/superpowers/specs/2026-04-09-library-landing-pages-design.md
@@ -35,7 +35,7 @@ Subtitle: "Everything your Angular team needs to ship AI agents to production."
3-card grid (responsive: 1 col mobile, 3 col desktop):
Card 1 — Angular (blue #004090)
- Package: @cacheplane/angular
+ Package: @cacheplane/langgraph
One-liner: "Signal-native streaming for LangGraph agents"
Chips: agent(), provideAgent(), interrupt(), MockStreamTransport
CTA: "Explore Angular →" → /angular
@@ -105,7 +105,7 @@ Each page uses the same ambient gradient blob pattern as the home page and pilot
### 2a. `/angular` — Agent Streaming Core
**Hero:**
-- Eyebrow: `@cacheplane/angular`
+- Eyebrow: `@cacheplane/langgraph`
- Headline: "Ship LangGraph agents in Angular — without building the plumbing"
- Subheadline: "Signal-native streaming, thread persistence, interrupts, and deterministic testing. The complete agent primitive layer for Angular 20+."
- Primary CTA: "Download the Guide" → `/whitepapers/angular.pdf`
@@ -129,11 +129,11 @@ Each page uses the same ambient gradient blob pattern as the home page and pilot
- Minimal `agent()` setup (8-10 lines)
- `provideAgent()` configuration with thread persistence (10-12 lines)
-**ComparisonTable: "LangGraph Angular SDK vs @cacheplane/angular"**
+**ComparisonTable: "LangGraph Angular SDK vs @cacheplane/langgraph"**
Head-to-head against the recently released official LangGraph Angular SDK:
-| Capability | LangGraph Angular SDK | @cacheplane/angular |
+| Capability | LangGraph Angular SDK | @cacheplane/langgraph |
|---|---|---|
| SSE streaming | Manual wiring | Signal-native via agent() |
| State management | Custom signals | Built-in reactive state |
@@ -314,7 +314,7 @@ Create 3 new drip campaign files, one per library whitepaper. Each follows the s
**Angular drip (`emails/drip-angular-followup.ts`):**
- Day 2: "Did you read Chapter 2 on the agent() API?" — highlights the core API, links to docs
-- Day 5: "LangGraph Angular SDK vs @cacheplane/angular" — comparison narrative, links to `/angular`
+- Day 5: "LangGraph Angular SDK vs @cacheplane/langgraph" — comparison narrative, links to `/angular`
- Day 10: "The pilot program includes hands-on integration" — pilot program intro, links to `/pilot-to-prod`
- Day 20: "Ready to ship your LangGraph agent? Let's talk." — soft sales outreach
@@ -412,7 +412,7 @@ Get Started
## 7. Follow-Up Tasks (Out of Scope)
-- **Consolidate @cacheplane/stream-resource into @cacheplane/angular** — merge the base primitives library into the agent library so there are truly 3 libraries, not 4. Tracked as a separate task.
+- **Consolidate @cacheplane/stream-resource into @cacheplane/langgraph** — merge the base primitives library into the agent library so there are truly 3 libraries, not 4. Tracked as a separate task.
- **Editorial review of generated whitepaper content** — the pipeline generates content via Claude API; human review of tone, accuracy, and messaging is a follow-up.
---
diff --git a/docs/superpowers/specs/2026-04-10-homepage-narrative-funnel-design.md b/docs/superpowers/specs/2026-04-10-homepage-narrative-funnel-design.md
index b0f3f8334..4a102d5b5 100644
--- a/docs/superpowers/specs/2026-04-10-homepage-narrative-funnel-design.md
+++ b/docs/superpowers/specs/2026-04-10-homepage-narrative-funnel-design.md
@@ -101,7 +101,7 @@ Each card structure:
**Card 1 — Agent** (color: `tokens.colors.accent` / `#004090`, rgb: `0,64,144`):
- Tag: `AGENT`
-- Package: `@cacheplane/angular`
+- Package: `@cacheplane/langgraph`
- Headline: "The reactive bridge to LangGraph"
- Value prop: "Signal-native streaming connects your Angular templates directly to LangGraph agent state. Interrupts, persistence, time-travel, and branch history — every LangGraph feature has a first-class Angular API. Test deterministically with MockAgentTransport."
- Pills: `Angular Signals` · `LangGraph Cloud` · `MockAgentTransport`
diff --git a/docs/superpowers/specs/2026-04-13-landing-page-alignment-design.md b/docs/superpowers/specs/2026-04-13-landing-page-alignment-design.md
index 665d5d462..89a8fa335 100644
--- a/docs/superpowers/specs/2026-04-13-landing-page-alignment-design.md
+++ b/docs/superpowers/specs/2026-04-13-landing-page-alignment-design.md
@@ -203,8 +203,8 @@ Sibling data is defined inline in each StackSiblings component (not imported fro
| Page | Card 1 | Card 2 |
|---|---|---|
| `/angular` | Render (green, `@cacheplane/render`, "Agents that render UI — on open standards") | Chat (purple, `@cacheplane/chat`, "Production chat UI in days, not sprints") |
-| `/render` | Agent (blue, `@cacheplane/angular`, "The reactive bridge to LangGraph") | Chat (purple, `@cacheplane/chat`, "Production chat UI in days, not sprints") |
-| `/chat` | Agent (blue, `@cacheplane/angular`, "The reactive bridge to LangGraph") | Render (green, `@cacheplane/render`, "Agents that render UI — on open standards") |
+| `/render` | Agent (blue, `@cacheplane/langgraph`, "The reactive bridge to LangGraph") | Chat (purple, `@cacheplane/chat`, "Production chat UI in days, not sprints") |
+| `/chat` | Agent (blue, `@cacheplane/langgraph`, "The reactive bridge to LangGraph") | Render (green, `@cacheplane/render`, "Agents that render UI — on open standards") |
### Mobile Breakpoint
diff --git a/docs/superpowers/specs/2026-04-17-v1-roadmap-design.md b/docs/superpowers/specs/2026-04-17-v1-roadmap-design.md
index a34c9ad49..1666f64d8 100644
--- a/docs/superpowers/specs/2026-04-17-v1-roadmap-design.md
+++ b/docs/superpowers/specs/2026-04-17-v1-roadmap-design.md
@@ -1,4 +1,4 @@
-# v1 Roadmap — `@cacheplane/angular`, `@cacheplane/render`, `@cacheplane/chat`
+# v1 Roadmap — `@cacheplane/langgraph`, `@cacheplane/render`, `@cacheplane/chat`
**Date:** 2026-04-17
**Status:** Approved design — ready for implementation plan
@@ -7,7 +7,7 @@
Publish three production-ready npm packages at version `1.0.0`:
-- `@cacheplane/angular` (source in `libs/agent`)
+- `@cacheplane/langgraph` (source in `libs/langgraph`)
- `@cacheplane/render` (source in `libs/render`)
- `@cacheplane/chat` (source in `libs/chat`)
@@ -50,7 +50,7 @@ Each library follows the same checklist. Order: agent → render → chat.
- **License check wired in** — init-time offline signature verification + nag UX (see §3)
- **Cockpit examples updated** — any API change lands with matching cockpit update in the same PR
-### `@cacheplane/angular` (source: `libs/agent`)
+### `@cacheplane/langgraph` (source: `libs/langgraph`)
- Primary API: agent primitives, signals, LangGraph SDK integration
- Most mature of the three; stabilization effort is reviewing + formalizing existing API
@@ -75,7 +75,7 @@ Each library follows the same checklist. Order: agent → render → chat.
Goal: `nx release` (single command) → tagged commits, changelogs, npm publish with provenance.
- **Nx Release** configured with per-package semver and independent version lines
-- **Tag format:** `@v` (e.g., `@cacheplane/angular@v1.0.0`)
+- **Tag format:** `@v` (e.g., `@cacheplane/langgraph@v1.0.0`)
- **Changelog:** Keep-a-Changelog format, one `CHANGELOG.md` per library
- **Conventional commits** enforced in CI (commitlint) to drive changelog generation
- **npm publish with provenance** (`--provenance` flag, GitHub OIDC)
diff --git a/docs/superpowers/specs/2026-04-21-chat-runtime-decoupling-design.md b/docs/superpowers/specs/2026-04-21-chat-runtime-decoupling-design.md
index 1518ffe30..707cc4826 100644
--- a/docs/superpowers/specs/2026-04-21-chat-runtime-decoupling-design.md
+++ b/docs/superpowers/specs/2026-04-21-chat-runtime-decoupling-design.md
@@ -6,9 +6,9 @@
## Problem
-`@cacheplane/chat` imports `AgentRef`, `SubagentStreamRef`, `Interrupt`, `ThreadState`, `ToolCallWithResult`, and `ResourceStatus` from `@cacheplane/angular` across nearly every primitive and composition. `@cacheplane/angular` is a LangGraph SDK adapter — its types are LangGraph-shaped (`BaseMessage` from `@langchain/core`, `Interrupt`/`ThreadState` from `@langchain/langgraph-sdk`). Consequently, chat's public API is LangGraph-specific: a user cannot drive chat primitives from CopilotKit's AG-UI runtime, Mastra, CrewAI, Microsoft Agent Framework, or a custom backend without re-implementing `AgentRef`.
+`@cacheplane/chat` imports `AgentRef`, `SubagentStreamRef`, `Interrupt`, `ThreadState`, `ToolCallWithResult`, and `ResourceStatus` from `@cacheplane/langgraph` across nearly every primitive and composition. `@cacheplane/langgraph` is a LangGraph SDK adapter — its types are LangGraph-shaped (`BaseMessage` from `@langchain/core`, `Interrupt`/`ThreadState` from `@langchain/langgraph-sdk`). Consequently, chat's public API is LangGraph-specific: a user cannot drive chat primitives from CopilotKit's AG-UI runtime, Mastra, CrewAI, Microsoft Agent Framework, or a custom backend without re-implementing `AgentRef`.
-`@cacheplane/render` has **no** dependency on `@cacheplane/angular` and is already decoupled from the agent runtime (though it remains coupled to the Angular framework — out of scope for this spec).
+`@cacheplane/render` has **no** dependency on `@cacheplane/langgraph` and is already decoupled from the agent runtime (though it remains coupled to the Angular framework — out of scope for this spec).
## Goal
@@ -31,7 +31,7 @@ Chat primitives accept a runtime-neutral `ChatAgent` contract. Existing LangGrap
│ │ │
│ @cacheplane/langgraph @cacheplane/ag-ui
│ (renamed from (new, optional)
- │ @cacheplane/angular)
+ │ @cacheplane/langgraph)
│ │ │
└── user code ▼ ▼
@langchain/* @ag-ui/client
@@ -41,7 +41,7 @@ Chat primitives accept a runtime-neutral `ChatAgent` contract. Existing LangGrap
### Package roles
- **`@cacheplane/chat`** — owns `ChatAgent` and all neutral data types; owns all primitives and compositions. Peer deps: Angular, `@cacheplane/render`, `@cacheplane/licensing`, `@cacheplane/a2ui`, `@cacheplane/partial-json`. **No dependency on any agent runtime.**
-- **`@cacheplane/langgraph`** — renamed from `@cacheplane/angular`. Wraps the LangGraph SDK; exports `agent()`, `AgentRef`, transports, and `toChatAgent(agentRef) → ChatAgent`. Keeps today's surface for users who want raw LangGraph access plus the adapter for chat.
+- **`@cacheplane/langgraph`** — renamed from `@cacheplane/langgraph`. Wraps the LangGraph SDK; exports `agent()`, `AgentRef`, transports, and `toChatAgent(agentRef) → ChatAgent`. Keeps today's surface for users who want raw LangGraph access plus the adapter for chat.
- **`@cacheplane/ag-ui`** — new. Wraps `@ag-ui/client`'s `AbstractAgent` Observable into `ChatAgent` signals. Exports `toChatAgent(agent: AbstractAgent)` and a convenience `provideAgUiAgent({ url, agentId })`.
- **User-written adapter** — any custom backend (Vercel AI SDK direct, homegrown SSE, etc.) implements `ChatAgent` without a library.
@@ -152,9 +152,9 @@ Reducer is a plain function so it can be unit-tested without Angular.
All libs are currently at 0.0.1. A breaking change is acceptable.
-- Rename `@cacheplane/angular` → `@cacheplane/langgraph`. Update all internal consumers.
+- Rename `@cacheplane/langgraph` → `@cacheplane/langgraph`. Update all internal consumers.
- Primitive inputs change from `AgentRef`-flavored types to `ChatAgent` types. No transitional overloads.
-- Publish a tombstone `@cacheplane/angular` (or README redirect) pointing to the new package.
+- Publish a tombstone `@cacheplane/langgraph` (or README redirect) pointing to the new package.
- CHANGELOG entries per phase documenting the import path and shape changes.
- Website and docs updated in lockstep with each phase (see Website & docs section).
@@ -166,7 +166,7 @@ Each phase ships as its own spec → implementation plan → PR set.
Primitives migrated: `provide-chat`, `chat-input`, `chat-messages`, `chat-tool-calls`, `chat-error`, `chat-typing-indicator`, `chat` composition (core path).
- Introduce `ChatAgent`, `ChatMessage`, `ChatToolCall`, `ChatSubmitInput`, `ChatStatus` in `@cacheplane/chat`.
-- Rename `@cacheplane/angular` → `@cacheplane/langgraph`; add `toChatAgent()`.
+- Rename `@cacheplane/langgraph` → `@cacheplane/langgraph`; add `toChatAgent()`.
- Ship `@cacheplane/ag-ui` covering lifecycle, message, tool-call, state events.
- Update affected tests; introduce `mockChatAgent()` helper under `@cacheplane/chat/testing`.
@@ -188,7 +188,7 @@ Treat documentation as a first-class deliverable of each phase, not a follow-up:
- **Architecture diagram** (the three-box diagram above) replaces any existing runtime-coupled diagram on the website.
- **Getting started** guides bifurcate: LangGraph path (`@cacheplane/langgraph`) and AG-UI path (`@cacheplane/ag-ui`), both ending in `toChatAgent()` fed to ``.
- **Capability matrix** — a table listing each primitive/composition and which runtimes it supports (core / interrupts / subagents / history).
-- **Migration guide** — dedicated page for the `@cacheplane/angular` → `@cacheplane/langgraph` rename plus primitive input changes.
+- **Migration guide** — dedicated page for the `@cacheplane/langgraph` → `@cacheplane/langgraph` rename plus primitive input changes.
- **API reference** — `ChatAgent`, `ChatMessage`, etc. documented with examples for each adapter.
- **Examples repo / apps/website demos** — at least one AG-UI-driven demo (e.g., against a Mastra or CopilotKit backend) to prove the decoupling end-to-end.
@@ -205,11 +205,11 @@ Website updates ship with each phase's PR; no phase is considered complete until
- **AG-UI protocol churn.** Draft events (MetaEvent, extended run events) are moving. Mitigate by pinning Phase 1 to stable core events only; Phase 2 accepts the risk of AG-UI adapter churn without affecting chat or LangGraph adapter.
- **Interrupt semantic divergence.** AG-UI's interrupt model is not 1:1 with LangGraph's. The `ChatInterrupt` shape must be broad enough for both; Phase 2 spec locks this down.
-- **Naming break.** `@cacheplane/angular` → `@cacheplane/langgraph` forces import updates. Acceptable pre-1.0; migration guide mitigates.
+- **Naming break.** `@cacheplane/langgraph` → `@cacheplane/langgraph` forces import updates. Acceptable pre-1.0; migration guide mitigates.
- **Hidden LangGraph assumptions in primitives.** Some primitives may rely on `BaseMessage`-specific fields (e.g., `additional_kwargs`). Phase 1 audit surfaces these and folds them into `ChatMessage` or explicit escape hatches.
## Open questions for reviewer
-1. Confirm the `@cacheplane/angular` → `@cacheplane/langgraph` rename (vs. keeping the old name).
+1. Confirm the `@cacheplane/langgraph` → `@cacheplane/langgraph` rename (vs. keeping the old name).
2. Confirm that history / time-travel stays LangGraph-only and is not part of the contract.
3. Any appetite for a `@cacheplane/vercel-ai` or `@cacheplane/mastra` adapter in a later phase, or is AG-UI enough for non-LangGraph coverage?
diff --git a/libs/chat/package.json b/libs/chat/package.json
index e4627a4f7..1795f20a6 100644
--- a/libs/chat/package.json
+++ b/libs/chat/package.json
@@ -10,7 +10,7 @@
"@cacheplane/render": "^0.0.1",
"@cacheplane/a2ui": "^0.0.1",
"@cacheplane/partial-json": "^0.0.1",
- "@cacheplane/angular": "^0.0.1",
+ "@cacheplane/langgraph": "^0.0.1",
"@json-render/core": "^0.16.0",
"@langchain/core": "^1.1.33",
"@langchain/langgraph-sdk": "^1.7.4",
diff --git a/libs/chat/src/lib/compositions/chat-debug/chat-debug.component.ts b/libs/chat/src/lib/compositions/chat-debug/chat-debug.component.ts
index d01e6e5e9..ba1ecc781 100644
--- a/libs/chat/src/lib/compositions/chat-debug/chat-debug.component.ts
+++ b/libs/chat/src/lib/compositions/chat-debug/chat-debug.component.ts
@@ -11,7 +11,7 @@ import {
ChangeDetectionStrategy,
} from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
-import type { AgentRef } from '@cacheplane/angular';
+import type { AgentRef } from '@cacheplane/langgraph';
import { ChatMessagesComponent } from '../../primitives/chat-messages/chat-messages.component';
import { MessageTemplateDirective } from '../../primitives/chat-messages/message-template.directive';
import { ChatInputComponent } from '../../primitives/chat-input/chat-input.component';
diff --git a/libs/chat/src/lib/compositions/chat-debug/debug-controls.component.ts b/libs/chat/src/lib/compositions/chat-debug/debug-controls.component.ts
index 739c966f0..5d80e9bee 100644
--- a/libs/chat/src/lib/compositions/chat-debug/debug-controls.component.ts
+++ b/libs/chat/src/lib/compositions/chat-debug/debug-controls.component.ts
@@ -5,7 +5,7 @@ import {
output,
ChangeDetectionStrategy,
} from '@angular/core';
-import type { AgentRef } from '@cacheplane/angular';
+import type { AgentRef } from '@cacheplane/langgraph';
@Component({
selector: 'chat-debug-controls',
diff --git a/libs/chat/src/lib/compositions/chat-debug/debug-summary.component.ts b/libs/chat/src/lib/compositions/chat-debug/debug-summary.component.ts
index 08b8113ee..89ee1a38a 100644
--- a/libs/chat/src/lib/compositions/chat-debug/debug-summary.component.ts
+++ b/libs/chat/src/lib/compositions/chat-debug/debug-summary.component.ts
@@ -5,7 +5,7 @@ import {
input,
ChangeDetectionStrategy,
} from '@angular/core';
-import type { AgentRef } from '@cacheplane/angular';
+import type { AgentRef } from '@cacheplane/langgraph';
import type { DebugCheckpoint } from './debug-checkpoint-card.component';
@Component({
diff --git a/libs/chat/src/lib/compositions/chat-debug/debug-utils.ts b/libs/chat/src/lib/compositions/chat-debug/debug-utils.ts
index bfb7425c5..5474094fa 100644
--- a/libs/chat/src/lib/compositions/chat-debug/debug-utils.ts
+++ b/libs/chat/src/lib/compositions/chat-debug/debug-utils.ts
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
-import type { ThreadState } from '@cacheplane/angular';
+import type { ThreadState } from '@cacheplane/langgraph';
import type { DebugCheckpoint } from './debug-checkpoint-card.component';
/**
diff --git a/libs/chat/src/lib/compositions/chat-interrupt-panel/chat-interrupt-panel.component.ts b/libs/chat/src/lib/compositions/chat-interrupt-panel/chat-interrupt-panel.component.ts
index 69638e46f..386f4ffea 100644
--- a/libs/chat/src/lib/compositions/chat-interrupt-panel/chat-interrupt-panel.component.ts
+++ b/libs/chat/src/lib/compositions/chat-interrupt-panel/chat-interrupt-panel.component.ts
@@ -6,7 +6,7 @@ import {
output,
ChangeDetectionStrategy,
} from '@angular/core';
-import type { AgentRef } from '@cacheplane/angular';
+import type { AgentRef } from '@cacheplane/langgraph';
export type InterruptAction = 'accept' | 'edit' | 'respond' | 'ignore';
diff --git a/libs/chat/src/lib/compositions/chat-subagent-card/chat-subagent-card.component.ts b/libs/chat/src/lib/compositions/chat-subagent-card/chat-subagent-card.component.ts
index 48f27019b..a8b8e552f 100644
--- a/libs/chat/src/lib/compositions/chat-subagent-card/chat-subagent-card.component.ts
+++ b/libs/chat/src/lib/compositions/chat-subagent-card/chat-subagent-card.component.ts
@@ -6,7 +6,7 @@ import {
signal,
ChangeDetectionStrategy,
} from '@angular/core';
-import type { SubagentStreamRef } from '@cacheplane/angular';
+import type { SubagentStreamRef } from '@cacheplane/langgraph';
type SubagentStatus = 'pending' | 'running' | 'complete' | 'error';
diff --git a/libs/chat/src/lib/compositions/chat-timeline-slider/chat-timeline-slider.component.ts b/libs/chat/src/lib/compositions/chat-timeline-slider/chat-timeline-slider.component.ts
index b2ca970bd..348332b3b 100644
--- a/libs/chat/src/lib/compositions/chat-timeline-slider/chat-timeline-slider.component.ts
+++ b/libs/chat/src/lib/compositions/chat-timeline-slider/chat-timeline-slider.component.ts
@@ -7,7 +7,7 @@ import {
signal,
ChangeDetectionStrategy,
} from '@angular/core';
-import type { AgentRef, ThreadState } from '@cacheplane/angular';
+import type { AgentRef, ThreadState } from '@cacheplane/langgraph';
@Component({
selector: 'chat-timeline-slider',
diff --git a/libs/chat/src/lib/compositions/chat/chat.component.ts b/libs/chat/src/lib/compositions/chat/chat.component.ts
index 0f46c029b..da9d9f5ee 100644
--- a/libs/chat/src/lib/compositions/chat/chat.component.ts
+++ b/libs/chat/src/lib/compositions/chat/chat.component.ts
@@ -11,7 +11,7 @@ import {
ElementRef,
ChangeDetectionStrategy,
} from '@angular/core';
-import type { AgentRef } from '@cacheplane/angular';
+import type { AgentRef } from '@cacheplane/langgraph';
import type { ViewRegistry, RenderEvent } from '@cacheplane/render';
import type { A2uiActionMessage } from '@cacheplane/a2ui';
import type { StateStore } from '@json-render/core';
diff --git a/libs/chat/src/lib/primitives/chat-error/chat-error.component.ts b/libs/chat/src/lib/primitives/chat-error/chat-error.component.ts
index a2cec181f..741dd7a55 100644
--- a/libs/chat/src/lib/primitives/chat-error/chat-error.component.ts
+++ b/libs/chat/src/lib/primitives/chat-error/chat-error.component.ts
@@ -5,7 +5,7 @@ import {
input,
ChangeDetectionStrategy,
} from '@angular/core';
-import type { AgentRef } from '@cacheplane/angular';
+import type { AgentRef } from '@cacheplane/langgraph';
export function extractErrorMessage(error: unknown): string | null {
if (!error) return null;
diff --git a/libs/chat/src/lib/primitives/chat-input/chat-input.component.ts b/libs/chat/src/lib/primitives/chat-input/chat-input.component.ts
index 4d6694ab9..6554869d4 100644
--- a/libs/chat/src/lib/primitives/chat-input/chat-input.component.ts
+++ b/libs/chat/src/lib/primitives/chat-input/chat-input.component.ts
@@ -10,7 +10,7 @@ import {
ChangeDetectionStrategy,
} from '@angular/core';
import { FormsModule } from '@angular/forms';
-import type { AgentRef } from '@cacheplane/angular';
+import type { AgentRef } from '@cacheplane/langgraph';
export function submitMessage(
ref: AgentRef,
diff --git a/libs/chat/src/lib/primitives/chat-interrupt/chat-interrupt.component.spec.ts b/libs/chat/src/lib/primitives/chat-interrupt/chat-interrupt.component.spec.ts
index 5bf1d9161..adb714226 100644
--- a/libs/chat/src/lib/primitives/chat-interrupt/chat-interrupt.component.spec.ts
+++ b/libs/chat/src/lib/primitives/chat-interrupt/chat-interrupt.component.spec.ts
@@ -3,7 +3,7 @@ import { describe, it, expect } from 'vitest';
import { signal, computed } from '@angular/core';
import { getInterrupt } from './chat-interrupt.component';
import { createMockAgentRef } from '../../testing/mock-agent-ref';
-import type { Interrupt } from '@cacheplane/angular';
+import type { Interrupt } from '@cacheplane/langgraph';
describe('getInterrupt()', () => {
it('returns undefined when no interrupt is present', () => {
diff --git a/libs/chat/src/lib/primitives/chat-interrupt/chat-interrupt.component.ts b/libs/chat/src/lib/primitives/chat-interrupt/chat-interrupt.component.ts
index f10ff383b..9c90b0458 100644
--- a/libs/chat/src/lib/primitives/chat-interrupt/chat-interrupt.component.ts
+++ b/libs/chat/src/lib/primitives/chat-interrupt/chat-interrupt.component.ts
@@ -8,8 +8,8 @@ import {
ChangeDetectionStrategy,
} from '@angular/core';
import { NgTemplateOutlet } from '@angular/common';
-import type { Interrupt } from '@cacheplane/angular';
-import type { AgentRef } from '@cacheplane/angular';
+import type { Interrupt } from '@cacheplane/langgraph';
+import type { AgentRef } from '@cacheplane/langgraph';
/**
* Retrieves the current interrupt value from a AgentRef.
diff --git a/libs/chat/src/lib/primitives/chat-messages/chat-messages.component.ts b/libs/chat/src/lib/primitives/chat-messages/chat-messages.component.ts
index 2ab8c405e..83ab5281e 100644
--- a/libs/chat/src/lib/primitives/chat-messages/chat-messages.component.ts
+++ b/libs/chat/src/lib/primitives/chat-messages/chat-messages.component.ts
@@ -8,7 +8,7 @@ import {
} from '@angular/core';
import { NgTemplateOutlet } from '@angular/common';
import type { BaseMessage } from '@langchain/core/messages';
-import type { AgentRef } from '@cacheplane/angular';
+import type { AgentRef } from '@cacheplane/langgraph';
import { MessageTemplateDirective } from './message-template.directive';
import type { MessageTemplateType } from '../../chat.types';
diff --git a/libs/chat/src/lib/primitives/chat-subagents/chat-subagents.component.spec.ts b/libs/chat/src/lib/primitives/chat-subagents/chat-subagents.component.spec.ts
index ebac612a7..eb9b99c69 100644
--- a/libs/chat/src/lib/primitives/chat-subagents/chat-subagents.component.spec.ts
+++ b/libs/chat/src/lib/primitives/chat-subagents/chat-subagents.component.spec.ts
@@ -2,7 +2,7 @@
import { describe, it, expect } from 'vitest';
import { signal, computed } from '@angular/core';
import { createMockAgentRef } from '../../testing/mock-agent-ref';
-import type { SubagentStreamRef } from '@cacheplane/angular';
+import type { SubagentStreamRef } from '@cacheplane/langgraph';
describe('ChatSubagentsComponent — activeSubagents computed', () => {
it('returns empty array when no active subagents', () => {
diff --git a/libs/chat/src/lib/primitives/chat-subagents/chat-subagents.component.ts b/libs/chat/src/lib/primitives/chat-subagents/chat-subagents.component.ts
index 9244ebb03..8a2fbb143 100644
--- a/libs/chat/src/lib/primitives/chat-subagents/chat-subagents.component.ts
+++ b/libs/chat/src/lib/primitives/chat-subagents/chat-subagents.component.ts
@@ -8,7 +8,7 @@ import {
ChangeDetectionStrategy,
} from '@angular/core';
import { NgTemplateOutlet } from '@angular/common';
-import type { AgentRef, SubagentStreamRef } from '@cacheplane/angular';
+import type { AgentRef, SubagentStreamRef } from '@cacheplane/langgraph';
@Component({
selector: 'chat-subagents',
diff --git a/libs/chat/src/lib/primitives/chat-timeline/chat-timeline.component.spec.ts b/libs/chat/src/lib/primitives/chat-timeline/chat-timeline.component.spec.ts
index a93298660..e3cd815e9 100644
--- a/libs/chat/src/lib/primitives/chat-timeline/chat-timeline.component.spec.ts
+++ b/libs/chat/src/lib/primitives/chat-timeline/chat-timeline.component.spec.ts
@@ -2,7 +2,7 @@
import { describe, it, expect } from 'vitest';
import { signal, computed } from '@angular/core';
import { createMockAgentRef } from '../../testing/mock-agent-ref';
-import type { ThreadState } from '@cacheplane/angular';
+import type { ThreadState } from '@cacheplane/langgraph';
const makeState = (id: string): ThreadState =>
({ checkpoint_id: id, values: {}, next: [], metadata: {} } as any);
diff --git a/libs/chat/src/lib/primitives/chat-timeline/chat-timeline.component.ts b/libs/chat/src/lib/primitives/chat-timeline/chat-timeline.component.ts
index 3277b2aa2..f2362e173 100644
--- a/libs/chat/src/lib/primitives/chat-timeline/chat-timeline.component.ts
+++ b/libs/chat/src/lib/primitives/chat-timeline/chat-timeline.component.ts
@@ -9,7 +9,7 @@ import {
ChangeDetectionStrategy,
} from '@angular/core';
import { NgTemplateOutlet } from '@angular/common';
-import type { AgentRef, ThreadState } from '@cacheplane/angular';
+import type { AgentRef, ThreadState } from '@cacheplane/langgraph';
@Component({
selector: 'chat-timeline',
diff --git a/libs/chat/src/lib/primitives/chat-tool-calls/chat-tool-calls.component.spec.ts b/libs/chat/src/lib/primitives/chat-tool-calls/chat-tool-calls.component.spec.ts
index 1b54211f9..2f9702bc6 100644
--- a/libs/chat/src/lib/primitives/chat-tool-calls/chat-tool-calls.component.spec.ts
+++ b/libs/chat/src/lib/primitives/chat-tool-calls/chat-tool-calls.component.spec.ts
@@ -3,7 +3,7 @@ import { describe, it, expect } from 'vitest';
import { signal, computed } from '@angular/core';
import { AIMessage, HumanMessage } from '@langchain/core/messages';
import { createMockAgentRef } from '../../testing/mock-agent-ref';
-import type { ToolCallWithResult } from '@cacheplane/angular';
+import type { ToolCallWithResult } from '@cacheplane/langgraph';
describe('ChatToolCallsComponent — toolCalls computed', () => {
it('returns ref.toolCalls() when no message is provided', () => {
diff --git a/libs/chat/src/lib/primitives/chat-tool-calls/chat-tool-calls.component.ts b/libs/chat/src/lib/primitives/chat-tool-calls/chat-tool-calls.component.ts
index 66e95ef1e..d23523ec0 100644
--- a/libs/chat/src/lib/primitives/chat-tool-calls/chat-tool-calls.component.ts
+++ b/libs/chat/src/lib/primitives/chat-tool-calls/chat-tool-calls.component.ts
@@ -10,7 +10,7 @@ import {
import { NgTemplateOutlet } from '@angular/common';
import { AIMessage } from '@langchain/core/messages';
import type { BaseMessage } from '@langchain/core/messages';
-import type { AgentRef } from '@cacheplane/angular';
+import type { AgentRef } from '@cacheplane/langgraph';
import type { ToolCallWithResult } from '@langchain/langgraph-sdk';
@Component({
diff --git a/libs/chat/src/lib/primitives/chat-typing-indicator/chat-typing-indicator.component.ts b/libs/chat/src/lib/primitives/chat-typing-indicator/chat-typing-indicator.component.ts
index 0d53d1e1e..7f835016f 100644
--- a/libs/chat/src/lib/primitives/chat-typing-indicator/chat-typing-indicator.component.ts
+++ b/libs/chat/src/lib/primitives/chat-typing-indicator/chat-typing-indicator.component.ts
@@ -5,7 +5,7 @@ import {
input,
ChangeDetectionStrategy,
} from '@angular/core';
-import type { AgentRef } from '@cacheplane/angular';
+import type { AgentRef } from '@cacheplane/langgraph';
export function isTyping(ref: AgentRef): boolean {
return ref.isLoading();
diff --git a/libs/chat/src/lib/testing/mock-agent-ref.spec.ts b/libs/chat/src/lib/testing/mock-agent-ref.spec.ts
index 09ca55827..73ad3f712 100644
--- a/libs/chat/src/lib/testing/mock-agent-ref.spec.ts
+++ b/libs/chat/src/lib/testing/mock-agent-ref.spec.ts
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
import { describe, it, expect } from 'vitest';
import { createMockAgentRef } from './mock-agent-ref';
-import { ResourceStatus } from '@cacheplane/angular';
+import { ResourceStatus } from '@cacheplane/langgraph';
describe('createMockAgentRef', () => {
it('creates a mock with default values', () => {
diff --git a/libs/chat/src/lib/testing/mock-agent-ref.ts b/libs/chat/src/lib/testing/mock-agent-ref.ts
index 642e90e89..b26a84bfe 100644
--- a/libs/chat/src/lib/testing/mock-agent-ref.ts
+++ b/libs/chat/src/lib/testing/mock-agent-ref.ts
@@ -1,8 +1,8 @@
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
import { signal, WritableSignal } from '@angular/core';
-import type { AgentRef, SubagentStreamRef, ResourceStatus as ResourceStatusType, Interrupt, ThreadState, SubmitOptions, CustomStreamEvent } from '@cacheplane/angular';
+import type { AgentRef, SubagentStreamRef, ResourceStatus as ResourceStatusType, Interrupt, ThreadState, SubmitOptions, CustomStreamEvent } from '@cacheplane/langgraph';
import type { ToolProgress, ToolCallWithResult } from '@langchain/langgraph-sdk';
-import { ResourceStatus } from '@cacheplane/angular';
+import { ResourceStatus } from '@cacheplane/langgraph';
import type { BaseMessage, AIMessage as CoreAIMessage } from '@langchain/core/messages';
import type { MessageMetadata } from '@langchain/langgraph-sdk/ui';
diff --git a/libs/agent/README.md b/libs/langgraph/README.md
similarity index 100%
rename from libs/agent/README.md
rename to libs/langgraph/README.md
diff --git a/libs/agent/eslint.config.mjs b/libs/langgraph/eslint.config.mjs
similarity index 100%
rename from libs/agent/eslint.config.mjs
rename to libs/langgraph/eslint.config.mjs
diff --git a/libs/agent/ng-package.json b/libs/langgraph/ng-package.json
similarity index 76%
rename from libs/agent/ng-package.json
rename to libs/langgraph/ng-package.json
index 3a3b5c552..8a4a864d0 100644
--- a/libs/agent/ng-package.json
+++ b/libs/langgraph/ng-package.json
@@ -1,6 +1,6 @@
{
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
- "dest": "../../dist/libs/agent",
+ "dest": "../../dist/libs/langgraph",
"lib": {
"entryFile": "src/public-api.ts"
}
diff --git a/libs/agent/package.json b/libs/langgraph/package.json
similarity index 90%
rename from libs/agent/package.json
rename to libs/langgraph/package.json
index ff1d81d32..a6eb6e897 100644
--- a/libs/agent/package.json
+++ b/libs/langgraph/package.json
@@ -1,5 +1,5 @@
{
- "name": "@cacheplane/angular",
+ "name": "@cacheplane/langgraph",
"version": "0.0.1",
"peerDependencies": {
"@cacheplane/chat": "^0.0.1",
diff --git a/libs/agent/project.json b/libs/langgraph/project.json
similarity index 75%
rename from libs/agent/project.json
rename to libs/langgraph/project.json
index 6f9baf8d5..f16de782f 100644
--- a/libs/agent/project.json
+++ b/libs/langgraph/project.json
@@ -1,7 +1,7 @@
{
- "name": "agent",
+ "name": "langgraph",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
- "sourceRoot": "libs/agent/src",
+ "sourceRoot": "libs/langgraph/src",
"prefix": "lib",
"projectType": "library",
"release": {
@@ -17,12 +17,12 @@
"executor": "@nx/angular:package",
"outputs": ["{workspaceRoot}/dist/{projectRoot}"],
"options": {
- "project": "libs/agent/ng-package.json",
- "tsConfig": "libs/agent/tsconfig.lib.json"
+ "project": "libs/langgraph/ng-package.json",
+ "tsConfig": "libs/langgraph/tsconfig.lib.json"
},
"configurations": {
"production": {
- "tsConfig": "libs/agent/tsconfig.lib.prod.json"
+ "tsConfig": "libs/langgraph/tsconfig.lib.prod.json"
},
"development": {}
},
@@ -39,7 +39,7 @@
"test": {
"executor": "@nx/vitest:test",
"options": {
- "configFile": "libs/agent/vite.config.mts"
+ "configFile": "libs/langgraph/vite.config.mts"
}
}
}
diff --git a/libs/agent/src/lib/agent.fn.spec.ts b/libs/langgraph/src/lib/agent.fn.spec.ts
similarity index 100%
rename from libs/agent/src/lib/agent.fn.spec.ts
rename to libs/langgraph/src/lib/agent.fn.spec.ts
diff --git a/libs/agent/src/lib/agent.fn.ts b/libs/langgraph/src/lib/agent.fn.ts
similarity index 100%
rename from libs/agent/src/lib/agent.fn.ts
rename to libs/langgraph/src/lib/agent.fn.ts
diff --git a/libs/agent/src/lib/agent.provider.spec.ts b/libs/langgraph/src/lib/agent.provider.spec.ts
similarity index 98%
rename from libs/agent/src/lib/agent.provider.spec.ts
rename to libs/langgraph/src/lib/agent.provider.spec.ts
index f2066d388..d7a7d6cc4 100644
--- a/libs/agent/src/lib/agent.provider.spec.ts
+++ b/libs/langgraph/src/lib/agent.provider.spec.ts
@@ -75,6 +75,6 @@ describe('provideAgent', () => {
TestBed.inject(AGENT_CONFIG);
await new Promise((r) => setTimeout(r, 0));
const calls = warn.mock.calls.map((c) => String(c[0]));
- expect(calls.some((m) => m.includes('[cacheplane] @cacheplane/angular'))).toBe(true);
+ expect(calls.some((m) => m.includes('[cacheplane] @cacheplane/langgraph'))).toBe(true);
});
});
diff --git a/libs/agent/src/lib/agent.provider.ts b/libs/langgraph/src/lib/agent.provider.ts
similarity index 97%
rename from libs/agent/src/lib/agent.provider.ts
rename to libs/langgraph/src/lib/agent.provider.ts
index 49fecce8c..d3e8f37c3 100644
--- a/libs/agent/src/lib/agent.provider.ts
+++ b/libs/langgraph/src/lib/agent.provider.ts
@@ -7,7 +7,7 @@ import {
} from '@cacheplane/licensing';
import { AgentTransport } from './agent.types';
-const PACKAGE_NAME = '@cacheplane/angular';
+const PACKAGE_NAME = '@cacheplane/langgraph';
// Wired up by the release pipeline — imported lazily to avoid a hard build-time
// dependency on package.json.
declare const __CACHEPLANE_AGENT_VERSION__: string | undefined;
diff --git a/libs/agent/src/lib/agent.types.ts b/libs/langgraph/src/lib/agent.types.ts
similarity index 100%
rename from libs/agent/src/lib/agent.types.ts
rename to libs/langgraph/src/lib/agent.types.ts
diff --git a/libs/agent/src/lib/internals/stream-manager.bridge.spec.ts b/libs/langgraph/src/lib/internals/stream-manager.bridge.spec.ts
similarity index 100%
rename from libs/agent/src/lib/internals/stream-manager.bridge.spec.ts
rename to libs/langgraph/src/lib/internals/stream-manager.bridge.spec.ts
diff --git a/libs/agent/src/lib/internals/stream-manager.bridge.ts b/libs/langgraph/src/lib/internals/stream-manager.bridge.ts
similarity index 100%
rename from libs/agent/src/lib/internals/stream-manager.bridge.ts
rename to libs/langgraph/src/lib/internals/stream-manager.bridge.ts
diff --git a/libs/agent/src/lib/to-chat-agent.conformance.spec.ts b/libs/langgraph/src/lib/to-chat-agent.conformance.spec.ts
similarity index 100%
rename from libs/agent/src/lib/to-chat-agent.conformance.spec.ts
rename to libs/langgraph/src/lib/to-chat-agent.conformance.spec.ts
diff --git a/libs/agent/src/lib/to-chat-agent.spec.ts b/libs/langgraph/src/lib/to-chat-agent.spec.ts
similarity index 100%
rename from libs/agent/src/lib/to-chat-agent.spec.ts
rename to libs/langgraph/src/lib/to-chat-agent.spec.ts
diff --git a/libs/agent/src/lib/to-chat-agent.ts b/libs/langgraph/src/lib/to-chat-agent.ts
similarity index 100%
rename from libs/agent/src/lib/to-chat-agent.ts
rename to libs/langgraph/src/lib/to-chat-agent.ts
diff --git a/libs/agent/src/lib/transport/fetch-stream.transport.spec.ts b/libs/langgraph/src/lib/transport/fetch-stream.transport.spec.ts
similarity index 100%
rename from libs/agent/src/lib/transport/fetch-stream.transport.spec.ts
rename to libs/langgraph/src/lib/transport/fetch-stream.transport.spec.ts
diff --git a/libs/agent/src/lib/transport/fetch-stream.transport.ts b/libs/langgraph/src/lib/transport/fetch-stream.transport.ts
similarity index 100%
rename from libs/agent/src/lib/transport/fetch-stream.transport.ts
rename to libs/langgraph/src/lib/transport/fetch-stream.transport.ts
diff --git a/libs/agent/src/lib/transport/mock-stream.transport.spec.ts b/libs/langgraph/src/lib/transport/mock-stream.transport.spec.ts
similarity index 100%
rename from libs/agent/src/lib/transport/mock-stream.transport.spec.ts
rename to libs/langgraph/src/lib/transport/mock-stream.transport.spec.ts
diff --git a/libs/agent/src/lib/transport/mock-stream.transport.ts b/libs/langgraph/src/lib/transport/mock-stream.transport.ts
similarity index 100%
rename from libs/agent/src/lib/transport/mock-stream.transport.ts
rename to libs/langgraph/src/lib/transport/mock-stream.transport.ts
diff --git a/libs/agent/src/lib/transport/transport.interface.ts b/libs/langgraph/src/lib/transport/transport.interface.ts
similarity index 100%
rename from libs/agent/src/lib/transport/transport.interface.ts
rename to libs/langgraph/src/lib/transport/transport.interface.ts
diff --git a/libs/agent/src/public-api.ts b/libs/langgraph/src/public-api.ts
similarity index 100%
rename from libs/agent/src/public-api.ts
rename to libs/langgraph/src/public-api.ts
diff --git a/libs/agent/src/test-setup.ts b/libs/langgraph/src/test-setup.ts
similarity index 86%
rename from libs/agent/src/test-setup.ts
rename to libs/langgraph/src/test-setup.ts
index fe740cd63..171964e39 100644
--- a/libs/agent/src/test-setup.ts
+++ b/libs/langgraph/src/test-setup.ts
@@ -13,8 +13,8 @@ import { createHash } from 'node:crypto';
// environment hits this. Route sha512 through Node's crypto module instead,
// which has no cross-realm constraints and produces the same digest.
//
-// Scoped to libs/agent test-setup only — this does not affect production code
-// or the published package (@cacheplane/angular). The @noble/ed25519 default
+// Scoped to libs/langgraph test-setup only — this does not affect production code
+// or the published package (@cacheplane/langgraph). The @noble/ed25519 default
// remains in place for all non-test consumers.
ed.etc.sha512Async = async (...messages: Uint8Array[]): Promise => {
const hash = createHash('sha512');
diff --git a/libs/agent/tsconfig.json b/libs/langgraph/tsconfig.json
similarity index 100%
rename from libs/agent/tsconfig.json
rename to libs/langgraph/tsconfig.json
diff --git a/libs/agent/tsconfig.lib.json b/libs/langgraph/tsconfig.lib.json
similarity index 100%
rename from libs/agent/tsconfig.lib.json
rename to libs/langgraph/tsconfig.lib.json
diff --git a/libs/agent/tsconfig.lib.prod.json b/libs/langgraph/tsconfig.lib.prod.json
similarity index 100%
rename from libs/agent/tsconfig.lib.prod.json
rename to libs/langgraph/tsconfig.lib.prod.json
diff --git a/libs/agent/vite.config.mts b/libs/langgraph/vite.config.mts
similarity index 100%
rename from libs/agent/vite.config.mts
rename to libs/langgraph/vite.config.mts
diff --git a/libs/licensing/README.md b/libs/licensing/README.md
index efee9f90c..da411f00b 100644
--- a/libs/licensing/README.md
+++ b/libs/licensing/README.md
@@ -5,7 +5,7 @@ Angular framework libraries.
## Status
-Private, pre-1.0. Consumed by `@cacheplane/angular`, `@cacheplane/render`, and
+Private, pre-1.0. Consumed by `@cacheplane/langgraph`, `@cacheplane/render`, and
`@cacheplane/chat`. Not intended as a standalone import.
## Behavior
diff --git a/libs/licensing/src/lib/nag.spec.ts b/libs/licensing/src/lib/nag.spec.ts
index e76b179cc..ce1a621b8 100644
--- a/libs/licensing/src/lib/nag.spec.ts
+++ b/libs/licensing/src/lib/nag.spec.ts
@@ -14,26 +14,26 @@ describe('emitNag', () => {
});
it('is silent when status is licensed', () => {
- emitNag({ status: 'licensed' }, { package: '@cacheplane/angular', warn });
+ emitNag({ status: 'licensed' }, { package: '@cacheplane/langgraph', warn });
expect(warn).not.toHaveBeenCalled();
});
it('is silent when status is noncommercial', () => {
- emitNag({ status: 'noncommercial' }, { package: '@cacheplane/angular', warn });
+ emitNag({ status: 'noncommercial' }, { package: '@cacheplane/langgraph', warn });
expect(warn).not.toHaveBeenCalled();
});
it('warns with a stable prefix when status is missing', () => {
- emitNag({ status: 'missing' }, { package: '@cacheplane/angular', warn });
+ emitNag({ status: 'missing' }, { package: '@cacheplane/langgraph', warn });
expect(warn).toHaveBeenCalledTimes(1);
const message = warn.mock.calls[0][0] as string;
expect(message).toContain('[cacheplane]');
- expect(message).toContain('@cacheplane/angular');
+ expect(message).toContain('@cacheplane/langgraph');
expect(message).toContain('cacheplane.dev/pricing');
});
it('warns differently for grace / expired / tampered', () => {
- emitNag({ status: 'grace' }, { package: '@cacheplane/angular', warn });
+ emitNag({ status: 'grace' }, { package: '@cacheplane/langgraph', warn });
emitNag({ status: 'expired' }, { package: '@cacheplane/render', warn });
emitNag({ status: 'tampered' }, { package: '@cacheplane/chat', warn });
expect(warn).toHaveBeenCalledTimes(3);
@@ -43,13 +43,13 @@ describe('emitNag', () => {
});
it('dedupes repeated calls for the same package + status', () => {
- emitNag({ status: 'missing' }, { package: '@cacheplane/angular', warn });
- emitNag({ status: 'missing' }, { package: '@cacheplane/angular', warn });
+ emitNag({ status: 'missing' }, { package: '@cacheplane/langgraph', warn });
+ emitNag({ status: 'missing' }, { package: '@cacheplane/langgraph', warn });
expect(warn).toHaveBeenCalledTimes(1);
});
it('does not dedupe across different packages', () => {
- emitNag({ status: 'missing' }, { package: '@cacheplane/angular', warn });
+ emitNag({ status: 'missing' }, { package: '@cacheplane/langgraph', warn });
emitNag({ status: 'missing' }, { package: '@cacheplane/render', warn });
expect(warn).toHaveBeenCalledTimes(2);
});
diff --git a/libs/licensing/src/lib/nag.ts b/libs/licensing/src/lib/nag.ts
index f816965c9..6f4e07efa 100644
--- a/libs/licensing/src/lib/nag.ts
+++ b/libs/licensing/src/lib/nag.ts
@@ -2,7 +2,7 @@
import type { EvaluateResult } from './evaluate-license.js';
export interface EmitNagOptions {
- /** Fully-qualified npm package name, e.g. "@cacheplane/angular". */
+ /** Fully-qualified npm package name, e.g. "@cacheplane/langgraph". */
package: string;
/** Injected warn channel; defaults to `console.warn`. */
warn?: (message: string) => void;
diff --git a/libs/licensing/src/lib/run-license-check.spec.ts b/libs/licensing/src/lib/run-license-check.spec.ts
index 76927c3db..fcefb3511 100644
--- a/libs/licensing/src/lib/run-license-check.spec.ts
+++ b/libs/licensing/src/lib/run-license-check.spec.ts
@@ -35,7 +35,7 @@ describe('runLicenseCheck', () => {
it('does not warn with a valid token and still fires telemetry', async () => {
const status = await runLicenseCheck({
- package: '@cacheplane/angular',
+ package: '@cacheplane/langgraph',
version: '1.0.0',
token: validToken,
publicKey: kp.publicKey,
@@ -53,7 +53,7 @@ describe('runLicenseCheck', () => {
it('warns when token is missing', async () => {
const status = await runLicenseCheck({
- package: '@cacheplane/angular',
+ package: '@cacheplane/langgraph',
version: '1.0.0',
publicKey: kp.publicKey,
nowSec: 1_900_000_000,
@@ -67,7 +67,7 @@ describe('runLicenseCheck', () => {
it('is idempotent per (package, token) pair', async () => {
await runLicenseCheck({
- package: '@cacheplane/angular',
+ package: '@cacheplane/langgraph',
version: '1.0.0',
token: validToken,
publicKey: kp.publicKey,
@@ -77,7 +77,7 @@ describe('runLicenseCheck', () => {
fetch: fetchMock,
});
await runLicenseCheck({
- package: '@cacheplane/angular',
+ package: '@cacheplane/langgraph',
version: '1.0.0',
token: validToken,
publicKey: kp.publicKey,
@@ -94,7 +94,7 @@ describe('runLicenseCheck', () => {
it('re-runs when token changes (e.g., after key rotation in the host)', async () => {
const otherToken = await signLicense({ ...BASE, sub: 'cus_xyz' }, kp.privateKey);
await runLicenseCheck({
- package: '@cacheplane/angular',
+ package: '@cacheplane/langgraph',
version: '1.0.0',
token: validToken,
publicKey: kp.publicKey,
@@ -104,7 +104,7 @@ describe('runLicenseCheck', () => {
fetch: fetchMock,
});
await runLicenseCheck({
- package: '@cacheplane/angular',
+ package: '@cacheplane/langgraph',
version: '1.0.0',
token: otherToken,
publicKey: kp.publicKey,
diff --git a/libs/licensing/src/lib/telemetry.spec.ts b/libs/licensing/src/lib/telemetry.spec.ts
index 8b7e9bc94..0884a9b43 100644
--- a/libs/licensing/src/lib/telemetry.spec.ts
+++ b/libs/licensing/src/lib/telemetry.spec.ts
@@ -25,7 +25,7 @@ describe('createTelemetryClient', () => {
});
await client.send({
- package: '@cacheplane/angular',
+ package: '@cacheplane/langgraph',
version: '1.0.0',
licenseId: 'cus_123',
});
@@ -35,7 +35,7 @@ describe('createTelemetryClient', () => {
expect(url).toBe('https://telemetry.example.com/v1/ping');
expect(init.method).toBe('POST');
const body = JSON.parse(init.body as string);
- expect(body.package).toBe('@cacheplane/angular');
+ expect(body.package).toBe('@cacheplane/langgraph');
expect(body.version).toBe('1.0.0');
expect(body.license_id).toBe('cus_123');
expect(typeof body.anon_instance_id).toBe('string');
@@ -48,8 +48,8 @@ describe('createTelemetryClient', () => {
endpoint: 'https://telemetry.example.com/v1/ping',
fetch: fetchMock,
});
- await client.send({ package: '@cacheplane/angular', version: '1.0.0' });
- await client.send({ package: '@cacheplane/angular', version: '1.0.0' });
+ await client.send({ package: '@cacheplane/langgraph', version: '1.0.0' });
+ await client.send({ package: '@cacheplane/langgraph', version: '1.0.0' });
const id1 = JSON.parse(fetchMock.mock.calls[0][1].body as string).anon_instance_id;
const id2 = JSON.parse(fetchMock.mock.calls[1][1].body as string).anon_instance_id;
@@ -63,7 +63,7 @@ describe('createTelemetryClient', () => {
endpoint: 'https://telemetry.example.com/v1/ping',
fetch: fetchMock,
});
- await client.send({ package: '@cacheplane/angular', version: '1.0.0' });
+ await client.send({ package: '@cacheplane/langgraph', version: '1.0.0' });
expect(fetchMock).not.toHaveBeenCalled();
});
@@ -74,7 +74,7 @@ describe('createTelemetryClient', () => {
endpoint: 'https://telemetry.example.com/v1/ping',
fetch: fetchMock,
});
- await client.send({ package: '@cacheplane/angular', version: '1.0.0' });
+ await client.send({ package: '@cacheplane/langgraph', version: '1.0.0' });
expect(fetchMock).not.toHaveBeenCalled();
});
@@ -85,7 +85,7 @@ describe('createTelemetryClient', () => {
fetch: fetchMock,
});
await expect(
- client.send({ package: '@cacheplane/angular', version: '1.0.0' }),
+ client.send({ package: '@cacheplane/langgraph', version: '1.0.0' }),
).resolves.toBeUndefined();
});
});
diff --git a/package-lock.json b/package-lock.json
index 9bea77c80..fa1ebba5d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6830,7 +6830,7 @@
"dev": true,
"license": "(Apache-2.0 WITH LLVM-exception)"
},
- "node_modules/@cacheplane/angular-mcp": {
+ "node_modules/@cacheplane/langgraph-mcp": {
"resolved": "packages/mcp",
"link": true
},
@@ -43289,7 +43289,7 @@
}
},
"packages/mcp": {
- "name": "@cacheplane/angular-mcp",
+ "name": "@cacheplane/langgraph-mcp",
"version": "0.1.0",
"license": "PolyForm-Noncommercial-1.0.0",
"dependencies": {
diff --git a/packages/mcp/package-smoke.test.mjs b/packages/mcp/package-smoke.test.mjs
index f5c85e49c..114312247 100644
--- a/packages/mcp/package-smoke.test.mjs
+++ b/packages/mcp/package-smoke.test.mjs
@@ -14,7 +14,7 @@ const packageJsonPath = path.join(outputRoot, 'package.json');
test('source package manifest entrypoints exist locally', () => {
const packageJson = require('./package.json');
- const binPath = packageJson.bin['@cacheplane/angular-mcp'];
+ const binPath = packageJson.bin['@cacheplane/langgraph-mcp'];
assert.equal(fs.existsSync(path.join(workspaceRoot, 'packages/mcp', packageJson.main)), true);
assert.equal(fs.existsSync(path.join(workspaceRoot, 'packages/mcp', binPath)), true);
@@ -26,7 +26,7 @@ function loadBuiltPackageJson() {
test('built package manifest entrypoints resolve inside Nx output', () => {
const packageJson = loadBuiltPackageJson();
- const binPath = packageJson.bin['@cacheplane/angular-mcp'];
+ const binPath = packageJson.bin['@cacheplane/langgraph-mcp'];
assert.equal(fs.existsSync(path.join(outputRoot, packageJson.main)), true);
assert.equal(fs.existsSync(path.join(outputRoot, binPath)), true);
diff --git a/packages/mcp/package.json b/packages/mcp/package.json
index 596a75f2d..65ba9bb63 100644
--- a/packages/mcp/package.json
+++ b/packages/mcp/package.json
@@ -1,10 +1,10 @@
{
- "name": "@cacheplane/angular-mcp",
+ "name": "@cacheplane/langgraph-mcp",
"version": "0.1.0",
- "description": "MCP server for the @cacheplane/angular library",
+ "description": "MCP server for the @cacheplane/langgraph library",
"license": "PolyForm-Noncommercial-1.0.0",
"main": "src/index.js",
- "bin": { "@cacheplane/angular-mcp": "src/index.js" },
+ "bin": { "@cacheplane/langgraph-mcp": "src/index.js" },
"scripts": {
"build": "tsc -p tsconfig.json",
"smoke": "node --test package-smoke.test.mjs",
diff --git a/packages/mcp/src/tools/add-agent.ts b/packages/mcp/src/tools/add-agent.ts
index 64820f415..754c36bdc 100644
--- a/packages/mcp/src/tools/add-agent.ts
+++ b/packages/mcp/src/tools/add-agent.ts
@@ -28,12 +28,12 @@ export function handleAddAgent(args: Record) {
1. Install the package:
\`\`\`bash
-npm install @cacheplane/angular
+npm install @cacheplane/langgraph
\`\`\`
2. Apply this change to ${appConfigPath}:
\`\`\`diff
-+import { provideAgent } from '@cacheplane/angular';
++import { provideAgent } from '@cacheplane/langgraph';
export const appConfig: ApplicationConfig = {
providers: [
diff --git a/packages/mcp/src/tools/get-example.ts b/packages/mcp/src/tools/get-example.ts
index 4c911a73d..90f218905 100644
--- a/packages/mcp/src/tools/get-example.ts
+++ b/packages/mcp/src/tools/get-example.ts
@@ -2,7 +2,7 @@
const EXAMPLES: Record = {
'basic-chat': `// Basic chat component with angular
import { Component } from '@angular/core';
-import { agent } from '@cacheplane/angular';
+import { agent } from '@cacheplane/langgraph';
import type { BaseMessage } from '@langchain/core/messages';
@Component({
@@ -26,7 +26,7 @@ export class ChatComponent {
'thread-persistence': `// Thread persistence with localStorage
import { Component, signal } from '@angular/core';
-import { agent } from '@cacheplane/angular';
+import { agent } from '@cacheplane/langgraph';
import type { BaseMessage } from '@langchain/core/messages';
@Component({ selector: 'app-chat', template: '' })
@@ -42,7 +42,7 @@ export class ChatComponent {
'system-prompt': `// System prompt configuration per session
import { Component } from '@angular/core';
-import { agent } from '@cacheplane/angular';
+import { agent } from '@cacheplane/langgraph';
@Component({ selector: 'app-chat', template: '' })
export class ChatComponent {
@@ -54,7 +54,7 @@ export class ChatComponent {
'mock-testing': `// Unit testing with MockAgentTransport
import { TestBed } from '@angular/core/testing';
-import { agent, MockAgentTransport } from '@cacheplane/angular';
+import { agent, MockAgentTransport } from '@cacheplane/langgraph';
import type { BaseMessage } from '@langchain/core/messages';
describe('ChatComponent', () => {
@@ -73,7 +73,7 @@ describe('ChatComponent', () => {
'interrupts': `// Handling interrupts (human-in-the-loop)
import { Component } from '@angular/core';
-import { agent } from '@cacheplane/angular';
+import { agent } from '@cacheplane/langgraph';
@Component({
selector: 'app-chat',
@@ -95,7 +95,7 @@ export class ChatComponent {
'subagent-progress': `// Showing subagent tool call progress
import { Component } from '@angular/core';
-import { agent } from '@cacheplane/angular';
+import { agent } from '@cacheplane/langgraph';
@Component({
selector: 'app-chat',
@@ -110,7 +110,7 @@ export class ChatComponent {
}`,
'custom-transport': `// Custom transport with auth headers
-import { AgentTransport } from '@cacheplane/angular';
+import { AgentTransport } from '@cacheplane/langgraph';
export class AuthTransport implements AgentTransport {
async *stream(input: unknown, _options: unknown): AsyncGenerator {
diff --git a/packages/mcp/src/tools/scaffold-chat-component.ts b/packages/mcp/src/tools/scaffold-chat-component.ts
index 135231d93..76d7f6e35 100644
--- a/packages/mcp/src/tools/scaffold-chat-component.ts
+++ b/packages/mcp/src/tools/scaffold-chat-component.ts
@@ -29,7 +29,7 @@ export function handleScaffoldChatComponent(args: Record) {
: '';
const code = `import { Component${persistenceImport} } from '@angular/core';
-import { agent } from '@cacheplane/angular';
+import { agent } from '@cacheplane/langgraph';
import type { BaseMessage } from '@langchain/core/messages';
@Component({
diff --git a/tsconfig.base.json b/tsconfig.base.json
index 3f3df86d8..ce82e20d1 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -23,7 +23,7 @@
"@cacheplane/cockpit-langgraph-streaming-python": [
"cockpit/langgraph/streaming/python/src/index.ts"
],
- "@cacheplane/angular": ["libs/agent/src/public-api.ts"],
+ "@cacheplane/langgraph": ["libs/langgraph/src/public-api.ts"],
"@cacheplane/render": ["libs/render/src/public-api.ts"],
"@cacheplane/chat": ["libs/chat/src/public-api.ts"],
"@cacheplane/partial-json": ["libs/partial-json/src/index.ts"],
From 0512f24765aa01f8a659cb38c660fd7722116b3b Mon Sep 17 00:00:00 2001
From: Brian Love
Date: Tue, 21 Apr 2026 13:48:25 -0700
Subject: [PATCH 19/40] refactor(chat): migrate chat-input to ChatAgent
contract
Co-Authored-By: Claude Sonnet 4.6
---
.../chat-input/chat-input.component.spec.ts | 72 +++++++++----------
.../chat-input/chat-input.component.ts | 12 ++--
2 files changed, 41 insertions(+), 43 deletions(-)
diff --git a/libs/chat/src/lib/primitives/chat-input/chat-input.component.spec.ts b/libs/chat/src/lib/primitives/chat-input/chat-input.component.spec.ts
index d81e6a729..473a8c3ac 100644
--- a/libs/chat/src/lib/primitives/chat-input/chat-input.component.spec.ts
+++ b/libs/chat/src/lib/primitives/chat-input/chat-input.component.spec.ts
@@ -1,78 +1,76 @@
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
-import { describe, it, expect, vi } from 'vitest';
+import { describe, it, expect } from 'vitest';
import { signal, computed } from '@angular/core';
import { submitMessage } from './chat-input.component';
-import { createMockAgentRef } from '../../testing/mock-agent-ref';
+import { mockChatAgent } from '../../testing/mock-chat-agent';
describe('submitMessage()', () => {
- it('calls ref.submit with a human message containing the trimmed text', () => {
- const mockRef = createMockAgentRef();
- const submitSpy = vi.spyOn(mockRef, 'submit');
+ it('calls agent.submit with { message: trimmed text }', async () => {
+ const agent = mockChatAgent();
- submitMessage(mockRef, ' hello world ');
+ submitMessage(agent, ' hello world ');
- expect(submitSpy).toHaveBeenCalledOnce();
- const args = submitSpy.mock.calls[0][0] as { messages: Array<{ role: string; content: string }> };
- expect(args.messages).toHaveLength(1);
- expect(args.messages[0].role).toBe('human');
- expect(args.messages[0].content).toBe('hello world');
+ // Flush the async submit (it's void-async, we just need microtask flush)
+ await Promise.resolve();
+ expect(agent.submitCalls).toHaveLength(1);
+ expect(agent.submitCalls[0].input).toEqual({ message: 'hello world' });
});
it('returns the trimmed text on successful submit', () => {
- const mockRef = createMockAgentRef();
- const result = submitMessage(mockRef, ' hello ');
+ const agent = mockChatAgent();
+ const result = submitMessage(agent, ' hello ');
expect(result).toBe('hello');
});
- it('does not call ref.submit and returns null for whitespace-only text', () => {
- const mockRef = createMockAgentRef();
- const submitSpy = vi.spyOn(mockRef, 'submit');
+ it('does not call agent.submit and returns null for whitespace-only text', async () => {
+ const agent = mockChatAgent();
- const result = submitMessage(mockRef, ' ');
+ const result = submitMessage(agent, ' ');
- expect(submitSpy).not.toHaveBeenCalled();
+ await Promise.resolve();
+ expect(agent.submitCalls).toHaveLength(0);
expect(result).toBeNull();
});
- it('does not call ref.submit and returns null for empty string', () => {
- const mockRef = createMockAgentRef();
- const submitSpy = vi.spyOn(mockRef, 'submit');
+ it('does not call agent.submit and returns null for empty string', async () => {
+ const agent = mockChatAgent();
- const result = submitMessage(mockRef, '');
+ const result = submitMessage(agent, '');
- expect(submitSpy).not.toHaveBeenCalled();
+ await Promise.resolve();
+ expect(agent.submitCalls).toHaveLength(0);
expect(result).toBeNull();
});
});
describe('ChatInputComponent — isDisabled computed', () => {
- it('isDisabled is false when ref.isLoading is false', () => {
- const mockRef = createMockAgentRef({ isLoading: false });
- const ref$ = signal(mockRef);
+ it('isDisabled is false when agent.isLoading is false', () => {
+ const agent = mockChatAgent({ isLoading: false });
+ const agent$ = signal(agent);
- const isDisabled = computed(() => ref$().isLoading());
+ const isDisabled = computed(() => agent$().isLoading());
expect(isDisabled()).toBe(false);
});
- it('isDisabled is true when ref.isLoading is true', () => {
- const mockRef = createMockAgentRef({ isLoading: true });
- const ref$ = signal(mockRef);
+ it('isDisabled is true when agent.isLoading is true', () => {
+ const agent = mockChatAgent({ isLoading: true });
+ const agent$ = signal(agent);
- const isDisabled = computed(() => ref$().isLoading());
+ const isDisabled = computed(() => agent$().isLoading());
expect(isDisabled()).toBe(true);
});
- it('isDisabled updates reactively when ref changes', () => {
- const idleRef = createMockAgentRef({ isLoading: false });
- const loadingRef = createMockAgentRef({ isLoading: true });
- const ref$ = signal(idleRef);
+ it('isDisabled updates reactively when agent changes', () => {
+ const idleAgent = mockChatAgent({ isLoading: false });
+ const loadingAgent = mockChatAgent({ isLoading: true });
+ const agent$ = signal(idleAgent);
- const isDisabled = computed(() => ref$().isLoading());
+ const isDisabled = computed(() => agent$().isLoading());
expect(isDisabled()).toBe(false);
- ref$.set(loadingRef);
+ agent$.set(loadingAgent);
expect(isDisabled()).toBe(true);
});
});
diff --git a/libs/chat/src/lib/primitives/chat-input/chat-input.component.ts b/libs/chat/src/lib/primitives/chat-input/chat-input.component.ts
index 6554869d4..300a65453 100644
--- a/libs/chat/src/lib/primitives/chat-input/chat-input.component.ts
+++ b/libs/chat/src/lib/primitives/chat-input/chat-input.component.ts
@@ -10,15 +10,15 @@ import {
ChangeDetectionStrategy,
} from '@angular/core';
import { FormsModule } from '@angular/forms';
-import type { AgentRef } from '@cacheplane/langgraph';
+import type { ChatAgent } from '../../agent';
export function submitMessage(
- ref: AgentRef,
+ agent: ChatAgent,
text: string,
): string | null {
const trimmed = text.trim();
if (!trimmed) return null;
- ref.submit({ messages: [{ role: 'human', content: trimmed }] });
+ void agent.submit({ message: trimmed });
return trimmed;
}
@@ -74,18 +74,18 @@ export function submitMessage(
`,
})
export class ChatInputComponent {
- readonly ref = input.required>();
+ readonly agent = input.required();
readonly submitOnEnter = input(true);
readonly placeholder = input('');
readonly submitted = output();
readonly messageText = signal('');
- readonly isDisabled = computed(() => this.ref().isLoading());
+ readonly isDisabled = computed(() => this.agent().isLoading());
readonly focused = signal(false);
private readonly textareaEl = viewChild>('textareaEl');
onSubmit(): void {
- const submitted = submitMessage(this.ref(), this.messageText());
+ const submitted = submitMessage(this.agent(), this.messageText());
if (submitted !== null) {
this.submitted.emit(submitted);
this.messageText.set('');
From 4e94fb09c4b33f4e8795a3c1457d7041d5f415a0 Mon Sep 17 00:00:00 2001
From: Brian Love
Date: Tue, 21 Apr 2026 13:48:58 -0700
Subject: [PATCH 20/40] refactor(chat): migrate chat-messages to ChatAgent
contract
Co-Authored-By: Claude Sonnet 4.6
---
.../chat-messages.component.spec.ts | 70 +++++++++----------
.../chat-messages/chat-messages.component.ts | 25 +++----
2 files changed, 42 insertions(+), 53 deletions(-)
diff --git a/libs/chat/src/lib/primitives/chat-messages/chat-messages.component.spec.ts b/libs/chat/src/lib/primitives/chat-messages/chat-messages.component.spec.ts
index 6685ae738..2f9e82ec9 100644
--- a/libs/chat/src/lib/primitives/chat-messages/chat-messages.component.spec.ts
+++ b/libs/chat/src/lib/primitives/chat-messages/chat-messages.component.spec.ts
@@ -1,65 +1,64 @@
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
import { describe, it, expect } from 'vitest';
import { signal } from '@angular/core';
-import { HumanMessage, AIMessage, SystemMessage, ToolMessage, FunctionMessage } from '@langchain/core/messages';
import { getMessageType } from './chat-messages.component';
-import { createMockAgentRef } from '../../testing/mock-agent-ref';
+import { mockChatAgent } from '../../testing/mock-chat-agent';
+import type { ChatMessage } from '../../agent';
describe('getMessageType', () => {
- it('maps HumanMessage to "human"', () => {
- expect(getMessageType(new HumanMessage('hello'))).toBe('human');
+ it('maps user role to "human"', () => {
+ const msg: ChatMessage = { id: '1', role: 'user', content: 'hello' };
+ expect(getMessageType(msg)).toBe('human');
});
- it('maps AIMessage to "ai"', () => {
- expect(getMessageType(new AIMessage('response'))).toBe('ai');
+ it('maps assistant role to "ai"', () => {
+ const msg: ChatMessage = { id: '2', role: 'assistant', content: 'response' };
+ expect(getMessageType(msg)).toBe('ai');
});
- it('maps SystemMessage to "system"', () => {
- expect(getMessageType(new SystemMessage('system prompt'))).toBe('system');
+ it('maps system role to "system"', () => {
+ const msg: ChatMessage = { id: '3', role: 'system', content: 'system prompt' };
+ expect(getMessageType(msg)).toBe('system');
});
- it('maps ToolMessage to "tool"', () => {
- const toolMsg = new ToolMessage({ content: 'result', tool_call_id: 'call_1' });
- expect(getMessageType(toolMsg)).toBe('tool');
+ it('maps tool role to "tool"', () => {
+ const msg: ChatMessage = { id: '4', role: 'tool', content: 'result', toolCallId: 'call_1' };
+ expect(getMessageType(msg)).toBe('tool');
});
- it('maps FunctionMessage to "function"', () => {
- const fnMsg = new FunctionMessage({ content: 'result', name: 'my_fn' });
- expect(getMessageType(fnMsg)).toBe('function');
- });
-
- it('falls back to "ai" for unknown message types', () => {
- const unknownMsg = { _getType: () => 'unknown' } as any;
- expect(getMessageType(unknownMsg)).toBe('ai');
+ it('falls back to "ai" for unknown roles', () => {
+ const msg = { id: '5', role: 'unknown', content: '' } as unknown as ChatMessage;
+ expect(getMessageType(msg)).toBe('ai');
});
});
describe('ChatMessagesComponent — computed messages', () => {
- it('messages() signal reflects the ref messages signal', () => {
- const msgs = [new HumanMessage('hi'), new AIMessage('hello')];
- const mockRef = createMockAgentRef({ messages: msgs });
+ it('messages() signal reflects the agent messages signal', () => {
+ const msgs: ChatMessage[] = [
+ { id: '1', role: 'user', content: 'hi' },
+ { id: '2', role: 'assistant', content: 'hello' },
+ ];
+ const agent = mockChatAgent({ messages: msgs });
- // Simulate what the component computes: ref().messages()
- const ref$ = signal(mockRef);
- const messages = () => ref$().messages();
+ const agent$ = signal(agent);
+ const messages = () => agent$().messages();
expect(messages()).toHaveLength(2);
- expect(messages()[0]._getType()).toBe('human');
- expect(messages()[1]._getType()).toBe('ai');
+ expect(messages()[0].role).toBe('user');
+ expect(messages()[1].role).toBe('assistant');
});
- it('messages() updates reactively when ref messages change', () => {
- const mockRef = createMockAgentRef({ messages: [] });
- const ref$ = signal(mockRef);
- const messages = () => ref$().messages();
+ it('messages() updates reactively when agent messages change', () => {
+ const agent = mockChatAgent({ messages: [] });
+ const agent$ = signal(agent);
+ const messages = () => agent$().messages();
expect(messages()).toHaveLength(0);
- // Swap the ref to one with messages to test signal reactivity
- const updatedRef = createMockAgentRef({
- messages: [new HumanMessage('new message')],
+ const updatedAgent = mockChatAgent({
+ messages: [{ id: '1', role: 'user', content: 'new message' }],
});
- ref$.set(updatedRef);
+ agent$.set(updatedAgent);
expect(messages()).toHaveLength(1);
});
@@ -67,7 +66,6 @@ describe('ChatMessagesComponent — computed messages', () => {
describe('ChatMessagesComponent — findTemplate logic', () => {
it('findTemplate returns matching directive by type', () => {
- // Simulate findTemplate logic: find in array by chatMessageTemplate() value
const templates = [
{ chatMessageTemplate: () => 'human' as const, templateRef: {} },
{ chatMessageTemplate: () => 'ai' as const, templateRef: {} },
diff --git a/libs/chat/src/lib/primitives/chat-messages/chat-messages.component.ts b/libs/chat/src/lib/primitives/chat-messages/chat-messages.component.ts
index 83ab5281e..30beb65e3 100644
--- a/libs/chat/src/lib/primitives/chat-messages/chat-messages.component.ts
+++ b/libs/chat/src/lib/primitives/chat-messages/chat-messages.component.ts
@@ -7,33 +7,24 @@ import {
ChangeDetectionStrategy,
} from '@angular/core';
import { NgTemplateOutlet } from '@angular/common';
-import type { BaseMessage } from '@langchain/core/messages';
-import type { AgentRef } from '@cacheplane/langgraph';
+import type { ChatAgent, ChatMessage } from '../../agent';
import { MessageTemplateDirective } from './message-template.directive';
import type { MessageTemplateType } from '../../chat.types';
/**
- * Maps a LangChain message to a {@link MessageTemplateType}.
- * Handles both class instances (with `_getType()`) and plain objects (with `type` property)
- * since SSE stream events deliver plain JSON, not hydrated BaseMessage instances.
+ * Maps a {@link ChatMessage} to a {@link MessageTemplateType}.
* Exported as a standalone function so it can be unit-tested without DOM rendering.
*/
-export function getMessageType(message: BaseMessage): MessageTemplateType {
- // Try class method first, fall back to plain object property
- const type = typeof message._getType === 'function'
- ? message._getType()
- : (message as unknown as Record)['type'] as string ?? 'ai';
- switch (type) {
- case 'human':
+export function getMessageType(message: ChatMessage): MessageTemplateType {
+ switch (message.role) {
+ case 'user':
return 'human';
- case 'ai':
+ case 'assistant':
return 'ai';
case 'tool':
return 'tool';
case 'system':
return 'system';
- case 'function':
- return 'function';
default:
return 'ai';
}
@@ -57,11 +48,11 @@ export function getMessageType(message: BaseMessage): MessageTemplateType {
`,
})
export class ChatMessagesComponent {
- readonly ref = input.required>();
+ readonly agent = input.required();
readonly messageTemplates = contentChildren(MessageTemplateDirective);
- readonly messages = computed(() => this.ref().messages());
+ readonly messages = computed(() => this.agent().messages());
readonly getMessageType = getMessageType;
From 34b410235059657e332f929d146cd5f7f570a3e2 Mon Sep 17 00:00:00 2001
From: Brian Love
Date: Tue, 21 Apr 2026 13:49:31 -0700
Subject: [PATCH 21/40] refactor(chat): migrate chat-tool-calls to ChatAgent
contract
Co-Authored-By: Claude Sonnet 4.6
---
.../chat-tool-calls.component.spec.ts | 96 +++++++++++--------
.../chat-tool-calls.component.ts | 23 +++--
2 files changed, 67 insertions(+), 52 deletions(-)
diff --git a/libs/chat/src/lib/primitives/chat-tool-calls/chat-tool-calls.component.spec.ts b/libs/chat/src/lib/primitives/chat-tool-calls/chat-tool-calls.component.spec.ts
index 2f9702bc6..3d3dba7ea 100644
--- a/libs/chat/src/lib/primitives/chat-tool-calls/chat-tool-calls.component.spec.ts
+++ b/libs/chat/src/lib/primitives/chat-tool-calls/chat-tool-calls.component.spec.ts
@@ -1,60 +1,74 @@
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
import { describe, it, expect } from 'vitest';
import { signal, computed } from '@angular/core';
-import { AIMessage, HumanMessage } from '@langchain/core/messages';
-import { createMockAgentRef } from '../../testing/mock-agent-ref';
-import type { ToolCallWithResult } from '@cacheplane/langgraph';
+import { mockChatAgent } from '../../testing/mock-chat-agent';
+import type { ChatMessage, ChatToolCall } from '../../agent';
describe('ChatToolCallsComponent — toolCalls computed', () => {
- it('returns ref.toolCalls() when no message is provided', () => {
- const mockToolCalls: ToolCallWithResult[] = [
- { id: 'call_1', name: 'get_weather', args: { city: 'NYC' }, result: null } as any,
+ it('returns agent.toolCalls() when no message is provided', () => {
+ const mockToolCalls: ChatToolCall[] = [
+ { id: 'call_1', name: 'get_weather', args: { city: 'NYC' }, status: 'complete', result: 'sunny' },
];
- const mockRef = createMockAgentRef();
- (mockRef.toolCalls as ReturnType>).set(mockToolCalls);
+ const agent = mockChatAgent({ toolCalls: mockToolCalls });
- const ref$ = signal(mockRef);
- const toolCalls = computed(() => ref$().toolCalls());
+ const agent$ = signal(agent);
+ const toolCalls = computed(() => agent$().toolCalls());
expect(toolCalls()).toHaveLength(1);
expect(toolCalls()[0].id).toBe('call_1');
});
- it('returns ref.toolCalls() when message has no tool_calls', () => {
- const mockRef = createMockAgentRef();
- const msg = new HumanMessage('hello');
+ it('returns agent.toolCalls() when message is a user message (no tool_use blocks)', () => {
+ const agent = mockChatAgent();
+ const msg: ChatMessage = { id: '1', role: 'user', content: 'hello' };
- const ref$ = signal(mockRef);
- const message$ = signal(msg);
+ const agent$ = signal(agent);
+ const message$ = signal(msg);
- // Simulate component logic: use message tool_calls if present, else ref
- const toolCalls = computed(() => {
+ const toolCalls = computed((): ChatToolCall[] => {
const m = message$();
- if (m && 'tool_calls' in m && Array.isArray(m.tool_calls) && m.tool_calls.length > 0) {
- return m.tool_calls;
+ if (m && m.role === 'assistant' && Array.isArray(m.content)) {
+ const blocks = m.content.filter((b: any) => b.type === 'tool_use') as Array<{
+ type: 'tool_use'; id: string; name: string; args: unknown;
+ }>;
+ const all = agent$().toolCalls();
+ return blocks
+ .map(b => all.find(tc => tc.id === b.id))
+ .filter((x): x is ChatToolCall => !!x);
}
- return ref$().toolCalls();
+ return agent$().toolCalls();
});
expect(toolCalls()).toHaveLength(0);
});
- it('returns message tool_calls when message has tool_calls', () => {
- const mockRef = createMockAgentRef();
- const msg = new AIMessage({
- content: '',
- tool_calls: [{ id: 'call_2', name: 'search', args: { query: 'test' } }],
- });
+ it('returns matched ChatToolCalls when message has tool_use content blocks', () => {
+ const mockToolCalls: ChatToolCall[] = [
+ { id: 'call_2', name: 'search', args: { query: 'test' }, status: 'complete', result: 'results' },
+ ];
+ const agent = mockChatAgent({ toolCalls: mockToolCalls });
+
+ const msg: ChatMessage = {
+ id: '2',
+ role: 'assistant',
+ content: [{ type: 'tool_use', id: 'call_2', name: 'search', args: { query: 'test' } }],
+ };
- const ref$ = signal(mockRef);
- const message$ = signal(msg);
+ const agent$ = signal(agent);
+ const message$ = signal(msg);
- const toolCalls = computed(() => {
+ const toolCalls = computed((): ChatToolCall[] => {
const m = message$();
- if (m && 'tool_calls' in m && Array.isArray((m as any).tool_calls)) {
- return (m as any).tool_calls;
+ if (m && m.role === 'assistant' && Array.isArray(m.content)) {
+ const blocks = m.content.filter((b: any) => b.type === 'tool_use') as Array<{
+ type: 'tool_use'; id: string; name: string; args: unknown;
+ }>;
+ const all = agent$().toolCalls();
+ return blocks
+ .map(b => all.find(tc => tc.id === b.id))
+ .filter((x): x is ChatToolCall => !!x);
}
- return ref$().toolCalls();
+ return agent$().toolCalls();
});
expect(toolCalls()).toHaveLength(1);
@@ -62,19 +76,17 @@ describe('ChatToolCallsComponent — toolCalls computed', () => {
expect(toolCalls()[0].name).toBe('search');
});
- it('toolCalls updates reactively when ref changes', () => {
- const emptyRef = createMockAgentRef();
- const loadedRef = createMockAgentRef();
- const mockToolCalls: ToolCallWithResult[] = [
- { id: 'call_3', name: 'calculator', args: {}, result: null } as any,
- ];
- (loadedRef.toolCalls as ReturnType>).set(mockToolCalls);
+ it('toolCalls updates reactively when agent changes', () => {
+ const emptyAgent = mockChatAgent();
+ const loadedAgent = mockChatAgent({
+ toolCalls: [{ id: 'call_3', name: 'calculator', args: {}, status: 'complete' }],
+ });
- const ref$ = signal(emptyRef);
- const toolCalls = computed(() => ref$().toolCalls());
+ const agent$ = signal(emptyAgent);
+ const toolCalls = computed(() => agent$().toolCalls());
expect(toolCalls()).toHaveLength(0);
- ref$.set(loadedRef);
+ agent$.set(loadedAgent);
expect(toolCalls()).toHaveLength(1);
});
});
diff --git a/libs/chat/src/lib/primitives/chat-tool-calls/chat-tool-calls.component.ts b/libs/chat/src/lib/primitives/chat-tool-calls/chat-tool-calls.component.ts
index d23523ec0..44f30d2c3 100644
--- a/libs/chat/src/lib/primitives/chat-tool-calls/chat-tool-calls.component.ts
+++ b/libs/chat/src/lib/primitives/chat-tool-calls/chat-tool-calls.component.ts
@@ -8,10 +8,7 @@ import {
ChangeDetectionStrategy,
} from '@angular/core';
import { NgTemplateOutlet } from '@angular/common';
-import { AIMessage } from '@langchain/core/messages';
-import type { BaseMessage } from '@langchain/core/messages';
-import type { AgentRef } from '@cacheplane/langgraph';
-import type { ToolCallWithResult } from '@langchain/langgraph-sdk';
+import type { ChatAgent, ChatMessage, ChatToolCall } from '../../agent';
@Component({
selector: 'chat-tool-calls',
@@ -30,16 +27,22 @@ import type { ToolCallWithResult } from '@langchain/langgraph-sdk';
`,
})
export class ChatToolCallsComponent {
- readonly ref = input.required>();
- readonly message = input(undefined);
+ readonly agent = input.required();
+ readonly message = input(undefined);
readonly templateRef = contentChild(TemplateRef);
- readonly toolCalls = computed((): ToolCallWithResult[] => {
+ readonly toolCalls = computed((): ChatToolCall[] => {
const msg = this.message();
- if (msg instanceof AIMessage) {
- return this.ref().getToolCalls(msg);
+ if (msg && msg.role === 'assistant' && Array.isArray(msg.content)) {
+ const blocks = msg.content.filter(b => b.type === 'tool_use') as Array<{
+ type: 'tool_use'; id: string; name: string; args: unknown;
+ }>;
+ const all = this.agent().toolCalls();
+ return blocks
+ .map(b => all.find(tc => tc.id === b.id))
+ .filter((x): x is ChatToolCall => !!x);
}
- return this.ref().toolCalls();
+ return this.agent().toolCalls();
});
}
From 31ebba27fb1383d51263a49dc2af4741de32b9ff Mon Sep 17 00:00:00 2001
From: Brian Love
Date: Tue, 21 Apr 2026 13:50:06 -0700
Subject: [PATCH 22/40] refactor(chat): migrate chat-typing-indicator to
ChatAgent contract
Co-Authored-By: Claude Sonnet 4.6
---
.../chat-typing-indicator.component.spec.ts | 87 ++++++++++++++-----
.../chat-typing-indicator.component.ts | 30 ++++---
2 files changed, 82 insertions(+), 35 deletions(-)
diff --git a/libs/chat/src/lib/primitives/chat-typing-indicator/chat-typing-indicator.component.spec.ts b/libs/chat/src/lib/primitives/chat-typing-indicator/chat-typing-indicator.component.spec.ts
index bc95324cf..44f324d26 100644
--- a/libs/chat/src/lib/primitives/chat-typing-indicator/chat-typing-indicator.component.spec.ts
+++ b/libs/chat/src/lib/primitives/chat-typing-indicator/chat-typing-indicator.component.spec.ts
@@ -2,48 +2,93 @@
import { describe, it, expect } from 'vitest';
import { signal, computed } from '@angular/core';
import { isTyping } from './chat-typing-indicator.component';
-import { createMockAgentRef } from '../../testing/mock-agent-ref';
+import { mockChatAgent } from '../../testing/mock-chat-agent';
+import type { ChatMessage } from '../../agent';
describe('isTyping()', () => {
- it('returns false when ref.isLoading is false', () => {
- const mockRef = createMockAgentRef({ isLoading: false });
- expect(isTyping(mockRef)).toBe(false);
+ it('returns false when agent.isLoading is false', () => {
+ const agent = mockChatAgent({ isLoading: false });
+ expect(isTyping(agent)).toBe(false);
});
- it('returns true when ref.isLoading is true', () => {
- const mockRef = createMockAgentRef({ isLoading: true });
- expect(isTyping(mockRef)).toBe(true);
+ it('returns true when agent.isLoading is true and messages is empty', () => {
+ const agent = mockChatAgent({ isLoading: true, messages: [] });
+ expect(isTyping(agent)).toBe(true);
+ });
+
+ it('returns true when loading and last message is user', () => {
+ const agent = mockChatAgent({
+ isLoading: true,
+ messages: [{ id: '1', role: 'user', content: 'hi' }],
+ });
+ expect(isTyping(agent)).toBe(true);
+ });
+
+ it('returns false when loading and last message is non-empty assistant', () => {
+ const agent = mockChatAgent({
+ isLoading: true,
+ messages: [
+ { id: '1', role: 'user', content: 'hi' },
+ { id: '2', role: 'assistant', content: 'hello there' },
+ ],
+ });
+ expect(isTyping(agent)).toBe(false);
+ });
+
+ it('returns true when loading and last message is empty-content assistant', () => {
+ const agent = mockChatAgent({
+ isLoading: true,
+ messages: [
+ { id: '1', role: 'user', content: 'hi' },
+ { id: '2', role: 'assistant', content: '' },
+ ],
+ });
+ expect(isTyping(agent)).toBe(true);
+ });
+
+ it('returns true when loading and last assistant message has empty block array', () => {
+ const agent = mockChatAgent({
+ isLoading: true,
+ messages: [
+ { id: '1', role: 'user', content: 'hi' },
+ { id: '2', role: 'assistant', content: [] },
+ ],
+ });
+ expect(isTyping(agent)).toBe(true);
});
});
describe('ChatTypingIndicatorComponent — visible computed', () => {
- it('visible is false when ref.isLoading is false', () => {
- const mockRef = createMockAgentRef({ isLoading: false });
- const ref$ = signal(mockRef);
+ it('visible is false when agent.isLoading is false', () => {
+ const agent = mockChatAgent({ isLoading: false });
+ const agent$ = signal(agent);
- const visible = computed(() => ref$().isLoading());
+ const visible = computed(() => isTyping(agent$()));
expect(visible()).toBe(false);
});
- it('visible is true when ref.isLoading is true', () => {
- const mockRef = createMockAgentRef({ isLoading: true });
- const ref$ = signal(mockRef);
+ it('visible is true when agent.isLoading is true and no messages', () => {
+ const agent = mockChatAgent({ isLoading: true, messages: [] });
+ const agent$ = signal(agent);
- const visible = computed(() => ref$().isLoading());
+ const visible = computed(() => isTyping(agent$()));
expect(visible()).toBe(true);
});
- it('visible updates reactively when ref changes', () => {
- const idleRef = createMockAgentRef({ isLoading: false });
- const loadingRef = createMockAgentRef({ isLoading: true });
- const ref$ = signal(idleRef);
+ it('visible updates reactively when agent changes', () => {
+ const idleAgent = mockChatAgent({ isLoading: false });
+ const loadingAgent = mockChatAgent({
+ isLoading: true,
+ messages: [{ id: '1', role: 'user', content: 'hi' }],
+ });
+ const agent$ = signal(idleAgent);
- const visible = computed(() => ref$().isLoading());
+ const visible = computed(() => isTyping(agent$()));
expect(visible()).toBe(false);
- ref$.set(loadingRef);
+ agent$.set(loadingAgent);
expect(visible()).toBe(true);
});
});
diff --git a/libs/chat/src/lib/primitives/chat-typing-indicator/chat-typing-indicator.component.ts b/libs/chat/src/lib/primitives/chat-typing-indicator/chat-typing-indicator.component.ts
index 7f835016f..39fea580b 100644
--- a/libs/chat/src/lib/primitives/chat-typing-indicator/chat-typing-indicator.component.ts
+++ b/libs/chat/src/lib/primitives/chat-typing-indicator/chat-typing-indicator.component.ts
@@ -5,10 +5,21 @@ import {
input,
ChangeDetectionStrategy,
} from '@angular/core';
-import type { AgentRef } from '@cacheplane/langgraph';
+import type { ChatAgent } from '../../agent';
-export function isTyping(ref: AgentRef): boolean {
- return ref.isLoading();
+export function isTyping(agent: ChatAgent): boolean {
+ if (!agent.isLoading()) return false;
+ const msgs = agent.messages();
+ if (msgs.length === 0) return true;
+ const last = msgs[msgs.length - 1];
+ if (last.role === 'user') return true;
+ if (last.role === 'assistant') {
+ // Empty assistant message: string is empty or content block array is empty
+ return typeof last.content === 'string'
+ ? !last.content
+ : last.content.length === 0;
+ }
+ return false;
}
@Component({
@@ -48,15 +59,6 @@ export function isTyping(ref: AgentRef): boolean {
`,
})
export class ChatTypingIndicatorComponent {
- readonly ref = input.required>();
- readonly visible = computed(() => {
- if (!this.ref().isLoading()) return false;
- const msgs = this.ref().messages();
- if (msgs.length === 0) return true;
- const last = msgs[msgs.length - 1];
- const type = typeof last._getType === 'function'
- ? last._getType()
- : (last as unknown as Record)['type'] as string;
- return type !== 'ai';
- });
+ readonly agent = input.required();
+ readonly visible = computed(() => isTyping(this.agent()));
}
From fdd8108f5892b21f5a1b8057a66f67a1b4fce90e Mon Sep 17 00:00:00 2001
From: Brian Love
Date: Tue, 21 Apr 2026 13:50:35 -0700
Subject: [PATCH 23/40] refactor(chat): migrate chat-error to ChatAgent
contract
Co-Authored-By: Claude Sonnet 4.6
---
.../chat-error/chat-error.component.spec.ts | 34 +++++++++----------
.../chat-error/chat-error.component.ts | 6 ++--
2 files changed, 20 insertions(+), 20 deletions(-)
diff --git a/libs/chat/src/lib/primitives/chat-error/chat-error.component.spec.ts b/libs/chat/src/lib/primitives/chat-error/chat-error.component.spec.ts
index 21eb2fb69..9ff0844e0 100644
--- a/libs/chat/src/lib/primitives/chat-error/chat-error.component.spec.ts
+++ b/libs/chat/src/lib/primitives/chat-error/chat-error.component.spec.ts
@@ -2,7 +2,7 @@
import { describe, it, expect } from 'vitest';
import { signal, computed } from '@angular/core';
import { extractErrorMessage } from './chat-error.component';
-import { createMockAgentRef } from '../../testing/mock-agent-ref';
+import { mockChatAgent } from '../../testing/mock-chat-agent';
describe('extractErrorMessage()', () => {
it('returns null for null error', () => {
@@ -27,42 +27,42 @@ describe('extractErrorMessage()', () => {
});
describe('ChatErrorComponent — errorMessage computed', () => {
- it('errorMessage is null when ref.error is null', () => {
- const mockRef = createMockAgentRef({ error: null });
- const ref$ = signal(mockRef);
+ it('errorMessage is null when agent.error is null', () => {
+ const agent = mockChatAgent({ error: null });
+ const agent$ = signal(agent);
- const errorMessage = computed(() => extractErrorMessage(ref$().error()));
+ const errorMessage = computed(() => extractErrorMessage(agent$().error()));
expect(errorMessage()).toBeNull();
});
it('errorMessage reflects Error object message', () => {
- const mockRef = createMockAgentRef({ error: new Error('boom') });
- const ref$ = signal(mockRef);
+ const agent = mockChatAgent({ status: 'error', error: new Error('boom') });
+ const agent$ = signal(agent);
- const errorMessage = computed(() => extractErrorMessage(ref$().error()));
+ const errorMessage = computed(() => extractErrorMessage(agent$().error()));
expect(errorMessage()).toBe('boom');
});
it('errorMessage reflects string error', () => {
- const mockRef = createMockAgentRef({ error: 'timeout' });
- const ref$ = signal(mockRef);
+ const agent = mockChatAgent({ error: 'timeout' });
+ const agent$ = signal(agent);
- const errorMessage = computed(() => extractErrorMessage(ref$().error()));
+ const errorMessage = computed(() => extractErrorMessage(agent$().error()));
expect(errorMessage()).toBe('timeout');
});
- it('errorMessage updates reactively when ref changes', () => {
- const noErrorRef = createMockAgentRef({ error: null });
- const errorRef = createMockAgentRef({ error: new Error('failed') });
- const ref$ = signal(noErrorRef);
+ it('errorMessage updates reactively when agent changes', () => {
+ const noErrorAgent = mockChatAgent({ error: null });
+ const errorAgent = mockChatAgent({ status: 'error', error: new Error('failed') });
+ const agent$ = signal(noErrorAgent);
- const errorMessage = computed(() => extractErrorMessage(ref$().error()));
+ const errorMessage = computed(() => extractErrorMessage(agent$().error()));
expect(errorMessage()).toBeNull();
- ref$.set(errorRef);
+ agent$.set(errorAgent);
expect(errorMessage()).toBe('failed');
});
});
diff --git a/libs/chat/src/lib/primitives/chat-error/chat-error.component.ts b/libs/chat/src/lib/primitives/chat-error/chat-error.component.ts
index 741dd7a55..75c6965d2 100644
--- a/libs/chat/src/lib/primitives/chat-error/chat-error.component.ts
+++ b/libs/chat/src/lib/primitives/chat-error/chat-error.component.ts
@@ -5,7 +5,7 @@ import {
input,
ChangeDetectionStrategy,
} from '@angular/core';
-import type { AgentRef } from '@cacheplane/langgraph';
+import type { ChatAgent } from '../../agent';
export function extractErrorMessage(error: unknown): string | null {
if (!error) return null;
@@ -29,6 +29,6 @@ export function extractErrorMessage(error: unknown): string | null {
`,
})
export class ChatErrorComponent {
- readonly ref = input.required>();
- readonly errorMessage = computed(() => extractErrorMessage(this.ref().error()));
+ readonly agent = input.required();
+ readonly errorMessage = computed(() => extractErrorMessage(this.agent().error()));
}
From 718ffd84e4cb661a9b923a8fd6b7dc06a3ab5b64 Mon Sep 17 00:00:00 2001
From: Brian Love
Date: Tue, 21 Apr 2026 13:54:45 -0700
Subject: [PATCH 24/40] refactor(chat): drop redundant tool_use cast; rely on
discriminant narrowing
Co-Authored-By: Claude Opus 4.7
---
.../primitives/chat-tool-calls/chat-tool-calls.component.ts | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/libs/chat/src/lib/primitives/chat-tool-calls/chat-tool-calls.component.ts b/libs/chat/src/lib/primitives/chat-tool-calls/chat-tool-calls.component.ts
index 44f30d2c3..ed5dff2a9 100644
--- a/libs/chat/src/lib/primitives/chat-tool-calls/chat-tool-calls.component.ts
+++ b/libs/chat/src/lib/primitives/chat-tool-calls/chat-tool-calls.component.ts
@@ -35,9 +35,7 @@ export class ChatToolCallsComponent {
readonly toolCalls = computed((): ChatToolCall[] => {
const msg = this.message();
if (msg && msg.role === 'assistant' && Array.isArray(msg.content)) {
- const blocks = msg.content.filter(b => b.type === 'tool_use') as Array<{
- type: 'tool_use'; id: string; name: string; args: unknown;
- }>;
+ const blocks = msg.content.filter((b) => b.type === 'tool_use');
const all = this.agent().toolCalls();
return blocks
.map(b => all.find(tc => tc.id === b.id))
From d7cea2f4790f4fb953b8e4fb11fa431419ea91ac Mon Sep 17 00:00:00 2001
From: Brian Love
Date: Tue, 21 Apr 2026 13:58:02 -0700
Subject: [PATCH 25/40] refactor(chat): migrate chat composition core path to
ChatAgent
Replaces the required `ref: AgentRef` input with `agent: ChatAgent` on the
chat composition. Adds an optional `langgraphRef: AgentRef | undefined` escape
hatch for chat-interrupt and customEvents (phase-1 only) guarded by @if.
Updates onA2uiAction to use ChatSubmitInput.message shape.
Co-Authored-By: Claude Sonnet 4.6
---
.../compositions/chat/chat.component.spec.ts | 20 +++++++
.../lib/compositions/chat/chat.component.ts | 57 ++++++++++++-------
2 files changed, 56 insertions(+), 21 deletions(-)
diff --git a/libs/chat/src/lib/compositions/chat/chat.component.spec.ts b/libs/chat/src/lib/compositions/chat/chat.component.spec.ts
index 1216b823b..2c17f1d31 100644
--- a/libs/chat/src/lib/compositions/chat/chat.component.spec.ts
+++ b/libs/chat/src/lib/compositions/chat/chat.component.spec.ts
@@ -5,6 +5,7 @@ import { HumanMessage, AIMessage } from '@langchain/core/messages';
import { ChatComponent } from './chat.component';
import { messageContent } from '../shared/message-utils';
import { createContentClassifier, type ContentClassifier } from '../../streaming/content-classifier';
+import { mockChatAgent } from '../../testing/mock-chat-agent';
describe('ChatComponent', () => {
it('is defined as a class', () => {
@@ -31,6 +32,25 @@ describe('ChatComponent', () => {
});
});
+describe('ChatComponent — onA2uiAction', () => {
+ it('submits the action message as a JSON string via ChatAgent', () => {
+ TestBed.configureTestingModule({});
+ TestBed.runInInjectionContext(() => {
+ const agent = mockChatAgent();
+
+ // Instantiate a minimal ChatComponent-like object to test onA2uiAction logic
+ // without a full DOM fixture (the component requires [agent] input which can't
+ // be set before construction via TestBed.createComponent for required inputs).
+ // We test the logic directly mirroring the implementation.
+ const actionMessage = { type: 'button_click', payload: { id: 'btn-1' } } as any;
+ void agent.submit({ message: JSON.stringify(actionMessage) });
+
+ expect(agent.submitCalls).toHaveLength(1);
+ expect(agent.submitCalls[0].input).toEqual({ message: JSON.stringify(actionMessage) });
+ });
+ });
+});
+
describe('ChatComponent — content classification', () => {
it('classifyMessage creates a classifier on first call and caches it', () => {
TestBed.configureTestingModule({});
diff --git a/libs/chat/src/lib/compositions/chat/chat.component.ts b/libs/chat/src/lib/compositions/chat/chat.component.ts
index da9d9f5ee..6f6f3a9ec 100644
--- a/libs/chat/src/lib/compositions/chat/chat.component.ts
+++ b/libs/chat/src/lib/compositions/chat/chat.component.ts
@@ -11,6 +11,7 @@ import {
ElementRef,
ChangeDetectionStrategy,
} from '@angular/core';
+import type { ChatAgent } from '../../agent';
import type { AgentRef } from '@cacheplane/langgraph';
import type { ViewRegistry, RenderEvent } from '@cacheplane/render';
import type { A2uiActionMessage } from '@cacheplane/a2ui';
@@ -97,7 +98,7 @@ import { KeyValuePipe } from '@angular/common';
aria-live="polite"
>
- @if (ref().messages().length === 0 && !ref().isLoading()) {
+ @if (agent().messages().length === 0 && !agent().isLoading()) {
}
-
+
@@ -134,7 +135,7 @@ import { KeyValuePipe } from '@angular/common';
class="chat-md break-words text-[length:var(--chat-font-size)] leading-[var(--chat-line-height)]"
style="color: var(--chat-text);"
[content]="md"
- [streaming]="ref().isLoading()"
+ [streaming]="agent().isLoading()"
/>
}
@@ -144,7 +145,7 @@ import { KeyValuePipe } from '@angular/common';
[registry]="renderRegistry()"
[store]="resolvedStore()"
[handlers]="handlers()"
- [loading]="ref().isLoading()"
+ [loading]="agent().isLoading()"
(events)="onSpecEvent($event, index)"
/>
}
@@ -184,29 +185,31 @@ import { KeyValuePipe } from '@angular/common';
-
+
-
-
-
-
Agent paused: {{ interrupt.value }}
-
-
-
+ @if (langgraphRef()) {
+
+
+
+
Agent paused: {{ interrupt.value }}
+
+
+
+ }
-
+
@@ -218,7 +221,18 @@ import { KeyValuePipe } from '@angular/common';
})
export class ChatComponent {
- readonly ref = input.required
>();
+ readonly agent = input.required();
+
+ /**
+ * TEMPORARY escape hatch for Phase-1: primitives not yet migrated
+ * (chat-interrupt, chat-subagents, a2ui surfaces relying on customEvents)
+ * still require an AgentRef. Pass the same underlying LangGraph ref alongside
+ * `agent`. Remove once Phase-2 migrates these primitives.
+ * TODO(phase-2): remove langgraphRef input once chat-interrupt and
+ * customEvents are migrated to the ChatAgent contract.
+ */
+ readonly langgraphRef = input | undefined>(undefined);
+
readonly views = input(undefined);
readonly store = input(undefined);
readonly handlers = input) => unknown | Promise>>({});
@@ -256,16 +270,19 @@ export class ChatComponent {
private readonly scrollContainer = viewChild>('scrollContainer');
/** Track message count to trigger auto-scroll */
- private readonly messageCount = computed(() => this.ref().messages().length);
+ private readonly messageCount = computed(() => this.agent().messages().length);
private prevMessageCount = 0;
+ // TODO(phase-2): move state_update events onto the ChatAgent custom-event surface
/**
* Route `state_update` custom events from the agent stream to the render
* state store so that components bound to `$state` paths reactively update.
*/
protected readonly customEventEffect = effect(() => {
- const events = this.ref().customEvents();
+ const ref = this.langgraphRef();
+ if (!ref) return;
+ const events = ref.customEvents();
const store = this.resolvedStore();
if (!store || events.length === 0) return;
@@ -283,7 +300,7 @@ export class ChatComponent {
effect(() => {
const count = this.messageCount();
// Track last message content to trigger scroll during streaming partials
- const msgs = this.ref().messages();
+ const msgs = this.agent().messages();
const lastContent = msgs.length > 0
? (msgs[msgs.length - 1] as unknown as Record)['content']
: undefined;
@@ -326,9 +343,7 @@ export class ChatComponent {
}
onA2uiAction(message: A2uiActionMessage): void {
- this.ref().submit({
- messages: [{ role: 'human', content: JSON.stringify(message) }],
- });
+ void this.agent().submit({ message: JSON.stringify(message) });
}
onA2uiEvent(event: RenderEvent, messageIndex: number, surfaceId: string): void {
From c67a29c0722ed5725867271cf14c4f470c305e86 Mon Sep 17 00:00:00 2001
From: Brian Love
Date: Tue, 21 Apr 2026 14:08:14 -0700
Subject: [PATCH 26/40] chore(chat): mark remaining primitives as
phase-2/phase-3 migration
Co-Authored-By: Claude Sonnet 4.6
---
libs/chat/src/lib/agent/chat-agent.spec.ts | 8 ++++----
.../lib/compositions/chat-debug/chat-debug.component.ts | 1 +
.../chat-interrupt-panel.component.ts | 1 +
.../chat-timeline-slider.component.ts | 1 +
.../primitives/chat-interrupt/chat-interrupt.component.ts | 1 +
.../primitives/chat-subagents/chat-subagents.component.ts | 1 +
.../primitives/chat-timeline/chat-timeline.component.ts | 1 +
7 files changed, 10 insertions(+), 4 deletions(-)
diff --git a/libs/chat/src/lib/agent/chat-agent.spec.ts b/libs/chat/src/lib/agent/chat-agent.spec.ts
index dbf64bf8e..8660522e5 100644
--- a/libs/chat/src/lib/agent/chat-agent.spec.ts
+++ b/libs/chat/src/lib/agent/chat-agent.spec.ts
@@ -11,8 +11,8 @@ describe('ChatAgent interface', () => {
error: signal(null),
toolCalls: signal([]),
state: signal({}),
- submit: async () => {},
- stop: async () => {},
+ submit: async () => Promise.resolve(),
+ stop: async () => Promise.resolve(),
};
expect(agent.status()).toBe('idle');
});
@@ -27,8 +27,8 @@ describe('ChatAgent interface', () => {
state: signal({}),
interrupt: signal(undefined),
subagents: signal(new Map()),
- submit: async () => {},
- stop: async () => {},
+ submit: async () => Promise.resolve(),
+ stop: async () => Promise.resolve(),
};
expect(agent.interrupt?.()).toBeUndefined();
});
diff --git a/libs/chat/src/lib/compositions/chat-debug/chat-debug.component.ts b/libs/chat/src/lib/compositions/chat-debug/chat-debug.component.ts
index ba1ecc781..f31561004 100644
--- a/libs/chat/src/lib/compositions/chat-debug/chat-debug.component.ts
+++ b/libs/chat/src/lib/compositions/chat-debug/chat-debug.component.ts
@@ -1,4 +1,5 @@
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
+// TODO(phase-3): migrate from AgentRef to ChatAgent contract.
import {
Component,
computed,
diff --git a/libs/chat/src/lib/compositions/chat-interrupt-panel/chat-interrupt-panel.component.ts b/libs/chat/src/lib/compositions/chat-interrupt-panel/chat-interrupt-panel.component.ts
index 386f4ffea..b36380ca1 100644
--- a/libs/chat/src/lib/compositions/chat-interrupt-panel/chat-interrupt-panel.component.ts
+++ b/libs/chat/src/lib/compositions/chat-interrupt-panel/chat-interrupt-panel.component.ts
@@ -1,4 +1,5 @@
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
+// TODO(phase-2): migrate from AgentRef to ChatAgent contract.
import {
Component,
computed,
diff --git a/libs/chat/src/lib/compositions/chat-timeline-slider/chat-timeline-slider.component.ts b/libs/chat/src/lib/compositions/chat-timeline-slider/chat-timeline-slider.component.ts
index 348332b3b..64d956326 100644
--- a/libs/chat/src/lib/compositions/chat-timeline-slider/chat-timeline-slider.component.ts
+++ b/libs/chat/src/lib/compositions/chat-timeline-slider/chat-timeline-slider.component.ts
@@ -1,4 +1,5 @@
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
+// TODO(phase-3): migrate from AgentRef to ChatAgent contract.
import {
Component,
computed,
diff --git a/libs/chat/src/lib/primitives/chat-interrupt/chat-interrupt.component.ts b/libs/chat/src/lib/primitives/chat-interrupt/chat-interrupt.component.ts
index 9c90b0458..07650433b 100644
--- a/libs/chat/src/lib/primitives/chat-interrupt/chat-interrupt.component.ts
+++ b/libs/chat/src/lib/primitives/chat-interrupt/chat-interrupt.component.ts
@@ -1,4 +1,5 @@
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
+// TODO(phase-2): migrate from AgentRef to ChatAgent contract.
import {
Component,
computed,
diff --git a/libs/chat/src/lib/primitives/chat-subagents/chat-subagents.component.ts b/libs/chat/src/lib/primitives/chat-subagents/chat-subagents.component.ts
index 8a2fbb143..fe06b96f8 100644
--- a/libs/chat/src/lib/primitives/chat-subagents/chat-subagents.component.ts
+++ b/libs/chat/src/lib/primitives/chat-subagents/chat-subagents.component.ts
@@ -1,4 +1,5 @@
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
+// TODO(phase-2): migrate from AgentRef to ChatAgent contract.
import {
Component,
computed,
diff --git a/libs/chat/src/lib/primitives/chat-timeline/chat-timeline.component.ts b/libs/chat/src/lib/primitives/chat-timeline/chat-timeline.component.ts
index f2362e173..77984f44c 100644
--- a/libs/chat/src/lib/primitives/chat-timeline/chat-timeline.component.ts
+++ b/libs/chat/src/lib/primitives/chat-timeline/chat-timeline.component.ts
@@ -1,4 +1,5 @@
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
+// TODO(phase-3): migrate from AgentRef to ChatAgent contract.
import {
Component,
computed,
From 1121b74b22026047f99c1da30ca3e8ac40b0e07d Mon Sep 17 00:00:00 2001
From: Brian Love
Date: Tue, 21 Apr 2026 14:46:06 -0700
Subject: [PATCH 27/40] feat(chat): add ChatCustomEvent type and optional
customEvents$ to ChatAgent
Co-Authored-By: Claude Sonnet 4.6
---
libs/chat/src/lib/agent/chat-agent.ts | 13 ++++----
.../src/lib/agent/chat-custom-event.spec.ts | 30 +++++++++++++++++++
libs/chat/src/lib/agent/chat-custom-event.ts | 17 +++++++++++
libs/chat/src/lib/agent/index.ts | 1 +
4 files changed, 56 insertions(+), 5 deletions(-)
create mode 100644 libs/chat/src/lib/agent/chat-custom-event.spec.ts
create mode 100644 libs/chat/src/lib/agent/chat-custom-event.ts
diff --git a/libs/chat/src/lib/agent/chat-agent.ts b/libs/chat/src/lib/agent/chat-agent.ts
index 40460252a..911471dee 100644
--- a/libs/chat/src/lib/agent/chat-agent.ts
+++ b/libs/chat/src/lib/agent/chat-agent.ts
@@ -1,10 +1,12 @@
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
import type { Signal } from '@angular/core';
+import type { Observable } from 'rxjs';
import type { ChatMessage } from './chat-message';
import type { ChatToolCall } from './chat-tool-call';
import type { ChatStatus } from './chat-status';
import type { ChatInterrupt } from './chat-interrupt';
import type { ChatSubagent } from './chat-subagent';
+import type { ChatCustomEvent } from './chat-custom-event';
import type { ChatSubmitInput, ChatSubmitOptions } from './chat-submit';
/**
@@ -13,9 +15,9 @@ import type { ChatSubmitInput, ChatSubmitOptions } from './chat-submit';
* Implementations are produced by adapters (e.g. `@cacheplane/langgraph`,
* `@cacheplane/ag-ui`) or by user code for custom backends.
*
- * `interrupt` and `subagents` are optional: runtimes that do not support
- * these concepts should leave them undefined, and primitives that need them
- * check presence and render a neutral fallback when absent.
+ * `interrupt`, `subagents`, and `customEvents$` are optional: runtimes that
+ * do not support these concepts should leave them undefined, and primitives
+ * that need them check presence and render a neutral fallback when absent.
*/
export interface ChatAgent {
// Core state
@@ -31,6 +33,7 @@ export interface ChatAgent {
stop: () => Promise;
// Extended (optional; absent when runtime does not support)
- interrupt?: Signal;
- subagents?: Signal
- @if (langgraphRef()) {
-
+ @if (agent().interrupt) {
+
Agent paused: {{ interrupt.value }}
@@ -223,16 +225,6 @@ export class ChatComponent {
readonly agent = input.required
();
- /**
- * TEMPORARY escape hatch for Phase-1: primitives not yet migrated
- * (chat-interrupt, chat-subagents, a2ui surfaces relying on customEvents)
- * still require an AgentRef. Pass the same underlying LangGraph ref alongside
- * `agent`. Remove once Phase-2 migrates these primitives.
- * TODO(phase-2): remove langgraphRef input once chat-interrupt and
- * customEvents are migrated to the ChatAgent contract.
- */
- readonly langgraphRef = input | undefined>(undefined);
-
readonly views = input(undefined);
readonly store = input(undefined);
readonly handlers = input) => unknown | Promise>>({});
@@ -257,6 +249,9 @@ export class ChatComponent {
return undefined;
});
+ private readonly destroyRef = inject(DestroyRef);
+ private customEventsSubscribed = false;
+
private readonly classifiers = new Map();
/** Convert ViewRegistry → AngularRegistry for ChatGenerativeUiComponent. */
@@ -274,33 +269,52 @@ export class ChatComponent {
private prevMessageCount = 0;
- // TODO(phase-2): move state_update events onto the ChatAgent custom-event surface
- /**
- * Route `state_update` custom events from the agent stream to the render
- * state store so that components bound to `$state` paths reactively update.
- */
- protected readonly customEventEffect = effect(() => {
- const ref = this.langgraphRef();
- if (!ref) return;
- const events = ref.customEvents();
- const store = this.resolvedStore();
- if (!store || events.length === 0) return;
-
- for (const event of events) {
- if (event.name === 'state_update' && event.data && typeof event.data === 'object') {
- store.update(event.data as Record);
+ constructor() {
+ // Route `state_update` custom events from the agent stream to the render
+ // state store so components bound to `$state` paths reactively update.
+ // customEvents$ is optional — runtimes without custom-event support leave
+ // it undefined and this wiring becomes a no-op after the first effect run.
+ // Guard with customEventsSubscribed so we subscribe at most once even if
+ // the effect re-runs due to other reactive reads. We only set the flag
+ // after successfully reading the required `agent` input, so it remains
+ // false until Angular has satisfied the required-input contract.
+ effect(() => {
+ if (this.customEventsSubscribed) return;
+ let agent: ReturnType;
+ try {
+ agent = this.agent();
+ } catch {
+ // Required input not yet available — skip this run; effect will retry.
+ return;
}
- }
- });
+ this.customEventsSubscribed = true;
+ const stream$ = agent.customEvents$;
+ if (!stream$) return;
+ stream$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((event) => {
+ if (event.type !== 'state_update') return;
+ const data = event['data'];
+ if (!data || typeof data !== 'object') return;
+ const store = this.resolvedStore();
+ if (!store) return;
+ store.update(data as Record);
+ });
+ });
- constructor() {
// Auto-scroll to bottom:
// - Always scroll when message count increases (new message sent/received)
// - During streaming partials, only scroll if user is near bottom
effect(() => {
- const count = this.messageCount();
+ // Guard against required `agent` input not yet being set (can fire
+ // during initial change detection before input signals are populated).
+ let count: number;
+ let msgs: ReturnType['messages']>;
+ try {
+ count = this.messageCount();
+ msgs = this.agent().messages();
+ } catch {
+ return;
+ }
// Track last message content to trigger scroll during streaming partials
- const msgs = this.agent().messages();
const lastContent = msgs.length > 0
? (msgs[msgs.length - 1] as unknown as Record)['content']
: undefined;
From 6f2de674ca83f152acc37039871031e498f34fb1 Mon Sep 17 00:00:00 2001
From: Brian Love
Date: Tue, 21 Apr 2026 15:47:55 -0700
Subject: [PATCH 32/40] refactor(chat): migrate chat-subagents from AgentRef to
ChatAgent
Co-Authored-By: Claude Sonnet 4.6
---
.../chat-subagents.component.spec.ts | 100 ++++++++++--------
.../chat-subagents.component.ts | 27 ++++-
2 files changed, 80 insertions(+), 47 deletions(-)
diff --git a/libs/chat/src/lib/primitives/chat-subagents/chat-subagents.component.spec.ts b/libs/chat/src/lib/primitives/chat-subagents/chat-subagents.component.spec.ts
index eb9b99c69..5c13624df 100644
--- a/libs/chat/src/lib/primitives/chat-subagents/chat-subagents.component.spec.ts
+++ b/libs/chat/src/lib/primitives/chat-subagents/chat-subagents.component.spec.ts
@@ -1,55 +1,71 @@
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
import { describe, it, expect } from 'vitest';
-import { signal, computed } from '@angular/core';
-import { createMockAgentRef } from '../../testing/mock-agent-ref';
-import type { SubagentStreamRef } from '@cacheplane/langgraph';
+import { signal, computed, type WritableSignal } from '@angular/core';
+import { activeSubagentsFromAgent } from './chat-subagents.component';
+import { mockChatAgent } from '../../testing/mock-chat-agent';
+import type { ChatSubagent, ChatSubagentStatus } from '../../agent/chat-subagent';
-describe('ChatSubagentsComponent — activeSubagents computed', () => {
- it('returns empty array when no active subagents', () => {
- const mockRef = createMockAgentRef();
- const ref$ = signal(mockRef);
+function makeSubagent(toolCallId: string, status: ChatSubagentStatus): ChatSubagent {
+ return {
+ toolCallId,
+ status: signal(status),
+ messages: signal([]),
+ state: signal({}),
+ };
+}
- const activeSubagents = computed(() => ref$().activeSubagents());
+describe('activeSubagentsFromAgent()', () => {
+ it('returns an empty array when agent does not expose subagents', () => {
+ const agent = mockChatAgent(); // no withSubagents
+ expect(activeSubagentsFromAgent(agent)).toEqual([]);
+ });
- expect(activeSubagents()).toHaveLength(0);
+ it('returns an empty array when the subagents map is empty', () => {
+ const agent = mockChatAgent({ withSubagents: true });
+ expect(activeSubagentsFromAgent(agent)).toEqual([]);
});
- it('returns active subagents from ref', () => {
- const mockSubagent: SubagentStreamRef = {
- id: 'sub_1',
- isLoading: signal(true),
- messages: signal([]),
- status: signal('running' as any),
- error: signal(null),
- } as any;
+ it('includes subagents with status pending or running', () => {
+ const agent = mockChatAgent({ withSubagents: true });
+ const pending = makeSubagent('tc-1', 'pending');
+ const running = makeSubagent('tc-2', 'running');
+ (agent.subagents as WritableSignal>).set(
+ new Map([['tc-1', pending], ['tc-2', running]]),
+ );
+ const active = activeSubagentsFromAgent(agent);
+ expect(active).toHaveLength(2);
+ expect(active).toContain(pending);
+ expect(active).toContain(running);
+ });
- const mockRef = createMockAgentRef();
- (mockRef.activeSubagents as ReturnType>).set([mockSubagent]);
+ it('excludes subagents with status complete or error', () => {
+ const agent = mockChatAgent({ withSubagents: true });
+ const complete = makeSubagent('tc-1', 'complete');
+ const error = makeSubagent('tc-2', 'error');
+ const running = makeSubagent('tc-3', 'running');
+ (agent.subagents as WritableSignal>).set(
+ new Map([
+ ['tc-1', complete],
+ ['tc-2', error],
+ ['tc-3', running],
+ ]),
+ );
+ const active = activeSubagentsFromAgent(agent);
+ expect(active).toEqual([running]);
+ });
+});
- const ref$ = signal(mockRef);
- const activeSubagents = computed(() => ref$().activeSubagents());
+describe('ChatSubagentsComponent — activeSubagents computed', () => {
+ it('reflects the agent map and updates reactively', () => {
+ const agent = mockChatAgent({ withSubagents: true });
+ const writable = agent.subagents as WritableSignal>;
+ const running = makeSubagent('tc-1', 'running');
- expect(activeSubagents()).toHaveLength(1);
- expect(activeSubagents()[0]).toBe(mockSubagent);
- });
+ const agent$ = signal(agent);
+ const active = computed(() => activeSubagentsFromAgent(agent$()));
- it('activeSubagents updates reactively when ref changes', () => {
- const emptyRef = createMockAgentRef();
- const loadedRef = createMockAgentRef();
- const mockSubagent: SubagentStreamRef = {
- id: 'sub_2',
- isLoading: signal(false),
- messages: signal([]),
- status: signal('done' as any),
- error: signal(null),
- } as any;
- (loadedRef.activeSubagents as ReturnType>).set([mockSubagent]);
-
- const ref$ = signal(emptyRef);
- const activeSubagents = computed(() => ref$().activeSubagents());
-
- expect(activeSubagents()).toHaveLength(0);
- ref$.set(loadedRef);
- expect(activeSubagents()).toHaveLength(1);
+ expect(active()).toHaveLength(0);
+ writable.set(new Map([['tc-1', running]]));
+ expect(active()).toEqual([running]);
});
});
diff --git a/libs/chat/src/lib/primitives/chat-subagents/chat-subagents.component.ts b/libs/chat/src/lib/primitives/chat-subagents/chat-subagents.component.ts
index fe06b96f8..4251a411c 100644
--- a/libs/chat/src/lib/primitives/chat-subagents/chat-subagents.component.ts
+++ b/libs/chat/src/lib/primitives/chat-subagents/chat-subagents.component.ts
@@ -1,5 +1,4 @@
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
-// TODO(phase-2): migrate from AgentRef to ChatAgent contract.
import {
Component,
computed,
@@ -9,7 +8,25 @@ import {
ChangeDetectionStrategy,
} from '@angular/core';
import { NgTemplateOutlet } from '@angular/common';
-import type { AgentRef, SubagentStreamRef } from '@cacheplane/langgraph';
+import type { ChatAgent } from '../../agent';
+import type { ChatSubagent } from '../../agent/chat-subagent';
+
+/**
+ * Returns the list of currently-active subagents on the agent. "Active" means
+ * the subagent status is neither `complete` nor `error`. Returns an empty list
+ * when the runtime does not expose a subagents surface.
+ * Exported for unit testing without DOM rendering.
+ */
+export function activeSubagentsFromAgent(agent: ChatAgent): ChatSubagent[] {
+ const map = agent.subagents?.();
+ if (!map) return [];
+ const out: ChatSubagent[] = [];
+ map.forEach((sa) => {
+ const s = sa.status();
+ if (s !== 'complete' && s !== 'error') out.push(sa);
+ });
+ return out;
+}
@Component({
selector: 'chat-subagents',
@@ -17,7 +34,7 @@ import type { AgentRef, SubagentStreamRef } from '@cacheplane/langgraph';
imports: [NgTemplateOutlet],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
- @for (subagent of activeSubagents(); track $index) {
+ @for (subagent of activeSubagents(); track subagent.toolCallId) {
@if (templateRef()) {
>();
+ readonly agent = input.required();
readonly templateRef = contentChild(TemplateRef);
- readonly activeSubagents = computed((): SubagentStreamRef[] => this.ref().activeSubagents());
+ readonly activeSubagents = computed(() => activeSubagentsFromAgent(this.agent()));
}
From 470c8f3a5c0a536cf78e61e9e7b9276cdcca4f80 Mon Sep 17 00:00:00 2001
From: Brian Love
Date: Tue, 21 Apr 2026 15:52:40 -0700
Subject: [PATCH 33/40] refactor(chat): migrate chat-subagent-card to
ChatSubagent contract
Swaps SubagentStreamRef from @cacheplane/langgraph to the runtime-neutral
ChatSubagent type from libs/chat/src/lib/agent. Aligns with Phase-1
decoupling objective. Also simplifies status type by importing
ChatSubagentStatus directly.
Co-Authored-By: Claude Haiku 4.5
---
...-chat-custom-events-interrupt-migration.md | 971 ++++++++++++++++++
...-langgraph-specific-primitives-location.md | 54 +
.../chat-subagent-card.component.ts | 8 +-
3 files changed, 1028 insertions(+), 5 deletions(-)
create mode 100644 docs/superpowers/plans/2026-04-21-chat-custom-events-interrupt-migration.md
create mode 100644 docs/superpowers/specs/2026-04-21-langgraph-specific-primitives-location.md
diff --git a/docs/superpowers/plans/2026-04-21-chat-custom-events-interrupt-migration.md b/docs/superpowers/plans/2026-04-21-chat-custom-events-interrupt-migration.md
new file mode 100644
index 000000000..4517e841f
--- /dev/null
+++ b/docs/superpowers/plans/2026-04-21-chat-custom-events-interrupt-migration.md
@@ -0,0 +1,971 @@
+# Chat Custom Events + Interrupt Migration 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:** Complete the runtime-decoupling by migrating `chat-interrupt` to `ChatAgent` and adding an observable `customEvents$` surface to `ChatAgent`, so the `chat` composition can drop its temporary `langgraphRef` escape hatch — which breaks the `chat ↔ langgraph` circular package dependency.
+
+**Architecture:**
+- `chat-interrupt` is mechanically swapped from `AgentRef` input to `ChatAgent` input, reading `agent.interrupt()` which already exists in the contract.
+- `ChatAgent` gains an optional `customEvents$?: Observable`. `ChatCustomEvent` is a loose-discriminator shape: `{ readonly type: string; readonly [key: string]: unknown }`. This lets AG-UI events pass through natively, a2ui/json-render namespace their own event types, and only requires `toChatAgent` to alias `name → type` when adapting LangGraph's `CustomStreamEvent`.
+- `toChatAgent` converts LangGraph's `Signal` into `Observable` by watching the signal via `effect()` and emitting only newly-appended events through a `Subject`.
+- `chat.component.ts` replaces its `langgraphRef`-based `effect()` with a `takeUntilDestroyed()` subscription on `agent.customEvents$` and deletes the `langgraphRef` input entirely. Once that import is gone, `libs/chat/package.json` can drop its `@cacheplane/langgraph` peer-dep, completing the decoupling.
+
+**Tech Stack:** Angular 20 signals + `rxjs-interop`, RxJS `Subject`/`Observable`, Nx monorepo, Jest, `@langchain/langgraph-sdk`, `@cacheplane/chat`, `@cacheplane/langgraph`.
+
+---
+
+## File Structure
+
+**New files:**
+- `libs/chat/src/lib/agent/chat-custom-event.ts` — `ChatCustomEvent` type definition
+- `libs/chat/src/lib/agent/chat-custom-event.spec.ts` — type/shape tests
+
+**Modified files:**
+- `libs/chat/src/lib/agent/chat-agent.ts` — add `customEvents$?` field
+- `libs/chat/src/lib/agent/index.ts` — export `ChatCustomEvent`
+- `libs/chat/src/lib/testing/mock-chat-agent.ts` — wire optional `customEvents$` option
+- `libs/chat/src/lib/testing/chat-agent-conformance.ts` — conformance check for `customEvents$` when present
+- `libs/chat/src/lib/primitives/chat-interrupt/chat-interrupt.component.ts` — `ref: AgentRef` → `agent: ChatAgent`
+- `libs/chat/src/lib/primitives/chat-interrupt/chat-interrupt.component.spec.ts` — update tests
+- `libs/chat/src/lib/compositions/chat/chat.component.ts` — drop `langgraphRef`, swap effect for subscription
+- `libs/chat/src/lib/compositions/chat/chat.component.spec.ts` — update / add tests as applicable
+- `libs/chat/package.json` — remove `@cacheplane/langgraph` peer-dep
+- `libs/langgraph/src/lib/to-chat-agent.ts` — implement `customEvents$` in adapter
+- `libs/langgraph/src/lib/to-chat-agent.spec.ts` — add tests for `customEvents$`
+- `libs/langgraph/src/lib/to-chat-agent.conformance.spec.ts` — add customEvents$ to fixture if conformance requires it
+
+**Deleted:**
+- None
+
+---
+
+## Task 1: Define `ChatCustomEvent` type and add to contract
+
+**Files:**
+- Create: `libs/chat/src/lib/agent/chat-custom-event.ts`
+- Create: `libs/chat/src/lib/agent/chat-custom-event.spec.ts`
+- Modify: `libs/chat/src/lib/agent/chat-agent.ts`
+- Modify: `libs/chat/src/lib/agent/index.ts`
+
+- [ ] **Step 1: Write failing test for ChatCustomEvent type**
+
+Create `libs/chat/src/lib/agent/chat-custom-event.spec.ts`:
+
+```ts
+// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
+import type { ChatCustomEvent } from './chat-custom-event';
+
+describe('ChatCustomEvent', () => {
+ it('accepts a minimal { type } event', () => {
+ const event: ChatCustomEvent = { type: 'state_update' };
+ expect(event.type).toBe('state_update');
+ });
+
+ it('accepts arbitrary additional fields via index signature', () => {
+ const event: ChatCustomEvent = {
+ type: 'a2ui.surface',
+ surfaceId: 'main',
+ payload: { foo: 'bar' },
+ timestamp: 1234567890,
+ };
+ expect(event['surfaceId']).toBe('main');
+ expect(event['payload']).toEqual({ foo: 'bar' });
+ });
+
+ it('allows AG-UI-shaped events to pass through without remapping', () => {
+ const agUiEvent: ChatCustomEvent = {
+ type: 'TEXT_MESSAGE_START',
+ messageId: 'msg-1',
+ role: 'assistant',
+ };
+ expect(agUiEvent.type).toBe('TEXT_MESSAGE_START');
+ expect(agUiEvent['messageId']).toBe('msg-1');
+ });
+});
+```
+
+- [ ] **Step 2: Run test to verify it fails**
+
+Run: `npx nx test chat --test-path-pattern=chat-custom-event`
+Expected: FAIL with "Cannot find module './chat-custom-event'".
+
+- [ ] **Step 3: Create `ChatCustomEvent` type**
+
+Create `libs/chat/src/lib/agent/chat-custom-event.ts`:
+
+```ts
+// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
+
+/**
+ * Runtime-neutral custom event shape flowing through `ChatAgent.customEvents$`.
+ *
+ * The only required field is `type` — a string discriminator consumers switch
+ * on. All other fields pass through verbatim from the source runtime, which
+ * lets AG-UI, LangGraph, a2ui, and json-render emit their own event shapes
+ * without the core contract owning their union.
+ *
+ * Adapters are responsible for normalising their native shape to include a
+ * `type` field (e.g., `toChatAgent` aliases LangGraph's `name` to `type`).
+ */
+export interface ChatCustomEvent {
+ readonly type: string;
+ readonly [key: string]: unknown;
+}
+```
+
+- [ ] **Step 4: Run test to verify it passes**
+
+Run: `npx nx test chat --test-path-pattern=chat-custom-event`
+Expected: PASS — 3 tests.
+
+- [ ] **Step 5: Add `customEvents$` to `ChatAgent` contract**
+
+Modify `libs/chat/src/lib/agent/chat-agent.ts`. Add import and field:
+
+```ts
+// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
+import type { Signal } from '@angular/core';
+import type { Observable } from 'rxjs';
+import type { ChatMessage } from './chat-message';
+import type { ChatToolCall } from './chat-tool-call';
+import type { ChatStatus } from './chat-status';
+import type { ChatInterrupt } from './chat-interrupt';
+import type { ChatSubagent } from './chat-subagent';
+import type { ChatCustomEvent } from './chat-custom-event';
+import type { ChatSubmitInput, ChatSubmitOptions } from './chat-submit';
+
+/**
+ * Runtime-neutral contract chat primitives consume.
+ *
+ * Implementations are produced by adapters (e.g. `@cacheplane/langgraph`,
+ * `@cacheplane/ag-ui`) or by user code for custom backends.
+ *
+ * `interrupt`, `subagents`, and `customEvents$` are optional: runtimes that
+ * do not support these concepts should leave them undefined, and primitives
+ * that need them check presence and render a neutral fallback when absent.
+ */
+export interface ChatAgent {
+ // Core state
+ messages: Signal;
+ status: Signal;
+ isLoading: Signal;
+ error: Signal;
+ toolCalls: Signal;
+ state: Signal>;
+
+ // Actions
+ submit: (input: ChatSubmitInput, opts?: ChatSubmitOptions) => Promise;
+ stop: () => Promise;
+
+ // Extended (optional; absent when runtime does not support)
+ interrupt?: Signal;
+ subagents?: Signal>;
+ customEvents$?: Observable;
+}
+```
+
+- [ ] **Step 6: Re-export `ChatCustomEvent` from chat agent barrel**
+
+Modify `libs/chat/src/lib/agent/index.ts` — add alongside existing exports:
+
+```ts
+export type { ChatCustomEvent } from './chat-custom-event';
+```
+
+(Preserve every existing export in that file; only add this one line.)
+
+- [ ] **Step 7: Verify chat builds and tests pass**
+
+Run: `npx nx test chat`
+Expected: PASS — all tests (existing + 3 new).
+
+Run: `npx nx build chat`
+Expected: SUCCESS (the chat↔langgraph circular dep is still present from Phase-1, but build will emit warnings, not hard-fail; record output).
+
+- [ ] **Step 8: Commit**
+
+```bash
+git add libs/chat/src/lib/agent/chat-custom-event.ts \
+ libs/chat/src/lib/agent/chat-custom-event.spec.ts \
+ libs/chat/src/lib/agent/chat-agent.ts \
+ libs/chat/src/lib/agent/index.ts
+git commit -m "feat(chat): add ChatCustomEvent type and optional customEvents\$ to ChatAgent"
+```
+
+---
+
+## Task 2: Extend testing helpers with `customEvents$` support
+
+**Files:**
+- Modify: `libs/chat/src/lib/testing/mock-chat-agent.ts`
+- Modify: `libs/chat/src/lib/testing/chat-agent-conformance.ts`
+
+- [ ] **Step 1: Add `customEvents$` option to mockChatAgent and conformance check**
+
+First read `libs/chat/src/lib/testing/mock-chat-agent.ts` fully to preserve existing options and return shape. Then modify `MockChatAgentOptions` to include an optional `customEvents$` field and pass it through to the returned object.
+
+Patch the options interface (near the top of the file) to add:
+
+```ts
+import type { Observable } from 'rxjs';
+import type { ChatCustomEvent } from '../agent/chat-custom-event';
+
+// ... inside existing MockChatAgentOptions interface:
+customEvents$?: Observable;
+```
+
+Patch the returned object construction to pass `customEvents$` through when provided:
+
+```ts
+return {
+ // ... existing fields
+ ...(opts.customEvents$ ? { customEvents$: opts.customEvents$ } : {}),
+ submit: async (input, submitOpts) => { submitCalls.push({ input, opts: submitOpts }); },
+ stop: async () => { stopCount++; },
+ // ... existing trailing fields
+};
+```
+
+- [ ] **Step 2: Add conformance test for customEvents$ presence**
+
+Modify `libs/chat/src/lib/testing/chat-agent-conformance.ts`. Inside the existing `describe(${label} — ChatAgent conformance)` block, add this `it` at the end:
+
+```ts
+it('if customEvents$ is present, it is an Observable-like with .subscribe', () => {
+ const agent = factory();
+ if (agent.customEvents$ !== undefined) {
+ expect(typeof agent.customEvents$.subscribe).toBe('function');
+ } else {
+ expect(agent.customEvents$).toBeUndefined();
+ }
+});
+```
+
+- [ ] **Step 3: Run tests**
+
+Run: `npx nx test chat`
+Expected: PASS — all existing tests still pass, new conformance case runs in both paths (present/absent).
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add libs/chat/src/lib/testing/mock-chat-agent.ts \
+ libs/chat/src/lib/testing/chat-agent-conformance.ts
+git commit -m "test(chat): extend mock + conformance for customEvents\$"
+```
+
+---
+
+## Task 3: Implement `customEvents$` in `toChatAgent`
+
+**Files:**
+- Modify: `libs/langgraph/src/lib/to-chat-agent.ts`
+- Modify: `libs/langgraph/src/lib/to-chat-agent.spec.ts`
+- Modify: `libs/langgraph/src/lib/to-chat-agent.conformance.spec.ts`
+
+- [ ] **Step 1: Write failing test for customEvents$**
+
+Open `libs/langgraph/src/lib/to-chat-agent.spec.ts` and add a new test near the existing tests (do not delete existing tests). Append inside the existing `describe` block:
+
+```ts
+it('exposes customEvents$ that emits newly-appended events with type aliased from name', async () => {
+ const TestBed_ = TestBed; // alias to keep import stable
+ const customSig = signal([]);
+ const ref = {
+ messages: signal([]),
+ toolCalls: signal([]),
+ status: signal(ResourceStatus.Idle),
+ isLoading: signal(false),
+ error: signal(null),
+ value: signal(null),
+ interrupt: signal | undefined>(undefined),
+ subagents: signal(new Map()),
+ customEvents: customSig,
+ submit: async () => undefined,
+ stop: async () => undefined,
+ } as unknown as AgentRef;
+
+ let adapter!: ChatAgent;
+ TestBed_.runInInjectionContext(() => {
+ adapter = toChatAgent(ref);
+ });
+
+ const received: ChatCustomEvent[] = [];
+ adapter.customEvents$!.subscribe((e) => received.push(e));
+
+ TestBed_.runInInjectionContext(() => {
+ customSig.set([{ name: 'state_update', data: { counter: 1 } }]);
+ TestBed_.flushEffects();
+ });
+
+ expect(received).toEqual([
+ { type: 'state_update', data: { counter: 1 } },
+ ]);
+
+ TestBed_.runInInjectionContext(() => {
+ customSig.set([
+ { name: 'state_update', data: { counter: 1 } },
+ { name: 'a2ui.surface', data: { surfaceId: 'main' } },
+ ]);
+ TestBed_.flushEffects();
+ });
+
+ expect(received).toEqual([
+ { type: 'state_update', data: { counter: 1 } },
+ { type: 'a2ui.surface', data: { surfaceId: 'main' } },
+ ]);
+});
+```
+
+If the spec file does not already import `signal`, `TestBed`, `ChatCustomEvent`, or `ChatAgent`, add:
+
+```ts
+import { signal } from '@angular/core';
+import { TestBed } from '@angular/core/testing';
+import type { ChatAgent, ChatCustomEvent } from '@cacheplane/chat';
+```
+
+- [ ] **Step 2: Run test to verify it fails**
+
+Run: `npx nx test langgraph --test-path-pattern=to-chat-agent`
+Expected: FAIL — `adapter.customEvents$` is `undefined`.
+
+- [ ] **Step 3: Implement customEvents$ in the adapter**
+
+Modify `libs/langgraph/src/lib/to-chat-agent.ts`. Replace the full file with:
+
+```ts
+// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
+import { computed, effect, Signal } from '@angular/core';
+import { Subject, type Observable } from 'rxjs';
+import type { BaseMessage } from '@langchain/core/messages';
+import type { ToolCallWithResult, Interrupt } from '@langchain/langgraph-sdk';
+import type {
+ ChatAgent,
+ ChatCustomEvent,
+ ChatMessage,
+ ChatRole,
+ ChatStatus,
+ ChatToolCall,
+ ChatToolCallStatus,
+ ChatInterrupt,
+ ChatSubagent,
+ ChatSubmitInput,
+ ChatSubmitOptions,
+} from '@cacheplane/chat';
+import type { AgentRef, CustomStreamEvent, SubagentStreamRef } from './agent.types';
+import { ResourceStatus } from './agent.types';
+
+/**
+ * Adapts a LangGraph AgentRef to the runtime-neutral ChatAgent contract.
+ * The returned object is a live view; it reads from the same signals and
+ * writes back via AgentRef.submit / AgentRef.stop.
+ *
+ * Must be called within an Angular injection context (uses `computed` and
+ * `effect`).
+ */
+export function toChatAgent(ref: AgentRef): ChatAgent {
+ const messages = computed(() =>
+ ref.messages().map(toChatMessage),
+ );
+
+ const toolCalls = computed(() =>
+ ref.toolCalls().map(toChatToolCall),
+ );
+
+ const status = computed(() => mapStatus(ref.status()));
+
+ const state = computed>(() => {
+ const v = ref.value();
+ return v && typeof v === 'object' ? (v as Record) : {};
+ });
+
+ const interrupt = computed(() => {
+ const ix = ref.interrupt();
+ return ix ? toChatInterrupt(ix) : undefined;
+ });
+
+ const subagents = computed>(() => {
+ const src = ref.subagents();
+ const out = new Map();
+ src.forEach((sa, key) => out.set(key, toChatSubagent(sa)));
+ return out;
+ });
+
+ const customEvents$ = buildCustomEvents$(ref);
+
+ return {
+ messages,
+ status,
+ isLoading: ref.isLoading,
+ error: ref.error,
+ toolCalls,
+ state,
+ interrupt,
+ subagents,
+ customEvents$,
+ submit: (input: ChatSubmitInput, opts?: ChatSubmitOptions) =>
+ ref.submit(buildSubmitPayload(input), opts ? { signal: opts.signal } as never : undefined),
+ stop: () => ref.stop(),
+ };
+}
+
+/**
+ * Build an Observable that bridges LangGraph's
+ * `Signal` (append-only array) into a stream of newly
+ * emitted events. Each effect firing compares against a cursor tracking the
+ * previously-seen length and emits only the tail slice.
+ */
+function buildCustomEvents$(
+ ref: AgentRef,
+): Observable {
+ const subject = new Subject();
+ let seen = 0;
+ effect(() => {
+ const all = ref.customEvents();
+ if (all.length < seen) {
+ // Stream reset (new session, thread switch, etc.). Rewind cursor.
+ seen = 0;
+ }
+ for (let i = seen; i < all.length; i++) {
+ subject.next(toChatCustomEvent(all[i]));
+ }
+ seen = all.length;
+ });
+ return subject.asObservable();
+}
+
+function toChatCustomEvent(e: CustomStreamEvent): ChatCustomEvent {
+ return { type: e.name, data: e.data };
+}
+
+function mapStatus(s: ResourceStatus): ChatStatus {
+ switch (s) {
+ case ResourceStatus.Error: return 'error';
+ case ResourceStatus.Loading:
+ case ResourceStatus.Reloading:
+ return 'running';
+ default:
+ return 'idle';
+ }
+}
+
+function toChatMessage(m: BaseMessage): ChatMessage {
+ const raw = m as unknown as Record;
+ const typeVal = typeof m._getType === 'function'
+ ? m._getType()
+ : (raw['type'] as string | undefined) ?? 'ai';
+ const role: ChatRole =
+ typeVal === 'human' ? 'user' :
+ typeVal === 'tool' ? 'tool' :
+ typeVal === 'system' ? 'system' :
+ 'assistant';
+ return {
+ id: (m.id as string | undefined) ?? (raw['id'] as string | undefined) ?? randomId(),
+ role,
+ content: typeof m.content === 'string' ? m.content : JSON.stringify(m.content),
+ toolCallId: raw['tool_call_id'] as string | undefined,
+ name: raw['name'] as string | undefined,
+ extra: raw,
+ };
+}
+
+function toChatToolCall(tc: ToolCallWithResult): ChatToolCall {
+ const stateMap: Record = {
+ pending: 'pending',
+ completed: 'complete',
+ error: 'error',
+ };
+ const status: ChatToolCallStatus = stateMap[tc.state] ?? 'running';
+ const result = tc.result as (Record | undefined);
+ return {
+ id: tc.id,
+ name: tc.call.name,
+ args: tc.call.args,
+ status,
+ result: result?.['content'],
+ error: tc.state === 'error' ? result?.['content'] : undefined,
+ };
+}
+
+function toChatInterrupt(ix: Interrupt): ChatInterrupt {
+ const raw = ix as unknown as Record;
+ return {
+ id: (raw['id'] as string | undefined) ?? randomId(),
+ value: raw['value'] ?? ix,
+ resumable: true,
+ };
+}
+
+function toChatSubagent(sa: SubagentStreamRef): ChatSubagent {
+ return {
+ toolCallId: sa.toolCallId,
+ status: sa.status,
+ messages: computed(() => sa.messages().map(toChatMessage)) as Signal,
+ state: sa.values as Signal>,
+ };
+}
+
+function buildSubmitPayload(input: ChatSubmitInput): unknown {
+ if (input.resume !== undefined) return { __resume__: input.resume };
+ if (input.message !== undefined) {
+ const content = typeof input.message === 'string'
+ ? input.message
+ : input.message.map((b) => (b.type === 'text' ? b.text : JSON.stringify(b))).join('');
+ return { messages: [{ role: 'human', content }], ...(input.state ?? {}) };
+ }
+ return input.state ?? {};
+}
+
+function randomId(): string {
+ return Math.random().toString(36).slice(2);
+}
+```
+
+- [ ] **Step 4: Run test to verify it passes**
+
+Run: `npx nx test langgraph --test-path-pattern=to-chat-agent`
+Expected: PASS — new test plus existing tests.
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add libs/langgraph/src/lib/to-chat-agent.ts \
+ libs/langgraph/src/lib/to-chat-agent.spec.ts
+git commit -m "feat(langgraph): bridge customEvents signal to Observable in toChatAgent"
+```
+
+---
+
+## Task 4: Migrate `chat-interrupt` primitive to `ChatAgent`
+
+**Files:**
+- Modify: `libs/chat/src/lib/primitives/chat-interrupt/chat-interrupt.component.ts`
+- Modify: `libs/chat/src/lib/primitives/chat-interrupt/chat-interrupt.component.spec.ts`
+
+- [ ] **Step 1: Update spec to use mockChatAgent and `agent` input**
+
+First read the existing `chat-interrupt.component.spec.ts` fully to preserve test names and scaffolding. Then rewrite the body so each test builds a `ChatAgent` via `mockChatAgent({ interrupt: signal() })` and binds it to the component's new `agent` input. For example, the smallest test body should be:
+
+```ts
+// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
+import { signal } from '@angular/core';
+import { TestBed } from '@angular/core/testing';
+import { ChatInterruptComponent, getInterrupt } from './chat-interrupt.component';
+import { mockChatAgent } from '../../testing/mock-chat-agent';
+import type { ChatInterrupt } from '../../agent/chat-interrupt';
+
+describe('ChatInterruptComponent', () => {
+ it('getInterrupt returns the agent interrupt signal value', () => {
+ const ix: ChatInterrupt = { id: 'ix-1', value: 'Need approval', resumable: true };
+ const agent = mockChatAgent({ interrupt: signal(ix) });
+ expect(getInterrupt(agent)).toEqual(ix);
+ });
+
+ it('getInterrupt returns undefined when runtime does not expose interrupt', () => {
+ const agent = mockChatAgent({}); // no interrupt option
+ expect(getInterrupt(agent)).toBeUndefined();
+ });
+
+ it('renders the template with the current interrupt', async () => {
+ const ix: ChatInterrupt = { id: 'ix-1', value: 'Need approval', resumable: true };
+ const agent = mockChatAgent({ interrupt: signal(ix) });
+ TestBed.configureTestingModule({ imports: [ChatInterruptComponent] });
+ const fixture = TestBed.createComponent(ChatInterruptComponent);
+ fixture.componentRef.setInput('agent', agent);
+ // Preserve any existing template-projection assertion from the old spec
+ fixture.detectChanges();
+ expect(fixture.nativeElement.textContent).toBeDefined();
+ });
+});
+```
+
+If the existing spec has additional assertions (template projection specifics, resumable handling), **keep them** — only the agent-wiring lines change from `ref` to `agent`.
+
+If `mockChatAgent` does not yet accept an `interrupt` option, open `libs/chat/src/lib/testing/mock-chat-agent.ts` and confirm it does — this was established in Workstream B. If the option is missing, add it the same way Task 2 added `customEvents$`: an optional `interrupt?: Signal` on `MockChatAgentOptions`, spread into the return object conditionally.
+
+- [ ] **Step 2: Run test to verify it fails**
+
+Run: `npx nx test chat --test-path-pattern=chat-interrupt.component`
+Expected: FAIL — `agent` input doesn't exist yet, or `getInterrupt` signature mismatches.
+
+- [ ] **Step 3: Rewrite the component**
+
+Replace `libs/chat/src/lib/primitives/chat-interrupt/chat-interrupt.component.ts` with:
+
+```ts
+// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
+import {
+ Component,
+ computed,
+ contentChild,
+ input,
+ TemplateRef,
+ ChangeDetectionStrategy,
+} from '@angular/core';
+import { NgTemplateOutlet } from '@angular/common';
+import type { ChatAgent } from '../../agent';
+import type { ChatInterrupt } from '../../agent/chat-interrupt';
+
+/**
+ * Retrieves the current interrupt value from a ChatAgent, or undefined when
+ * the runtime does not expose interrupts.
+ * Exported for unit testing without DOM rendering.
+ */
+export function getInterrupt(agent: ChatAgent): ChatInterrupt | undefined {
+ return agent.interrupt?.();
+}
+
+@Component({
+ selector: 'chat-interrupt',
+ standalone: true,
+ imports: [NgTemplateOutlet],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ template: `
+ @if (interrupt(); as currentInterrupt) {
+ @if (templateRef()) {
+
+ }
+ }
+ `,
+})
+export class ChatInterruptComponent {
+ readonly agent = input.required();
+
+ readonly templateRef = contentChild(TemplateRef);
+
+ readonly interrupt = computed(() => getInterrupt(this.agent()));
+}
+```
+
+- [ ] **Step 4: Run test to verify it passes**
+
+Run: `npx nx test chat --test-path-pattern=chat-interrupt.component`
+Expected: PASS.
+
+- [ ] **Step 5: Full chat test run**
+
+Run: `npx nx test chat`
+Expected: PASS — all tests. (The `chat.component.ts` composition still passes `[ref]="langgraphRef()"` to ``, which will now fail type-check; that is fixed in Task 5. If the build is run between tasks it will fail — tests should still pass because they don't exercise the composition's template binding for interrupt.)
+
+If the composition template referencing the now-removed `ref` input causes a type-check failure during test compilation, skip Step 5 here and re-run after Task 5. Record the outcome.
+
+- [ ] **Step 6: Commit**
+
+```bash
+git add libs/chat/src/lib/primitives/chat-interrupt/chat-interrupt.component.ts \
+ libs/chat/src/lib/primitives/chat-interrupt/chat-interrupt.component.spec.ts \
+ libs/chat/src/lib/testing/mock-chat-agent.ts
+git commit -m "refactor(chat): migrate chat-interrupt from AgentRef to ChatAgent"
+```
+
+(If `mock-chat-agent.ts` was not touched, omit it from the `git add`.)
+
+---
+
+## Task 5: Migrate `chat.component.ts` — drop `langgraphRef`, subscribe to `customEvents$`
+
+**Files:**
+- Modify: `libs/chat/src/lib/compositions/chat/chat.component.ts`
+- Modify: `libs/chat/src/lib/compositions/chat/chat.component.spec.ts` (if a spec file exists for this composition)
+
+- [ ] **Step 1: Add a failing test for customEvents$ routing**
+
+Open `libs/chat/src/lib/compositions/chat/chat.component.spec.ts` (create if it does not exist — use the import style from the existing `chat-interrupt.component.spec.ts`). Add this test inside the existing `describe` or a new one:
+
+```ts
+import { signal } from '@angular/core';
+import { TestBed } from '@angular/core/testing';
+import { Subject } from 'rxjs';
+import { ChatComponent } from './chat.component';
+import { mockChatAgent } from '../../testing/mock-chat-agent';
+import type { ChatCustomEvent } from '../../agent/chat-custom-event';
+import { signalStateStore } from '@cacheplane/render';
+
+describe('ChatComponent customEvents$', () => {
+ it('routes state_update customEvents to the resolved render store', () => {
+ const events$ = new Subject();
+ const agent = mockChatAgent({ customEvents$: events$.asObservable() });
+ const store = signalStateStore({});
+
+ TestBed.configureTestingModule({ imports: [ChatComponent] });
+ const fixture = TestBed.createComponent(ChatComponent);
+ fixture.componentRef.setInput('agent', agent);
+ fixture.componentRef.setInput('store', store);
+ fixture.detectChanges();
+
+ events$.next({ type: 'state_update', data: { counter: 7 } });
+
+ expect(store.snapshot()).toMatchObject({ counter: 7 });
+ });
+
+ it('ignores non-state_update events and events with non-object data', () => {
+ const events$ = new Subject();
+ const agent = mockChatAgent({ customEvents$: events$.asObservable() });
+ const store = signalStateStore({ initial: true });
+
+ TestBed.configureTestingModule({ imports: [ChatComponent] });
+ const fixture = TestBed.createComponent(ChatComponent);
+ fixture.componentRef.setInput('agent', agent);
+ fixture.componentRef.setInput('store', store);
+ fixture.detectChanges();
+
+ events$.next({ type: 'a2ui.surface', data: { surfaceId: 'main' } });
+ events$.next({ type: 'state_update', data: 'not-an-object' });
+
+ expect(store.snapshot()).toEqual({ initial: true });
+ });
+});
+```
+
+If `signalStateStore().snapshot()` is not the actual public API for reading the store, replace `store.snapshot()` with whatever accessor the existing `customEventEffect` writes back to (check `libs/render` exports). If only `store.update()` exists, read the store's underlying signal via whatever getter the store exposes — do not invent one.
+
+- [ ] **Step 2: Run test to verify it fails**
+
+Run: `npx nx test chat --test-path-pattern=chat.component.spec`
+Expected: FAIL — `customEventEffect` still reads from `langgraphRef()`, so the subject events are never routed.
+
+- [ ] **Step 3: Rewrite the composition**
+
+Modify `libs/chat/src/lib/compositions/chat/chat.component.ts`. Apply these specific changes:
+
+**3a.** Remove imports that are no longer used:
+
+```diff
+-import type { AgentRef } from '@cacheplane/langgraph';
+```
+
+**3b.** Add RxJS interop + DestroyRef imports at the top of the file:
+
+```ts
+import { DestroyRef, inject } from '@angular/core';
+import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
+```
+
+(`DestroyRef` and `inject` may already be imported — merge, don't duplicate.)
+
+**3c.** Delete the `langgraphRef` input block:
+
+```diff
+- /**
+- * TEMPORARY escape hatch for Phase-1: primitives not yet migrated
+- * (chat-interrupt, chat-subagents, a2ui surfaces relying on customEvents)
+- * still require an AgentRef. Pass the same underlying LangGraph ref alongside
+- * `agent`. Remove once Phase-2 migrates these primitives.
+- * TODO(phase-2): remove langgraphRef input once chat-interrupt and
+- * customEvents are migrated to the ChatAgent contract.
+- */
+- readonly langgraphRef = input | undefined>(undefined);
+```
+
+**3d.** Replace the `customEventEffect` block with a subscription-based implementation driven by `agent.customEvents$`. Replace:
+
+```ts
+ // TODO(phase-2): move state_update events onto the ChatAgent custom-event surface
+ /**
+ * Route `state_update` custom events from the agent stream to the render
+ * state store so that components bound to `$state` paths reactively update.
+ */
+ protected readonly customEventEffect = effect(() => {
+ const ref = this.langgraphRef();
+ if (!ref) return;
+ const events = ref.customEvents();
+ const store = this.resolvedStore();
+ if (!store || events.length === 0) return;
+
+ for (const event of events) {
+ if (event.name === 'state_update' && event.data && typeof event.data === 'object') {
+ store.update(event.data as Record);
+ }
+ }
+ });
+```
+
+with a subscription bootstrapped from inside the constructor's existing `effect()` pattern. Add a `destroyRef` field at the top of the class:
+
+```ts
+ private readonly destroyRef = inject(DestroyRef);
+ private customEventsSubscribed = false;
+```
+
+Then in the `constructor()` body (alongside the existing auto-scroll `effect`), add a second effect that subscribes once the agent resolves:
+
+```ts
+ // Route `state_update` custom events from the agent stream to the render
+ // state store so components bound to `$state` paths reactively update.
+ // customEvents$ is optional — runtimes without custom-event support leave
+ // it undefined and this wiring becomes a no-op after the first effect run.
+ effect(() => {
+ if (this.customEventsSubscribed) return;
+ const stream$ = this.agent().customEvents$;
+ this.customEventsSubscribed = true;
+ if (!stream$) return;
+ stream$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((event) => {
+ if (event.type !== 'state_update') return;
+ const data = event['data'];
+ if (!data || typeof data !== 'object') return;
+ const store = this.resolvedStore();
+ if (!store) return;
+ store.update(data as Record);
+ });
+ });
+```
+
+Rationale: required inputs cannot be read from class field initialisers (they throw before Angular sets the input), so the read must happen inside a reactive context. The `customEventsSubscribed` guard ensures we subscribe exactly once; the effect tracks `this.agent()` so if Angular ever rebinds the input it will re-run, but in practice the agent input is treated as stable by the rest of the composition.
+
+**3e.** Update the interrupt template binding:
+
+```diff
+- @if (langgraphRef()) {
+-
++ @if (agent().interrupt) {
++
+
+
+
Agent paused: {{ interrupt.value }}
+
+
+
+ }
+```
+
+**3f.** Remove `effect` from the `@angular/core` import list if no other `effect()` usage remains (the constructor still has one for auto-scroll — keep `effect` in that case). Do not remove it blindly; grep the file.
+
+- [ ] **Step 4: Run tests to verify they pass**
+
+Run: `npx nx test chat`
+Expected: PASS — new subscription tests plus all existing.
+
+- [ ] **Step 5: Verify no stray AgentRef/langgraph imports remain in the composition**
+
+Run (use the Grep tool, not shell `grep`): search for `AgentRef|@cacheplane/langgraph|langgraphRef` in `libs/chat/src/lib/compositions/chat/`.
+Expected: zero matches.
+
+- [ ] **Step 6: Commit**
+
+```bash
+git add libs/chat/src/lib/compositions/chat/chat.component.ts \
+ libs/chat/src/lib/compositions/chat/chat.component.spec.ts
+git commit -m "refactor(chat): replace langgraphRef with ChatAgent.customEvents\$ subscription"
+```
+
+---
+
+## Task 6: Remove `@cacheplane/langgraph` peer-dep from chat package
+
+**Files:**
+- Modify: `libs/chat/package.json`
+
+- [ ] **Step 1: Audit that no chat sources import from `@cacheplane/langgraph`**
+
+Run (use the Grep tool): search for `@cacheplane/langgraph` in `libs/chat/src/`.
+Expected: zero matches. If any match remains, STOP and fix it before editing package.json.
+
+- [ ] **Step 2: Read current package.json**
+
+Read `libs/chat/package.json` in full to capture the exact `peerDependencies` (and/or `dependencies`) block containing `@cacheplane/langgraph`.
+
+- [ ] **Step 3: Remove the peer-dep entry**
+
+Remove only the `"@cacheplane/langgraph": "..."` line from the relevant dependency block. Preserve every other key. If the containing block becomes empty after removal, remove the empty block as well.
+
+- [ ] **Step 4: Verify chat builds without the dep**
+
+Run: `npx nx build chat`
+Expected: SUCCESS — no "chat → langgraph → chat" circular build warning.
+
+Run: `npx nx build langgraph`
+Expected: SUCCESS — langgraph still builds (it imports types from `@cacheplane/chat`, which is the intended one-way direction).
+
+- [ ] **Step 5: Full test sweep**
+
+Run: `npx nx test chat`
+Run: `npx nx test langgraph`
+Expected: PASS for both.
+
+- [ ] **Step 6: Commit**
+
+```bash
+git add libs/chat/package.json
+git commit -m "chore(chat): drop @cacheplane/langgraph peer-dep — circular dep resolved"
+```
+
+---
+
+## Task 7: Update consumers that pass `langgraphRef` to ``
+
+**Files:**
+- Search for any consumer of `` and update.
+
+- [ ] **Step 1: Find all consumers of `langgraphRef`**
+
+Run (Grep tool): search for `langgraphRef` across the whole worktree.
+Expected matches: example apps, documentation, and story/demo files that previously passed the ref alongside `agent`.
+
+- [ ] **Step 2: For each match, remove the `[langgraphRef]="..."` binding**
+
+For every file that has ``, delete only the `[langgraphRef]="..."` attribute. The `[agent]` binding stays. If a file has a reference (`const foo = toChatAgent(ref)` or similar) whose sole purpose was powering `langgraphRef`, remove the now-unused local as well (check by grep within the file — do not delete if it has other uses).
+
+Do not change any file that does not contain `langgraphRef`.
+
+- [ ] **Step 3: Re-run test sweep across affected projects**
+
+Run: `npx nx run-many -t test -p chat,langgraph`
+Expected: PASS.
+
+For any updated app/demo project, run its own test/build target:
+Run: `npx nx run-many -t test,build --projects=chat,langgraph` plus any project names surfaced in Step 1.
+Expected: PASS.
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add -A
+git commit -m "refactor: drop langgraphRef binding from consumers"
+```
+
+If Step 1 found no consumers (the input was only used in internal tests already covered by prior commits), skip Steps 2–4 and record "no external consumers".
+
+---
+
+## Task 8: Final verification and circular-dep smoke test
+
+**Files:** None (verification only).
+
+- [ ] **Step 1: Full workspace test run**
+
+Run: `npx nx run-many -t test -p chat,langgraph`
+Expected: PASS.
+
+- [ ] **Step 2: Full workspace build**
+
+Run: `npx nx run-many -t build -p chat,langgraph`
+Expected: PASS with no circular-dep warnings.
+
+- [ ] **Step 3: Lint**
+
+Run: `npx nx lint chat`
+Run: `npx nx lint langgraph`
+Expected: PASS (no new errors; pre-existing warnings OK).
+
+- [ ] **Step 4: Confirm dep direction**
+
+Run (Grep tool): `@cacheplane/chat` in `libs/langgraph/src/` — expect matches (one-way dep is fine).
+Run (Grep tool): `@cacheplane/langgraph` in `libs/chat/src/` — expect **zero** matches.
+
+- [ ] **Step 5: No commit needed**
+
+Verification only. If anything failed, diagnose and address in a follow-up commit before declaring Phase-1 complete.
+
+---
+
+## Phase-1 completion note
+
+After Task 8 passes cleanly:
+- `chat` is fully runtime-neutral.
+- `chat → langgraph` import edge is eliminated.
+- `langgraph → chat` remains (for `toChatAgent`), which is the intended direction.
+- `chat-subagents`, `chat-timeline`, `chat-timeline-slider`, `chat-debug` still carry `TODO(phase-2)` / `TODO(phase-3)` markers and continue to consume `AgentRef`. These are out of scope here and do not block Phase-1 closure because the generic `chat` composition no longer imports them in a way that forces a chat→langgraph edge.
diff --git a/docs/superpowers/specs/2026-04-21-langgraph-specific-primitives-location.md b/docs/superpowers/specs/2026-04-21-langgraph-specific-primitives-location.md
new file mode 100644
index 000000000..dbe0f63a0
--- /dev/null
+++ b/docs/superpowers/specs/2026-04-21-langgraph-specific-primitives-location.md
@@ -0,0 +1,54 @@
+# Decision: LangGraph-specific chat primitives live in `@cacheplane/langgraph`, not `@cacheplane/chat`
+
+**Status:** Accepted
+**Date:** 2026-04-21
+**Context:** Phase-1 chat runtime decoupling completion (see `docs/superpowers/plans/2026-04-21-chat-custom-events-interrupt-migration.md`)
+
+## Problem
+
+Several primitives in `libs/chat/` render UI that depends on LangGraph-specific concepts — checkpoint history, checkpoint IDs, and `ThreadState` internals:
+
+- `chat-timeline` (primitive)
+- `chat-timeline-slider` (composition)
+- `chat-debug` + `debug-controls` + `debug-summary` + `debug-utils` (composition + helpers)
+
+These components consume `ref.history(): ThreadState[]` and display checkpoint metadata (checkpoint IDs, values snapshots, fork/replay affordances) that do not exist in other runtimes (AG-UI, custom backends). Leaving them in `@cacheplane/chat` kept the package importing from `@cacheplane/langgraph` even after the core composition was decoupled, preserving the circular dependency `chat ↔ langgraph`.
+
+## Options considered
+
+**A1. Extend `ChatAgent` with optional `history?: Signal`**
+
+Add a runtime-neutral `ChatCheckpoint` type to the `ChatAgent` contract. `toChatAgent` would populate it from `ref.history()`. Other adapters would leave it undefined.
+
+Rejected: the concept of a replay-able checkpoint with a checkpoint ID is fundamentally LangGraph-shaped. Other runtimes would be forced to either (a) leave `history` empty, rendering the timeline unusable, or (b) invent synthetic checkpoints that don't correspond to anything real. Either way the contract grows a concept that serves exactly one runtime. The "runtime-neutral" framing would be false advertising.
+
+**A2. Move the langgraph-specific primitives into `libs/langgraph/` (CHOSEN)**
+
+Relocate the six files into `libs/langgraph/src/lib/primitives/` (and `compositions/`). They continue to import `AgentRef` — now as a sibling module rather than a peer-dep. Consumers import `ChatTimelineComponent`, `ChatDebugComponent`, etc. from `@cacheplane/langgraph` rather than `@cacheplane/chat`.
+
+Chosen because: (a) these components are honest about their LangGraph coupling — the source location matches the semantic truth; (b) `@cacheplane/chat` becomes pristinely runtime-neutral, which is the stated Phase-1 goal; (c) breaks the circular build-graph edge without inventing new contract surface.
+
+**A3. New sibling package `@cacheplane/chat-langgraph`**
+
+Rejected earlier in the brainstorming for this phase (see conversation 2026-04-21 — user declined to add a new package).
+
+## Consequences
+
+- **Breaking change** for any consumer importing `ChatTimelineComponent`, `ChatTimelineSliderComponent`, `ChatDebugComponent`, `DebugControlsComponent`, `DebugSummaryComponent`, or timeline/debug helpers from `@cacheplane/chat`. After the move, they must import from `@cacheplane/langgraph`.
+- `@cacheplane/chat` no longer imports from `@cacheplane/langgraph`. The `@cacheplane/langgraph` peer-dep can be dropped from `libs/chat/package.json`.
+- Dependency direction becomes strictly one-way: `@cacheplane/langgraph → @cacheplane/chat` (for `ChatAgent` types and `toChatAgent`).
+- `mock-agent-ref.ts` in `libs/chat/src/lib/testing/` is deleted (no longer consumed after Group 1 migrations; it was always a LangGraph-shaped mock).
+
+## When to revisit
+
+Revisit this decision if any of the following become true:
+
+1. **A second runtime adapter gains a meaningful checkpoint/history concept** (e.g. AG-UI adds branching checkpoints, or a user-written adapter wraps a stateful backend with comparable replay semantics). If more than one adapter has real history, a runtime-neutral `ChatCheckpoint` shape becomes tractable — at that point, promote `chat-timeline` / `chat-debug` back to `@cacheplane/chat` and extend the `ChatAgent` contract with `history?: Signal`.
+
+2. **The distinction between "generic chat primitive" and "langgraph-specific observability primitive" erodes** in practice. For example, if users start reaching for `ChatTimelineComponent` to render non-langgraph event sequences, or if the timeline UI proves useful for streams that aren't checkpoint-based, the primitive is no longer adapter-specific and should move back.
+
+3. **A third language/framework adapter materialises** (e.g. a Python/LangChain-JS adapter that mirrors LangGraph's checkpoint shape). At three adapters with converging history models, the cost of duplicating observability UI per adapter outweighs the cost of a contract extension.
+
+4. **The `@cacheplane/langgraph` package grows primitives unrelated to langgraph semantics**, which would suggest a separate `@cacheplane/chat-langgraph` package (option A3) is warranted after all.
+
+Any revisit should start from re-reading this record and confirming which of the above triggered the reconsideration.
diff --git a/libs/chat/src/lib/compositions/chat-subagent-card/chat-subagent-card.component.ts b/libs/chat/src/lib/compositions/chat-subagent-card/chat-subagent-card.component.ts
index a8b8e552f..feb5fd798 100644
--- a/libs/chat/src/lib/compositions/chat-subagent-card/chat-subagent-card.component.ts
+++ b/libs/chat/src/lib/compositions/chat-subagent-card/chat-subagent-card.component.ts
@@ -6,11 +6,9 @@ import {
signal,
ChangeDetectionStrategy,
} from '@angular/core';
-import type { SubagentStreamRef } from '@cacheplane/langgraph';
+import type { ChatSubagent, ChatSubagentStatus } from '../../agent/chat-subagent';
-type SubagentStatus = 'pending' | 'running' | 'complete' | 'error';
-
-function statusColor(status: SubagentStatus): string {
+function statusColor(status: ChatSubagentStatus): string {
switch (status) {
case 'pending': return 'background: var(--chat-bg-alt); color: var(--chat-text-muted);';
case 'running': return 'background: var(--chat-warning-bg); color: var(--chat-warning-text);';
@@ -71,7 +69,7 @@ export { statusColor };
`,
})
export class ChatSubagentCardComponent {
- readonly subagent = input.required();
+ readonly subagent = input.required();
readonly expanded = signal(false);
From c2b05031bdc77ba8593feedc9731b5a017beeb60 Mon Sep 17 00:00:00 2001
From: Brian Love
Date: Tue, 21 Apr 2026 15:57:35 -0700
Subject: [PATCH 34/40] refactor(chat): migrate chat-interrupt-panel to
ChatAgent contract
Replace AgentRef with ChatAgent input; add getInterruptFromAgent helper for
testability. Update specs to test the helper and component definition using
mockChatAgent with withInterrupt option.
Co-Authored-By: Claude Opus 4.7
---
.../chat-interrupt-panel.component.spec.ts | 40 ++++++++++++++++++-
.../chat-interrupt-panel.component.ts | 18 ++++++---
2 files changed, 52 insertions(+), 6 deletions(-)
diff --git a/libs/chat/src/lib/compositions/chat-interrupt-panel/chat-interrupt-panel.component.spec.ts b/libs/chat/src/lib/compositions/chat-interrupt-panel/chat-interrupt-panel.component.spec.ts
index 210e296c8..4c6d59d20 100644
--- a/libs/chat/src/lib/compositions/chat-interrupt-panel/chat-interrupt-panel.component.spec.ts
+++ b/libs/chat/src/lib/compositions/chat-interrupt-panel/chat-interrupt-panel.component.spec.ts
@@ -1,7 +1,45 @@
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
import { describe, it, expect } from 'vitest';
-import { ChatInterruptPanelComponent } from './chat-interrupt-panel.component';
+import { signal, computed } from '@angular/core';
+import { getInterruptFromAgent, ChatInterruptPanelComponent } from './chat-interrupt-panel.component';
import type { InterruptAction } from './chat-interrupt-panel.component';
+import { mockChatAgent } from '../../testing/mock-chat-agent';
+import type { ChatInterrupt } from '../../agent/chat-interrupt';
+
+describe('getInterruptFromAgent()', () => {
+ it('returns undefined when agent has no interrupt property', () => {
+ const agent = mockChatAgent();
+ expect(getInterruptFromAgent(agent)).toBeUndefined();
+ });
+
+ it('returns undefined when agent.interrupt signal is undefined', () => {
+ const agent = mockChatAgent({ withInterrupt: true });
+ expect(getInterruptFromAgent(agent)).toBeUndefined();
+ });
+
+ it('returns the interrupt value when present', () => {
+ const agent = mockChatAgent({ withInterrupt: true });
+ const mockInterrupt: ChatInterrupt = { id: 'int-1', value: { question: 'Confirm?' }, resumable: true };
+ agent.interrupt!.set(mockInterrupt);
+
+ expect(getInterruptFromAgent(agent)).toBe(mockInterrupt);
+ });
+
+ it('updates reactively when interrupt signal changes', () => {
+ const agent = mockChatAgent({ withInterrupt: true });
+ const interrupt1: ChatInterrupt = { id: 'int-1', value: 'first', resumable: true };
+ const interrupt2: ChatInterrupt = { id: 'int-2', value: 'second', resumable: false };
+
+ agent.interrupt!.set(interrupt1);
+ expect(getInterruptFromAgent(agent)).toBe(interrupt1);
+
+ agent.interrupt!.set(interrupt2);
+ expect(getInterruptFromAgent(agent)).toBe(interrupt2);
+
+ agent.interrupt!.set(undefined);
+ expect(getInterruptFromAgent(agent)).toBeUndefined();
+ });
+});
describe('ChatInterruptPanelComponent', () => {
it('is defined', () => {
diff --git a/libs/chat/src/lib/compositions/chat-interrupt-panel/chat-interrupt-panel.component.ts b/libs/chat/src/lib/compositions/chat-interrupt-panel/chat-interrupt-panel.component.ts
index b36380ca1..2cced4cf1 100644
--- a/libs/chat/src/lib/compositions/chat-interrupt-panel/chat-interrupt-panel.component.ts
+++ b/libs/chat/src/lib/compositions/chat-interrupt-panel/chat-interrupt-panel.component.ts
@@ -1,5 +1,4 @@
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
-// TODO(phase-2): migrate from AgentRef to ChatAgent contract.
import {
Component,
computed,
@@ -7,10 +6,20 @@ import {
output,
ChangeDetectionStrategy,
} from '@angular/core';
-import type { AgentRef } from '@cacheplane/langgraph';
+import type { ChatAgent } from '../../agent';
+import type { ChatInterrupt } from '../../agent/chat-interrupt';
export type InterruptAction = 'accept' | 'edit' | 'respond' | 'ignore';
+/**
+ * Retrieves the current interrupt value from a ChatAgent, or undefined when
+ * the runtime does not expose interrupts.
+ * Exported for unit testing without DOM rendering.
+ */
+export function getInterruptFromAgent(agent: ChatAgent): ChatInterrupt | undefined {
+ return agent.interrupt?.();
+}
+
@Component({
selector: 'chat-interrupt-panel',
standalone: true,
@@ -76,12 +85,11 @@ export type InterruptAction = 'accept' | 'edit' | 'respond' | 'ignore';
`,
})
export class ChatInterruptPanelComponent {
- readonly ref = input.required>();
+ readonly agent = input.required();
readonly action = output();
-
- readonly interrupt = computed(() => this.ref().interrupt());
+ readonly interrupt = computed(() => getInterruptFromAgent(this.agent()));
readonly interruptPayload = computed(() => {
const interrupt = this.interrupt();
From bb9bd0405373b263addaefcd9e4206796079b733 Mon Sep 17 00:00:00 2001
From: Brian Love
Date: Tue, 21 Apr 2026 16:09:35 -0700
Subject: [PATCH 35/40] refactor(chat,langgraph): relocate LangGraph-specific
primitives to @cacheplane/langgraph
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Per ADR docs/superpowers/specs/2026-04-21-langgraph-specific-primitives-location.md,
components that render checkpoint_id / ThreadState / fork-replay UI belong in
@cacheplane/langgraph, not in the runtime-neutral @cacheplane/chat.
Files moved (git mv, history preserved):
- libs/chat/src/lib/primitives/chat-timeline/ → libs/langgraph/src/lib/primitives/chat-timeline/
- libs/chat/src/lib/compositions/chat-timeline-slider/ → libs/langgraph/src/lib/compositions/chat-timeline-slider/
- libs/chat/src/lib/compositions/chat-debug/ → libs/langgraph/src/lib/compositions/chat-debug/
- libs/chat/src/lib/testing/mock-agent-ref.{ts,spec.ts} → libs/langgraph/src/lib/testing/
Import fixes in moved files:
- @cacheplane/langgraph self-imports → relative ../agent.types
- Internal chat primitives (ChatMessagesComponent, CHAT_THEME_STYLES, etc.) → @cacheplane/chat
- messageContent() added to @cacheplane/chat public-api to support the peer import
Package.json updates:
- libs/chat/package.json: removed unused @langchain/langgraph-sdk, added missing rxjs
- libs/langgraph/package.json: added @angular/common and @angular/platform-browser
Note: chat→langgraph circular dep in build graph persists until Task 6f drops
@cacheplane/langgraph from libs/chat/package.json peerDependencies.
Co-Authored-By: Claude Sonnet 4.6
---
libs/chat/package.json | 2 +-
libs/chat/src/public-api.ts | 16 +-------------
libs/langgraph/package.json | 2 ++
.../chat-debug/chat-debug.component.spec.ts | 0
.../chat-debug/chat-debug.component.ts | 18 +++++++--------
.../debug-checkpoint-card.component.ts | 0
.../chat-debug/debug-controls.component.ts | 2 +-
.../chat-debug/debug-detail.component.ts | 0
.../chat-debug/debug-state-diff.component.ts | 0
.../debug-state-inspector.component.ts | 0
.../chat-debug/debug-summary.component.ts | 2 +-
.../chat-debug/debug-timeline.component.ts | 0
.../compositions/chat-debug/debug-utils.ts | 2 +-
.../lib/compositions/chat-debug/state-diff.ts | 0
.../chat-timeline-slider.component.spec.ts | 0
.../chat-timeline-slider.component.ts | 2 +-
.../chat-timeline.component.spec.ts | 2 +-
.../chat-timeline/chat-timeline.component.ts | 2 +-
.../src/lib/testing/mock-agent-ref.spec.ts | 2 +-
.../src/lib/testing/mock-agent-ref.ts | 4 ++--
libs/langgraph/src/public-api.ts | 22 +++++++++++++++++++
21 files changed, 44 insertions(+), 34 deletions(-)
rename libs/{chat => langgraph}/src/lib/compositions/chat-debug/chat-debug.component.spec.ts (100%)
rename libs/{chat => langgraph}/src/lib/compositions/chat-debug/chat-debug.component.ts (92%)
rename libs/{chat => langgraph}/src/lib/compositions/chat-debug/debug-checkpoint-card.component.ts (100%)
rename libs/{chat => langgraph}/src/lib/compositions/chat-debug/debug-controls.component.ts (97%)
rename libs/{chat => langgraph}/src/lib/compositions/chat-debug/debug-detail.component.ts (100%)
rename libs/{chat => langgraph}/src/lib/compositions/chat-debug/debug-state-diff.component.ts (100%)
rename libs/{chat => langgraph}/src/lib/compositions/chat-debug/debug-state-inspector.component.ts (100%)
rename libs/{chat => langgraph}/src/lib/compositions/chat-debug/debug-summary.component.ts (93%)
rename libs/{chat => langgraph}/src/lib/compositions/chat-debug/debug-timeline.component.ts (100%)
rename libs/{chat => langgraph}/src/lib/compositions/chat-debug/debug-utils.ts (93%)
rename libs/{chat => langgraph}/src/lib/compositions/chat-debug/state-diff.ts (100%)
rename libs/{chat => langgraph}/src/lib/compositions/chat-timeline-slider/chat-timeline-slider.component.spec.ts (100%)
rename libs/{chat => langgraph}/src/lib/compositions/chat-timeline-slider/chat-timeline-slider.component.ts (98%)
rename libs/{chat => langgraph}/src/lib/primitives/chat-timeline/chat-timeline.component.spec.ts (97%)
rename libs/{chat => langgraph}/src/lib/primitives/chat-timeline/chat-timeline.component.ts (94%)
rename libs/{chat => langgraph}/src/lib/testing/mock-agent-ref.spec.ts (97%)
rename libs/{chat => langgraph}/src/lib/testing/mock-agent-ref.ts (97%)
diff --git a/libs/chat/package.json b/libs/chat/package.json
index 1795f20a6..c5d5a1612 100644
--- a/libs/chat/package.json
+++ b/libs/chat/package.json
@@ -13,7 +13,7 @@
"@cacheplane/langgraph": "^0.0.1",
"@json-render/core": "^0.16.0",
"@langchain/core": "^1.1.33",
- "@langchain/langgraph-sdk": "^1.7.4",
+ "rxjs": "~7.8.0",
"marked": "^15.0.0 || ^16.0.0"
},
"peerDependenciesMeta": {
diff --git a/libs/chat/src/public-api.ts b/libs/chat/src/public-api.ts
index eaea73859..23edbe012 100644
--- a/libs/chat/src/public-api.ts
+++ b/libs/chat/src/public-api.ts
@@ -38,7 +38,6 @@ export { ChatToolCallsComponent } from './lib/primitives/chat-tool-calls/chat-to
export { ChatSubagentsComponent } from './lib/primitives/chat-subagents/chat-subagents.component';
export { ChatThreadListComponent } from './lib/primitives/chat-thread-list/chat-thread-list.component';
export type { Thread } from './lib/primitives/chat-thread-list/chat-thread-list.component';
-export { ChatTimelineComponent } from './lib/primitives/chat-timeline/chat-timeline.component';
// DI provider
export { provideChat, CHAT_CONFIG } from './lib/provide-chat';
@@ -51,23 +50,11 @@ export type { InterruptAction } from './lib/compositions/chat-interrupt-panel/ch
export { ChatToolCallCardComponent } from './lib/compositions/chat-tool-call-card/chat-tool-call-card.component';
export type { ToolCallInfo } from './lib/compositions/chat-tool-call-card/chat-tool-call-card.component';
export { ChatSubagentCardComponent } from './lib/compositions/chat-subagent-card/chat-subagent-card.component';
-export { ChatTimelineSliderComponent } from './lib/compositions/chat-timeline-slider/chat-timeline-slider.component';
-export { ChatDebugComponent } from './lib/compositions/chat-debug/chat-debug.component';
-export { toDebugCheckpoint, extractStateValues } from './lib/compositions/chat-debug/debug-utils';
-export { DebugCheckpointCardComponent } from './lib/compositions/chat-debug/debug-checkpoint-card.component';
-export type { DebugCheckpoint } from './lib/compositions/chat-debug/debug-checkpoint-card.component';
-export { DebugStateInspectorComponent } from './lib/compositions/chat-debug/debug-state-inspector.component';
-export { DebugStateDiffComponent } from './lib/compositions/chat-debug/debug-state-diff.component';
-export { DebugTimelineComponent } from './lib/compositions/chat-debug/debug-timeline.component';
-export { DebugDetailComponent } from './lib/compositions/chat-debug/debug-detail.component';
-export { DebugControlsComponent } from './lib/compositions/chat-debug/debug-controls.component';
-export { DebugSummaryComponent } from './lib/compositions/chat-debug/debug-summary.component';
-export { computeStateDiff } from './lib/compositions/chat-debug/state-diff';
-export type { DiffEntry } from './lib/compositions/chat-debug/state-diff';
// Shared styles & utilities
export { CHAT_THEME_STYLES } from './lib/styles/chat-theme';
export { CHAT_MARKDOWN_STYLES, renderMarkdown } from './lib/styles/chat-markdown';
+export { messageContent } from './lib/compositions/shared/message-utils';
export {
ICON_CHEVRON_DOWN, ICON_CHEVRON_UP, ICON_TOOL,
ICON_WARNING, ICON_AGENT, ICON_CHECK, ICON_SEND,
@@ -125,7 +112,6 @@ export type {
export { isPathRef, isFunctionCall } from '@cacheplane/a2ui';
// Test utilities
-export { createMockAgentRef } from './lib/testing/mock-agent-ref';
export { mockChatAgent } from './lib/testing/mock-chat-agent';
export type { MockChatAgent, MockChatAgentOptions } from './lib/testing/mock-chat-agent';
export { runChatAgentConformance } from './lib/testing/chat-agent-conformance';
diff --git a/libs/langgraph/package.json b/libs/langgraph/package.json
index a6eb6e897..bcd934f54 100644
--- a/libs/langgraph/package.json
+++ b/libs/langgraph/package.json
@@ -5,6 +5,8 @@
"@cacheplane/chat": "^0.0.1",
"@cacheplane/licensing": "^0.0.1",
"@angular/core": "^20.0.0 || ^21.0.0",
+ "@angular/common": "^20.0.0 || ^21.0.0",
+ "@angular/platform-browser": "^20.0.0 || ^21.0.0",
"@langchain/core": "^1.1.33",
"@langchain/langgraph-sdk": "^1.7.4",
"rxjs": "~7.8.0"
diff --git a/libs/chat/src/lib/compositions/chat-debug/chat-debug.component.spec.ts b/libs/langgraph/src/lib/compositions/chat-debug/chat-debug.component.spec.ts
similarity index 100%
rename from libs/chat/src/lib/compositions/chat-debug/chat-debug.component.spec.ts
rename to libs/langgraph/src/lib/compositions/chat-debug/chat-debug.component.spec.ts
diff --git a/libs/chat/src/lib/compositions/chat-debug/chat-debug.component.ts b/libs/langgraph/src/lib/compositions/chat-debug/chat-debug.component.ts
similarity index 92%
rename from libs/chat/src/lib/compositions/chat-debug/chat-debug.component.ts
rename to libs/langgraph/src/lib/compositions/chat-debug/chat-debug.component.ts
index f31561004..c4cdcf520 100644
--- a/libs/chat/src/lib/compositions/chat-debug/chat-debug.component.ts
+++ b/libs/langgraph/src/lib/compositions/chat-debug/chat-debug.component.ts
@@ -12,21 +12,21 @@ import {
ChangeDetectionStrategy,
} from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
-import type { AgentRef } from '@cacheplane/langgraph';
-import { ChatMessagesComponent } from '../../primitives/chat-messages/chat-messages.component';
-import { MessageTemplateDirective } from '../../primitives/chat-messages/message-template.directive';
-import { ChatInputComponent } from '../../primitives/chat-input/chat-input.component';
-import { ChatTypingIndicatorComponent } from '../../primitives/chat-typing-indicator/chat-typing-indicator.component';
-import { ChatErrorComponent } from '../../primitives/chat-error/chat-error.component';
+import type { AgentRef } from '../../agent.types';
+import { ChatMessagesComponent } from '@cacheplane/chat';
+import { MessageTemplateDirective } from '@cacheplane/chat';
+import { ChatInputComponent } from '@cacheplane/chat';
+import { ChatTypingIndicatorComponent } from '@cacheplane/chat';
+import { ChatErrorComponent } from '@cacheplane/chat';
import { DebugTimelineComponent } from './debug-timeline.component';
import { DebugDetailComponent } from './debug-detail.component';
import { DebugControlsComponent } from './debug-controls.component';
import { DebugSummaryComponent } from './debug-summary.component';
import type { DebugCheckpoint } from './debug-checkpoint-card.component';
import { toDebugCheckpoint, extractStateValues } from './debug-utils';
-import { messageContent } from '../shared/message-utils';
-import { CHAT_THEME_STYLES } from '../../styles/chat-theme';
-import { CHAT_MARKDOWN_STYLES, renderMarkdown } from '../../styles/chat-markdown';
+import { messageContent } from '@cacheplane/chat';
+import { CHAT_THEME_STYLES } from '@cacheplane/chat';
+import { CHAT_MARKDOWN_STYLES, renderMarkdown } from '@cacheplane/chat';
@Component({
selector: 'chat-debug',
diff --git a/libs/chat/src/lib/compositions/chat-debug/debug-checkpoint-card.component.ts b/libs/langgraph/src/lib/compositions/chat-debug/debug-checkpoint-card.component.ts
similarity index 100%
rename from libs/chat/src/lib/compositions/chat-debug/debug-checkpoint-card.component.ts
rename to libs/langgraph/src/lib/compositions/chat-debug/debug-checkpoint-card.component.ts
diff --git a/libs/chat/src/lib/compositions/chat-debug/debug-controls.component.ts b/libs/langgraph/src/lib/compositions/chat-debug/debug-controls.component.ts
similarity index 97%
rename from libs/chat/src/lib/compositions/chat-debug/debug-controls.component.ts
rename to libs/langgraph/src/lib/compositions/chat-debug/debug-controls.component.ts
index 5d80e9bee..92576f882 100644
--- a/libs/chat/src/lib/compositions/chat-debug/debug-controls.component.ts
+++ b/libs/langgraph/src/lib/compositions/chat-debug/debug-controls.component.ts
@@ -5,7 +5,7 @@ import {
output,
ChangeDetectionStrategy,
} from '@angular/core';
-import type { AgentRef } from '@cacheplane/langgraph';
+import type { AgentRef } from '../../agent.types';
@Component({
selector: 'chat-debug-controls',
diff --git a/libs/chat/src/lib/compositions/chat-debug/debug-detail.component.ts b/libs/langgraph/src/lib/compositions/chat-debug/debug-detail.component.ts
similarity index 100%
rename from libs/chat/src/lib/compositions/chat-debug/debug-detail.component.ts
rename to libs/langgraph/src/lib/compositions/chat-debug/debug-detail.component.ts
diff --git a/libs/chat/src/lib/compositions/chat-debug/debug-state-diff.component.ts b/libs/langgraph/src/lib/compositions/chat-debug/debug-state-diff.component.ts
similarity index 100%
rename from libs/chat/src/lib/compositions/chat-debug/debug-state-diff.component.ts
rename to libs/langgraph/src/lib/compositions/chat-debug/debug-state-diff.component.ts
diff --git a/libs/chat/src/lib/compositions/chat-debug/debug-state-inspector.component.ts b/libs/langgraph/src/lib/compositions/chat-debug/debug-state-inspector.component.ts
similarity index 100%
rename from libs/chat/src/lib/compositions/chat-debug/debug-state-inspector.component.ts
rename to libs/langgraph/src/lib/compositions/chat-debug/debug-state-inspector.component.ts
diff --git a/libs/chat/src/lib/compositions/chat-debug/debug-summary.component.ts b/libs/langgraph/src/lib/compositions/chat-debug/debug-summary.component.ts
similarity index 93%
rename from libs/chat/src/lib/compositions/chat-debug/debug-summary.component.ts
rename to libs/langgraph/src/lib/compositions/chat-debug/debug-summary.component.ts
index 89ee1a38a..b6c8ff47d 100644
--- a/libs/chat/src/lib/compositions/chat-debug/debug-summary.component.ts
+++ b/libs/langgraph/src/lib/compositions/chat-debug/debug-summary.component.ts
@@ -5,7 +5,7 @@ import {
input,
ChangeDetectionStrategy,
} from '@angular/core';
-import type { AgentRef } from '@cacheplane/langgraph';
+import type { AgentRef } from '../../agent.types';
import type { DebugCheckpoint } from './debug-checkpoint-card.component';
@Component({
diff --git a/libs/chat/src/lib/compositions/chat-debug/debug-timeline.component.ts b/libs/langgraph/src/lib/compositions/chat-debug/debug-timeline.component.ts
similarity index 100%
rename from libs/chat/src/lib/compositions/chat-debug/debug-timeline.component.ts
rename to libs/langgraph/src/lib/compositions/chat-debug/debug-timeline.component.ts
diff --git a/libs/chat/src/lib/compositions/chat-debug/debug-utils.ts b/libs/langgraph/src/lib/compositions/chat-debug/debug-utils.ts
similarity index 93%
rename from libs/chat/src/lib/compositions/chat-debug/debug-utils.ts
rename to libs/langgraph/src/lib/compositions/chat-debug/debug-utils.ts
index 5474094fa..5d830df63 100644
--- a/libs/chat/src/lib/compositions/chat-debug/debug-utils.ts
+++ b/libs/langgraph/src/lib/compositions/chat-debug/debug-utils.ts
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
-import type { ThreadState } from '@cacheplane/langgraph';
+import type { ThreadState } from '../../agent.types';
import type { DebugCheckpoint } from './debug-checkpoint-card.component';
/**
diff --git a/libs/chat/src/lib/compositions/chat-debug/state-diff.ts b/libs/langgraph/src/lib/compositions/chat-debug/state-diff.ts
similarity index 100%
rename from libs/chat/src/lib/compositions/chat-debug/state-diff.ts
rename to libs/langgraph/src/lib/compositions/chat-debug/state-diff.ts
diff --git a/libs/chat/src/lib/compositions/chat-timeline-slider/chat-timeline-slider.component.spec.ts b/libs/langgraph/src/lib/compositions/chat-timeline-slider/chat-timeline-slider.component.spec.ts
similarity index 100%
rename from libs/chat/src/lib/compositions/chat-timeline-slider/chat-timeline-slider.component.spec.ts
rename to libs/langgraph/src/lib/compositions/chat-timeline-slider/chat-timeline-slider.component.spec.ts
diff --git a/libs/chat/src/lib/compositions/chat-timeline-slider/chat-timeline-slider.component.ts b/libs/langgraph/src/lib/compositions/chat-timeline-slider/chat-timeline-slider.component.ts
similarity index 98%
rename from libs/chat/src/lib/compositions/chat-timeline-slider/chat-timeline-slider.component.ts
rename to libs/langgraph/src/lib/compositions/chat-timeline-slider/chat-timeline-slider.component.ts
index 64d956326..9c808f327 100644
--- a/libs/chat/src/lib/compositions/chat-timeline-slider/chat-timeline-slider.component.ts
+++ b/libs/langgraph/src/lib/compositions/chat-timeline-slider/chat-timeline-slider.component.ts
@@ -8,7 +8,7 @@ import {
signal,
ChangeDetectionStrategy,
} from '@angular/core';
-import type { AgentRef, ThreadState } from '@cacheplane/langgraph';
+import type { AgentRef, ThreadState } from '../../agent.types';
@Component({
selector: 'chat-timeline-slider',
diff --git a/libs/chat/src/lib/primitives/chat-timeline/chat-timeline.component.spec.ts b/libs/langgraph/src/lib/primitives/chat-timeline/chat-timeline.component.spec.ts
similarity index 97%
rename from libs/chat/src/lib/primitives/chat-timeline/chat-timeline.component.spec.ts
rename to libs/langgraph/src/lib/primitives/chat-timeline/chat-timeline.component.spec.ts
index e3cd815e9..6ac601ed2 100644
--- a/libs/chat/src/lib/primitives/chat-timeline/chat-timeline.component.spec.ts
+++ b/libs/langgraph/src/lib/primitives/chat-timeline/chat-timeline.component.spec.ts
@@ -2,7 +2,7 @@
import { describe, it, expect } from 'vitest';
import { signal, computed } from '@angular/core';
import { createMockAgentRef } from '../../testing/mock-agent-ref';
-import type { ThreadState } from '@cacheplane/langgraph';
+import type { ThreadState } from '../../agent.types';
const makeState = (id: string): ThreadState =>
({ checkpoint_id: id, values: {}, next: [], metadata: {} } as any);
diff --git a/libs/chat/src/lib/primitives/chat-timeline/chat-timeline.component.ts b/libs/langgraph/src/lib/primitives/chat-timeline/chat-timeline.component.ts
similarity index 94%
rename from libs/chat/src/lib/primitives/chat-timeline/chat-timeline.component.ts
rename to libs/langgraph/src/lib/primitives/chat-timeline/chat-timeline.component.ts
index 77984f44c..f7b26dabc 100644
--- a/libs/chat/src/lib/primitives/chat-timeline/chat-timeline.component.ts
+++ b/libs/langgraph/src/lib/primitives/chat-timeline/chat-timeline.component.ts
@@ -10,7 +10,7 @@ import {
ChangeDetectionStrategy,
} from '@angular/core';
import { NgTemplateOutlet } from '@angular/common';
-import type { AgentRef, ThreadState } from '@cacheplane/langgraph';
+import type { AgentRef, ThreadState } from '../../agent.types';
@Component({
selector: 'chat-timeline',
diff --git a/libs/chat/src/lib/testing/mock-agent-ref.spec.ts b/libs/langgraph/src/lib/testing/mock-agent-ref.spec.ts
similarity index 97%
rename from libs/chat/src/lib/testing/mock-agent-ref.spec.ts
rename to libs/langgraph/src/lib/testing/mock-agent-ref.spec.ts
index 73ad3f712..d6aee241f 100644
--- a/libs/chat/src/lib/testing/mock-agent-ref.spec.ts
+++ b/libs/langgraph/src/lib/testing/mock-agent-ref.spec.ts
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
import { describe, it, expect } from 'vitest';
import { createMockAgentRef } from './mock-agent-ref';
-import { ResourceStatus } from '@cacheplane/langgraph';
+import { ResourceStatus } from '../agent.types';
describe('createMockAgentRef', () => {
it('creates a mock with default values', () => {
diff --git a/libs/chat/src/lib/testing/mock-agent-ref.ts b/libs/langgraph/src/lib/testing/mock-agent-ref.ts
similarity index 97%
rename from libs/chat/src/lib/testing/mock-agent-ref.ts
rename to libs/langgraph/src/lib/testing/mock-agent-ref.ts
index b26a84bfe..cd0420e96 100644
--- a/libs/chat/src/lib/testing/mock-agent-ref.ts
+++ b/libs/langgraph/src/lib/testing/mock-agent-ref.ts
@@ -1,8 +1,8 @@
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
import { signal, WritableSignal } from '@angular/core';
-import type { AgentRef, SubagentStreamRef, ResourceStatus as ResourceStatusType, Interrupt, ThreadState, SubmitOptions, CustomStreamEvent } from '@cacheplane/langgraph';
+import type { AgentRef, SubagentStreamRef, ResourceStatus as ResourceStatusType, Interrupt, ThreadState, SubmitOptions, CustomStreamEvent } from '../agent.types';
import type { ToolProgress, ToolCallWithResult } from '@langchain/langgraph-sdk';
-import { ResourceStatus } from '@cacheplane/langgraph';
+import { ResourceStatus } from '../agent.types';
import type { BaseMessage, AIMessage as CoreAIMessage } from '@langchain/core/messages';
import type { MessageMetadata } from '@langchain/langgraph-sdk/ui';
diff --git a/libs/langgraph/src/public-api.ts b/libs/langgraph/src/public-api.ts
index 403eb750e..dad4df532 100644
--- a/libs/langgraph/src/public-api.ts
+++ b/libs/langgraph/src/public-api.ts
@@ -29,3 +29,25 @@ export { toChatAgent } from './lib/to-chat-agent';
// Test utilities (always exported — tree-shaken in prod builds)
export { MockAgentTransport } from './lib/transport/mock-stream.transport';
export { FetchStreamTransport } from './lib/transport/fetch-stream.transport';
+
+// LangGraph-specific chat primitives (checkpoint_id / ThreadState / fork-replay UI)
+export { ChatTimelineComponent } from './lib/primitives/chat-timeline/chat-timeline.component';
+
+export { ChatTimelineSliderComponent } from './lib/compositions/chat-timeline-slider/chat-timeline-slider.component';
+
+export { ChatDebugComponent } from './lib/compositions/chat-debug/chat-debug.component';
+export { toDebugCheckpoint, extractStateValues } from './lib/compositions/chat-debug/debug-utils';
+export { DebugCheckpointCardComponent } from './lib/compositions/chat-debug/debug-checkpoint-card.component';
+export type { DebugCheckpoint } from './lib/compositions/chat-debug/debug-checkpoint-card.component';
+export { DebugStateInspectorComponent } from './lib/compositions/chat-debug/debug-state-inspector.component';
+export { DebugStateDiffComponent } from './lib/compositions/chat-debug/debug-state-diff.component';
+export { DebugTimelineComponent } from './lib/compositions/chat-debug/debug-timeline.component';
+export { DebugDetailComponent } from './lib/compositions/chat-debug/debug-detail.component';
+export { DebugControlsComponent } from './lib/compositions/chat-debug/debug-controls.component';
+export { DebugSummaryComponent } from './lib/compositions/chat-debug/debug-summary.component';
+export { computeStateDiff } from './lib/compositions/chat-debug/state-diff';
+export type { DiffEntry } from './lib/compositions/chat-debug/state-diff';
+
+// Mock test utility for LangGraph AgentRef
+export { createMockAgentRef } from './lib/testing/mock-agent-ref';
+export type { MockAgentRef } from './lib/testing/mock-agent-ref';
From e58d503b4de360f19baa8dbdd8a9e3466f0c673c Mon Sep 17 00:00:00 2001
From: Brian Love
Date: Tue, 21 Apr 2026 16:17:37 -0700
Subject: [PATCH 36/40] refactor(chat): drop @cacheplane/langgraph peer-dep,
breaking the circular edge
Task 6f of Phase-1 decoupling. After Task 6e relocated LangGraph-specific
primitives to @cacheplane/langgraph, no source in libs/chat imports from
the langgraph package, so the peer-dep can be dropped. Dependency
direction is now strictly one-way: langgraph -> chat.
Also updates the langgraph lint config to accept 'chat' and 'debug'
component-selector prefixes (needed for the moved chat-timeline,
chat-debug, debug-* components to keep their public selectors stable),
and softens a docstring mention of @cacheplane/langgraph in the
ChatAgent contract so @nx/dependency-checks no longer flags it.
Co-Authored-By: Claude Opus 4.7
---
libs/chat/package.json | 1 -
libs/chat/src/lib/agent/chat-agent.ts | 4 ++--
libs/langgraph/eslint.config.mjs | 4 ++--
3 files changed, 4 insertions(+), 5 deletions(-)
diff --git a/libs/chat/package.json b/libs/chat/package.json
index c5d5a1612..d68552915 100644
--- a/libs/chat/package.json
+++ b/libs/chat/package.json
@@ -10,7 +10,6 @@
"@cacheplane/render": "^0.0.1",
"@cacheplane/a2ui": "^0.0.1",
"@cacheplane/partial-json": "^0.0.1",
- "@cacheplane/langgraph": "^0.0.1",
"@json-render/core": "^0.16.0",
"@langchain/core": "^1.1.33",
"rxjs": "~7.8.0",
diff --git a/libs/chat/src/lib/agent/chat-agent.ts b/libs/chat/src/lib/agent/chat-agent.ts
index 911471dee..58a5d76f1 100644
--- a/libs/chat/src/lib/agent/chat-agent.ts
+++ b/libs/chat/src/lib/agent/chat-agent.ts
@@ -12,8 +12,8 @@ import type { ChatSubmitInput, ChatSubmitOptions } from './chat-submit';
/**
* Runtime-neutral contract chat primitives consume.
*
- * Implementations are produced by adapters (e.g. `@cacheplane/langgraph`,
- * `@cacheplane/ag-ui`) or by user code for custom backends.
+ * Implementations are produced by runtime adapters (e.g. a LangGraph or
+ * AG-UI adapter) or by user code for custom backends.
*
* `interrupt`, `subagents`, and `customEvents$` are optional: runtimes that
* do not support these concepts should leave them undefined, and primitives
diff --git a/libs/langgraph/eslint.config.mjs b/libs/langgraph/eslint.config.mjs
index f7954ab1d..223b467d7 100644
--- a/libs/langgraph/eslint.config.mjs
+++ b/libs/langgraph/eslint.config.mjs
@@ -27,7 +27,7 @@ export default [
'error',
{
type: 'attribute',
- prefix: 'lib',
+ prefix: ['lib', 'chat', 'debug'],
style: 'camelCase',
},
],
@@ -35,7 +35,7 @@ export default [
'error',
{
type: 'element',
- prefix: 'lib',
+ prefix: ['lib', 'chat', 'debug'],
style: 'kebab-case',
},
],
From 2be46e708b656fefaf8aa35c311eb9150d0587bc Mon Sep 17 00:00:00 2001
From: Brian Love
Date: Tue, 21 Apr 2026 16:27:03 -0700
Subject: [PATCH 37/40] refactor(cockpit): bind cockpit demos to ChatAgent via
toChatAgent
All @cacheplane/chat component bindings ([ref]="stream") updated to
[agent]="chatAgent" using toChatAgent(this.stream) direct-call idiom.
Fixes wrong import sources for ChatDebugComponent and
ChatTimelineSliderComponent (moved from @cacheplane/chat to
@cacheplane/langgraph in Phase 6). LangGraph-specific chat-debug and
chat-timeline-slider retain [ref]="stream" bindings throughout.
langgraph/interrupts: migrated submit call to chatAgent.submit({resume:null}).
Affects 25 cockpit components across chat/, langgraph/, and deep-agents/ trees.
Co-Authored-By: Claude Sonnet 4.6
---
cockpit/chat/a2ui/angular/src/app/a2ui.component.ts | 5 +++--
cockpit/chat/debug/angular/src/app/debug.component.ts | 2 +-
.../angular/src/app/generative-ui.component.ts | 5 +++--
cockpit/chat/input/angular/src/app/input.component.ts | 7 ++++---
.../angular/src/app/interrupts.component.ts | 7 ++++---
.../messages/angular/src/app/messages.component.ts | 9 +++++----
.../subagents/angular/src/app/subagents.component.ts | 7 ++++---
.../chat/theming/angular/src/app/theming.component.ts | 5 +++--
.../chat/threads/angular/src/app/threads.component.ts | 5 +++--
.../timeline/angular/src/app/timeline.component.ts | 7 ++++---
.../angular/src/app/tool-calls.component.ts | 7 ++++---
.../angular/src/app/filesystem.component.ts | 5 +++--
.../memory/angular/src/app/memory.component.ts | 5 +++--
.../planning/angular/src/app/planning.component.ts | 5 +++--
.../sandboxes/angular/src/app/sandboxes.component.ts | 5 +++--
.../skills/angular/src/app/skills.component.ts | 5 +++--
.../subagents/angular/src/app/subagents.component.ts | 5 +++--
.../angular/src/app/deployment-runtime.component.ts | 5 +++--
.../angular/src/app/durable-execution.component.ts | 5 +++--
.../angular/src/app/interrupts.component.ts | 11 ++++++-----
.../memory/angular/src/app/memory.component.ts | 5 +++--
.../angular/src/app/persistence.component.ts | 5 +++--
.../streaming/angular/src/app/streaming.component.ts | 5 +++--
.../subgraphs/angular/src/app/subgraphs.component.ts | 5 +++--
.../angular/src/app/time-travel.component.ts | 5 +++--
25 files changed, 83 insertions(+), 59 deletions(-)
diff --git a/cockpit/chat/a2ui/angular/src/app/a2ui.component.ts b/cockpit/chat/a2ui/angular/src/app/a2ui.component.ts
index 804a1a94b..8ce334ebc 100644
--- a/cockpit/chat/a2ui/angular/src/app/a2ui.component.ts
+++ b/cockpit/chat/a2ui/angular/src/app/a2ui.component.ts
@@ -1,19 +1,20 @@
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
import { Component } from '@angular/core';
import { ChatComponent, a2uiBasicCatalog } from '@cacheplane/chat';
-import { agent } from '@cacheplane/langgraph';
+import { agent, toChatAgent } from '@cacheplane/langgraph';
import { environment } from '../environments/environment';
@Component({
selector: 'app-a2ui',
standalone: true,
imports: [ChatComponent],
- template: ``,
+ template: ``,
})
export class A2uiComponent {
protected readonly agentRef = agent({
apiUrl: environment.langGraphApiUrl,
assistantId: environment.a2uiAssistantId,
});
+ protected readonly chatAgent = toChatAgent(this.agentRef);
protected readonly catalog = a2uiBasicCatalog();
}
diff --git a/cockpit/chat/debug/angular/src/app/debug.component.ts b/cockpit/chat/debug/angular/src/app/debug.component.ts
index 883749f09..1ca0f0e0f 100644
--- a/cockpit/chat/debug/angular/src/app/debug.component.ts
+++ b/cockpit/chat/debug/angular/src/app/debug.component.ts
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
import { Component } from '@angular/core';
-import { ChatDebugComponent } from '@cacheplane/chat';
+import { ChatDebugComponent } from '@cacheplane/langgraph';
import { agent } from '@cacheplane/langgraph';
import { ExampleChatLayoutComponent } from '@cacheplane/example-layouts';
import { environment } from '../environments/environment';
diff --git a/cockpit/chat/generative-ui/angular/src/app/generative-ui.component.ts b/cockpit/chat/generative-ui/angular/src/app/generative-ui.component.ts
index d738a013a..f82b43bf2 100644
--- a/cockpit/chat/generative-ui/angular/src/app/generative-ui.component.ts
+++ b/cockpit/chat/generative-ui/angular/src/app/generative-ui.component.ts
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
import { Component } from '@angular/core';
import { ChatComponent, views } from '@cacheplane/chat';
-import { agent } from '@cacheplane/langgraph';
+import { agent, toChatAgent } from '@cacheplane/langgraph';
import { ExampleChatLayoutComponent } from '@cacheplane/example-layouts';
import { environment } from '../environments/environment';
@@ -27,7 +27,7 @@ const dashboardViews = views({
imports: [ChatComponent, ExampleChatLayoutComponent],
template: `
-
+
`,
})
@@ -36,5 +36,6 @@ export class GenerativeUiComponent {
apiUrl: environment.langGraphApiUrl,
assistantId: environment.generativeUiAssistantId,
});
+ protected readonly chatAgent = toChatAgent(this.agentRef);
protected readonly dashboardViews = dashboardViews;
}
diff --git a/cockpit/chat/input/angular/src/app/input.component.ts b/cockpit/chat/input/angular/src/app/input.component.ts
index dca0f91a8..90dfb98f5 100644
--- a/cockpit/chat/input/angular/src/app/input.component.ts
+++ b/cockpit/chat/input/angular/src/app/input.component.ts
@@ -3,7 +3,7 @@ import { Component, computed } from '@angular/core';
import { ChatInputComponent as ChatInputPrimitive } from '@cacheplane/chat';
import { ChatMessagesComponent } from '@cacheplane/chat';
import { ExampleChatLayoutComponent } from '@cacheplane/example-layouts';
-import { agent } from '@cacheplane/langgraph';
+import { agent, toChatAgent } from '@cacheplane/langgraph';
import { environment } from '../environments/environment';
/**
@@ -22,10 +22,10 @@ import { environment } from '../environments/environment';
Chat Input Demo
-
+
-
+
@@ -56,6 +56,7 @@ export class InputComponent {
apiUrl: environment.langGraphApiUrl,
assistantId: environment.streamingAssistantId,
});
+ protected readonly chatAgent = toChatAgent(this.stream);
protected readonly streamStatus = computed(() => this.stream.status());
protected readonly isLoading = computed(() => this.stream.isLoading());
diff --git a/cockpit/chat/interrupts/angular/src/app/interrupts.component.ts b/cockpit/chat/interrupts/angular/src/app/interrupts.component.ts
index b910bdf2b..78db2db6a 100644
--- a/cockpit/chat/interrupts/angular/src/app/interrupts.component.ts
+++ b/cockpit/chat/interrupts/angular/src/app/interrupts.component.ts
@@ -3,7 +3,7 @@ import { Component, computed } from '@angular/core';
import { JsonPipe } from '@angular/common';
import { ChatComponent, ChatInterruptPanelComponent } from '@cacheplane/chat';
import { ExampleChatLayoutComponent } from '@cacheplane/example-layouts';
-import { agent } from '@cacheplane/langgraph';
+import { agent, toChatAgent } from '@cacheplane/langgraph';
import { environment } from '../environments/environment';
/**
@@ -18,11 +18,11 @@ import { environment } from '../environments/environment';
imports: [ChatComponent, ChatInterruptPanelComponent, JsonPipe, ExampleChatLayoutComponent],
template: `
-
+
Interrupt Panel
-
+
Stream Status
@@ -37,6 +37,7 @@ export class InterruptsComponent {
apiUrl: environment.langGraphApiUrl,
assistantId: environment.streamingAssistantId,
});
+ protected readonly chatAgent = toChatAgent(this.stream);
protected readonly streamStatus = computed(() => this.stream.status());
}
diff --git a/cockpit/chat/messages/angular/src/app/messages.component.ts b/cockpit/chat/messages/angular/src/app/messages.component.ts
index 8b5b5c06f..6c39149c7 100644
--- a/cockpit/chat/messages/angular/src/app/messages.component.ts
+++ b/cockpit/chat/messages/angular/src/app/messages.component.ts
@@ -6,7 +6,7 @@ import {
ChatTypingIndicatorComponent,
} from '@cacheplane/chat';
import { ExampleChatLayoutComponent } from '@cacheplane/example-layouts';
-import { agent } from '@cacheplane/langgraph';
+import { agent, toChatAgent } from '@cacheplane/langgraph';
import { environment } from '../environments/environment';
/**
@@ -27,11 +27,11 @@ import { environment } from '../environments/environment';
Chat Messages Primitives
-
+
-
-
+
+
@@ -51,6 +51,7 @@ export class MessagesComponent {
apiUrl: environment.langGraphApiUrl,
assistantId: environment.streamingAssistantId,
});
+ protected readonly chatAgent = toChatAgent(this.stream);
submitMessage(content: string) {
this.stream.submit([{ role: 'human', content }]);
diff --git a/cockpit/chat/subagents/angular/src/app/subagents.component.ts b/cockpit/chat/subagents/angular/src/app/subagents.component.ts
index 33a931e4d..2eed880bd 100644
--- a/cockpit/chat/subagents/angular/src/app/subagents.component.ts
+++ b/cockpit/chat/subagents/angular/src/app/subagents.component.ts
@@ -6,7 +6,7 @@ import {
ChatSubagentCardComponent,
} from '@cacheplane/chat';
import { ExampleChatLayoutComponent } from '@cacheplane/example-layouts';
-import { agent } from '@cacheplane/langgraph';
+import { agent, toChatAgent } from '@cacheplane/langgraph';
import { environment } from '../environments/environment';
/**
@@ -20,11 +20,11 @@ import { environment } from '../environments/environment';
imports: [ChatComponent, ChatSubagentsComponent, ChatSubagentCardComponent, ExampleChatLayoutComponent],
template: `
-
+
Active Subagents
-
+
Agent Pipeline
@@ -44,4 +44,5 @@ export class SubagentsComponent {
apiUrl: environment.langGraphApiUrl,
assistantId: environment.streamingAssistantId,
});
+ protected readonly chatAgent = toChatAgent(this.stream);
}
diff --git a/cockpit/chat/theming/angular/src/app/theming.component.ts b/cockpit/chat/theming/angular/src/app/theming.component.ts
index cefd91fb8..64f21d35d 100644
--- a/cockpit/chat/theming/angular/src/app/theming.component.ts
+++ b/cockpit/chat/theming/angular/src/app/theming.component.ts
@@ -3,7 +3,7 @@ import { Component, signal } from '@angular/core';
import { TitleCasePipe } from '@angular/common';
import { ChatComponent } from '@cacheplane/chat';
import { ExampleChatLayoutComponent } from '@cacheplane/example-layouts';
-import { agent } from '@cacheplane/langgraph';
+import { agent, toChatAgent } from '@cacheplane/langgraph';
import { environment } from '../environments/environment';
const THEMES: Record
> = {
@@ -52,7 +52,7 @@ const THEMES: Record> = {
imports: [ChatComponent, ExampleChatLayoutComponent, TitleCasePipe],
template: `
-
+
Theme Picker
@@ -88,6 +88,7 @@ export class ThemingComponent {
apiUrl: environment.langGraphApiUrl,
assistantId: environment.streamingAssistantId,
});
+ protected readonly chatAgent = toChatAgent(this.stream);
protected readonly themeNames = Object.keys(THEMES);
protected readonly activeTheme = signal('dark');
diff --git a/cockpit/chat/threads/angular/src/app/threads.component.ts b/cockpit/chat/threads/angular/src/app/threads.component.ts
index 20f4f4b62..e338ab3d7 100644
--- a/cockpit/chat/threads/angular/src/app/threads.component.ts
+++ b/cockpit/chat/threads/angular/src/app/threads.component.ts
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
import { Component, signal } from '@angular/core';
import { ChatComponent, ChatThreadListComponent, type Thread } from '@cacheplane/chat';
-import { agent } from '@cacheplane/langgraph';
+import { agent, toChatAgent } from '@cacheplane/langgraph';
import { ExampleChatLayoutComponent } from '@cacheplane/example-layouts';
import { environment } from '../environments/environment';
@@ -15,7 +15,7 @@ import { environment } from '../environments/environment';
imports: [ChatComponent, ChatThreadListComponent, ExampleChatLayoutComponent],
template: `
-
+
([
{ id: 'thread-1', title: 'First Conversation' },
diff --git a/cockpit/chat/timeline/angular/src/app/timeline.component.ts b/cockpit/chat/timeline/angular/src/app/timeline.component.ts
index b7da1b8a3..d9e39f3fa 100644
--- a/cockpit/chat/timeline/angular/src/app/timeline.component.ts
+++ b/cockpit/chat/timeline/angular/src/app/timeline.component.ts
@@ -1,8 +1,8 @@
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
import { Component } from '@angular/core';
-import { ChatComponent, ChatTimelineSliderComponent } from '@cacheplane/chat';
+import { ChatComponent } from '@cacheplane/chat';
import { ExampleChatLayoutComponent } from '@cacheplane/example-layouts';
-import { agent } from '@cacheplane/langgraph';
+import { agent, toChatAgent, ChatTimelineSliderComponent } from '@cacheplane/langgraph';
import { environment } from '../environments/environment';
/**
@@ -16,7 +16,7 @@ import { environment } from '../environments/environment';
imports: [ChatComponent, ChatTimelineSliderComponent, ExampleChatLayoutComponent],
template: `
-
+
Timeline
@@ -38,4 +38,5 @@ export class TimelineComponent {
apiUrl: environment.langGraphApiUrl,
assistantId: environment.streamingAssistantId,
});
+ protected readonly chatAgent = toChatAgent(this.stream);
}
diff --git a/cockpit/chat/tool-calls/angular/src/app/tool-calls.component.ts b/cockpit/chat/tool-calls/angular/src/app/tool-calls.component.ts
index 3bab1c278..be429ee0e 100644
--- a/cockpit/chat/tool-calls/angular/src/app/tool-calls.component.ts
+++ b/cockpit/chat/tool-calls/angular/src/app/tool-calls.component.ts
@@ -6,7 +6,7 @@ import {
ChatToolCallCardComponent,
} from '@cacheplane/chat';
import { ExampleChatLayoutComponent } from '@cacheplane/example-layouts';
-import { agent } from '@cacheplane/langgraph';
+import { agent, toChatAgent } from '@cacheplane/langgraph';
import { environment } from '../environments/environment';
/**
@@ -19,11 +19,11 @@ import { environment } from '../environments/environment';
imports: [ChatComponent, ChatToolCallsComponent, ChatToolCallCardComponent, ExampleChatLayoutComponent],
template: `
-
+
Tool Calls
-
+
Available Tools
@@ -42,4 +42,5 @@ export class ToolCallsComponent {
apiUrl: environment.langGraphApiUrl,
assistantId: environment.streamingAssistantId,
});
+ protected readonly chatAgent = toChatAgent(this.stream);
}
diff --git a/cockpit/deep-agents/filesystem/angular/src/app/filesystem.component.ts b/cockpit/deep-agents/filesystem/angular/src/app/filesystem.component.ts
index 7f56e3f8a..aef80a94f 100644
--- a/cockpit/deep-agents/filesystem/angular/src/app/filesystem.component.ts
+++ b/cockpit/deep-agents/filesystem/angular/src/app/filesystem.component.ts
@@ -1,7 +1,7 @@
import { Component, computed } from '@angular/core';
import { ChatComponent, views } from '@cacheplane/chat';
import { ExampleChatLayoutComponent } from '@cacheplane/example-layouts';
-import { agent } from '@cacheplane/langgraph';
+import { agent, toChatAgent } from '@cacheplane/langgraph';
import { signalStateStore } from '@cacheplane/render';
import { environment } from '../environments/environment';
import { FilePreviewComponent } from './views/file-preview.component';
@@ -33,7 +33,7 @@ interface FileOperation {
imports: [ChatComponent, ExampleChatLayoutComponent],
template: `
-
+
File Operations
@@ -74,6 +74,7 @@ export class FilesystemComponent {
apiUrl: environment.langGraphApiUrl,
assistantId: environment.streamingAssistantId,
});
+ protected readonly chatAgent = toChatAgent(this.stream);
/**
* Reactive list of file operations derived from the message history.
diff --git a/cockpit/deep-agents/memory/angular/src/app/memory.component.ts b/cockpit/deep-agents/memory/angular/src/app/memory.component.ts
index ef262334c..6d0046930 100644
--- a/cockpit/deep-agents/memory/angular/src/app/memory.component.ts
+++ b/cockpit/deep-agents/memory/angular/src/app/memory.component.ts
@@ -1,7 +1,7 @@
import { Component, computed } from '@angular/core';
import { ChatComponent } from '@cacheplane/chat';
import { ExampleChatLayoutComponent } from '@cacheplane/example-layouts';
-import { agent } from '@cacheplane/langgraph';
+import { agent, toChatAgent } from '@cacheplane/langgraph';
import { environment } from '../environments/environment';
/**
@@ -22,7 +22,7 @@ import { environment } from '../environments/environment';
imports: [ChatComponent, ExampleChatLayoutComponent],
template: `
-
+
@@ -55,6 +55,7 @@ export class MemoryComponent {
apiUrl: environment.langGraphApiUrl,
assistantId: environment.streamingAssistantId,
});
+ protected readonly chatAgent = toChatAgent(this.stream);
/**
* Reactive list of [key, value] memory entries derived from the graph state.
diff --git a/cockpit/deep-agents/planning/angular/src/app/planning.component.ts b/cockpit/deep-agents/planning/angular/src/app/planning.component.ts
index 37c8c3829..fa682195e 100644
--- a/cockpit/deep-agents/planning/angular/src/app/planning.component.ts
+++ b/cockpit/deep-agents/planning/angular/src/app/planning.component.ts
@@ -1,7 +1,7 @@
import { Component, computed } from '@angular/core';
import { ChatComponent, views } from '@cacheplane/chat';
import { ExampleChatLayoutComponent } from '@cacheplane/example-layouts';
-import { agent } from '@cacheplane/langgraph';
+import { agent, toChatAgent } from '@cacheplane/langgraph';
import { signalStateStore } from '@cacheplane/render';
import { environment } from '../environments/environment';
import { PlanChecklistComponent } from './views/plan-checklist.component';
@@ -34,7 +34,7 @@ interface PlanStep {
imports: [ChatComponent, ExampleChatLayoutComponent],
template: `
-
+
Plan
@@ -74,6 +74,7 @@ export class PlanningComponent {
apiUrl: environment.langGraphApiUrl,
assistantId: environment.streamingAssistantId,
});
+ protected readonly chatAgent = toChatAgent(this.stream);
/**
* Reactive list of plan steps derived from the graph state.
diff --git a/cockpit/deep-agents/sandboxes/angular/src/app/sandboxes.component.ts b/cockpit/deep-agents/sandboxes/angular/src/app/sandboxes.component.ts
index 2abed98fb..7a88b9ab5 100644
--- a/cockpit/deep-agents/sandboxes/angular/src/app/sandboxes.component.ts
+++ b/cockpit/deep-agents/sandboxes/angular/src/app/sandboxes.component.ts
@@ -1,7 +1,7 @@
import { Component, computed } from '@angular/core';
import { ChatComponent, views } from '@cacheplane/chat';
import { ExampleChatLayoutComponent } from '@cacheplane/example-layouts';
-import { agent } from '@cacheplane/langgraph';
+import { agent, toChatAgent } from '@cacheplane/langgraph';
import { signalStateStore } from '@cacheplane/render';
import { environment } from '../environments/environment';
import { CodeExecutionComponent } from './views/code-execution.component';
@@ -31,7 +31,7 @@ interface CodeExecution {
imports: [ChatComponent, ExampleChatLayoutComponent],
template: `
-
+
Execution Output
@@ -68,6 +68,7 @@ export class SandboxesComponent {
apiUrl: environment.langGraphApiUrl,
assistantId: environment.streamingAssistantId,
});
+ protected readonly chatAgent = toChatAgent(this.stream);
/**
* Derived signal: extracts code executions from the message stream.
diff --git a/cockpit/deep-agents/skills/angular/src/app/skills.component.ts b/cockpit/deep-agents/skills/angular/src/app/skills.component.ts
index c3eb61b67..876e0b8a9 100644
--- a/cockpit/deep-agents/skills/angular/src/app/skills.component.ts
+++ b/cockpit/deep-agents/skills/angular/src/app/skills.component.ts
@@ -1,7 +1,7 @@
import { Component, computed } from '@angular/core';
import { ChatComponent, views } from '@cacheplane/chat';
import { ExampleChatLayoutComponent } from '@cacheplane/example-layouts';
-import { agent } from '@cacheplane/langgraph';
+import { agent, toChatAgent } from '@cacheplane/langgraph';
import { signalStateStore } from '@cacheplane/render';
import { environment } from '../environments/environment';
import { CalculatorResultComponent } from './views/calculator-result.component';
@@ -33,7 +33,7 @@ interface SkillInvocation {
imports: [ChatComponent, ExampleChatLayoutComponent],
template: `
-
+
Skill Invocations
@@ -75,6 +75,7 @@ export class SkillsComponent {
apiUrl: environment.langGraphApiUrl,
assistantId: environment.streamingAssistantId,
});
+ protected readonly chatAgent = toChatAgent(this.stream);
private readonly SKILL_NAMES = new Set(['calculator', 'word_count', 'summarize']);
diff --git a/cockpit/deep-agents/subagents/angular/src/app/subagents.component.ts b/cockpit/deep-agents/subagents/angular/src/app/subagents.component.ts
index 79ae2631b..4e2e34660 100644
--- a/cockpit/deep-agents/subagents/angular/src/app/subagents.component.ts
+++ b/cockpit/deep-agents/subagents/angular/src/app/subagents.component.ts
@@ -1,7 +1,7 @@
import { Component, computed } from '@angular/core';
import { ChatComponent } from '@cacheplane/chat';
import { ExampleChatLayoutComponent } from '@cacheplane/example-layouts';
-import { agent } from '@cacheplane/langgraph';
+import { agent, toChatAgent } from '@cacheplane/langgraph';
import { environment } from '../environments/environment';
/**
@@ -31,7 +31,7 @@ interface Delegation {
imports: [ChatComponent, ExampleChatLayoutComponent],
template: `
-
+
Delegations
@@ -59,6 +59,7 @@ export class SubagentsComponent {
apiUrl: environment.langGraphApiUrl,
assistantId: environment.streamingAssistantId,
});
+ protected readonly chatAgent = toChatAgent(this.stream);
/**
* Reactive delegation list derived from messages.
diff --git a/cockpit/langgraph/deployment-runtime/angular/src/app/deployment-runtime.component.ts b/cockpit/langgraph/deployment-runtime/angular/src/app/deployment-runtime.component.ts
index 60c9aa098..1f5f0e260 100644
--- a/cockpit/langgraph/deployment-runtime/angular/src/app/deployment-runtime.component.ts
+++ b/cockpit/langgraph/deployment-runtime/angular/src/app/deployment-runtime.component.ts
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
import { Component } from '@angular/core';
import { ChatComponent } from '@cacheplane/chat';
-import { agent } from '@cacheplane/langgraph';
+import { agent, toChatAgent } from '@cacheplane/langgraph';
import { ExampleChatLayoutComponent } from '@cacheplane/example-layouts';
import { environment } from '../environments/environment';
@@ -17,7 +17,7 @@ import { environment } from '../environments/environment';
imports: [ChatComponent, ExampleChatLayoutComponent],
template: `
-
+
`,
})
@@ -26,4 +26,5 @@ export class DeploymentRuntimeComponent {
apiUrl: environment.langGraphApiUrl,
assistantId: environment.deploymentRuntimeAssistantId,
});
+ protected readonly chatAgent = toChatAgent(this.stream);
}
diff --git a/cockpit/langgraph/durable-execution/angular/src/app/durable-execution.component.ts b/cockpit/langgraph/durable-execution/angular/src/app/durable-execution.component.ts
index acfe19905..39a323909 100644
--- a/cockpit/langgraph/durable-execution/angular/src/app/durable-execution.component.ts
+++ b/cockpit/langgraph/durable-execution/angular/src/app/durable-execution.component.ts
@@ -1,6 +1,6 @@
import { Component, computed } from '@angular/core';
import { ChatComponent, views } from '@cacheplane/chat';
-import { agent } from '@cacheplane/langgraph';
+import { agent, toChatAgent } from '@cacheplane/langgraph';
import { signalStateStore } from '@cacheplane/render';
import { ExampleChatLayoutComponent } from '@cacheplane/example-layouts';
import { environment } from '../environments/environment';
@@ -40,7 +40,7 @@ const STEP_LABELS: Record
= {
imports: [ChatComponent, ExampleChatLayoutComponent],
template: `
-
+
-
- @if (stream.interrupt()) {
+
+ @if (chatAgent.interrupt()) {
-
+
}
@@ -50,6 +50,7 @@ export class InterruptsComponent {
apiUrl: environment.langGraphApiUrl,
assistantId: environment.streamingAssistantId,
});
+ protected readonly chatAgent = toChatAgent(this.stream);
/**
* Handle an interrupt action from the panel.
@@ -65,6 +66,6 @@ export class InterruptsComponent {
// In a production app, 'edit' would let the user modify the response before approval.
// For this demo, all actions simply resume the graph.
void action; // Each branch intentionally does the same thing in this demo
- this.stream.submit(null);
+ void this.chatAgent.submit({ resume: null });
}
}
diff --git a/cockpit/langgraph/memory/angular/src/app/memory.component.ts b/cockpit/langgraph/memory/angular/src/app/memory.component.ts
index cc913f1f3..a24992ec6 100644
--- a/cockpit/langgraph/memory/angular/src/app/memory.component.ts
+++ b/cockpit/langgraph/memory/angular/src/app/memory.component.ts
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
import { Component, computed } from '@angular/core';
import { ChatComponent } from '@cacheplane/chat';
-import { agent } from '@cacheplane/langgraph';
+import { agent, toChatAgent } from '@cacheplane/langgraph';
import { ExampleChatLayoutComponent } from '@cacheplane/example-layouts';
import { environment } from '../environments/environment';
@@ -23,7 +23,7 @@ import { environment } from '../environments/environment';
imports: [ChatComponent, ExampleChatLayoutComponent],
template: `
-
+
-
+
-
+
`,
})
@@ -27,4 +27,5 @@ export class StreamingComponent {
apiUrl: environment.langGraphApiUrl,
assistantId: environment.streamingAssistantId,
});
+ protected readonly chatAgent = toChatAgent(this.stream);
}
diff --git a/cockpit/langgraph/subgraphs/angular/src/app/subgraphs.component.ts b/cockpit/langgraph/subgraphs/angular/src/app/subgraphs.component.ts
index 76f950c16..7313794a3 100644
--- a/cockpit/langgraph/subgraphs/angular/src/app/subgraphs.component.ts
+++ b/cockpit/langgraph/subgraphs/angular/src/app/subgraphs.component.ts
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
import { Component, computed } from '@angular/core';
import { ChatComponent } from '@cacheplane/chat';
-import { agent } from '@cacheplane/langgraph';
+import { agent, toChatAgent } from '@cacheplane/langgraph';
import { ExampleChatLayoutComponent } from '@cacheplane/example-layouts';
import { environment } from '../environments/environment';
@@ -23,7 +23,7 @@ import { environment } from '../environments/environment';
imports: [ChatComponent, ExampleChatLayoutComponent],
template: `
-
+
-
+
(-1);
From 599bc45728c62061eaa73514fb84c5a839e6f7c0 Mon Sep 17 00:00:00 2001
From: Brian Love
Date: Tue, 21 Apr 2026 16:32:45 -0700
Subject: [PATCH 38/40] fix(chat,langgraph): final Phase-1 build fixes
- Export ChatCustomEvent from @cacheplane/chat public-api (used by
toChatAgent's signature in @cacheplane/langgraph).
- Import describe/it/expect from vitest in chat-agent-conformance.ts so
the helper compiles under ng-packagr's production build (previously
relied on Vitest globals, which aren't in the lib tsconfig types).
- Loosen AgentRef's second type argument in buildCustomEvents$ from
unknown to any so it satisfies the BagTemplate constraint.
- Wire chat-debug composition (now in @cacheplane/langgraph) to
toChatAgent for its chat-messages/chat-typing-indicator/chat-error/
chat-input child bindings, which moved from [ref] to [agent] in
Phase-1. The AgentRef-shaped [ref] binding is kept for
chat-debug-summary and chat-debug-controls, which still consume
AgentRef directly.
With this, `nx build chat`, `nx build langgraph`, `nx test chat`, and
`nx test langgraph` all pass. Dependency direction is strictly one-way:
langgraph -> chat.
Co-Authored-By: Claude Opus 4.7
---
libs/chat/src/lib/testing/chat-agent-conformance.ts | 1 +
libs/chat/src/public-api.ts | 1 +
.../compositions/chat-debug/chat-debug.component.ts | 11 +++++++----
libs/langgraph/src/lib/to-chat-agent.ts | 2 +-
4 files changed, 10 insertions(+), 5 deletions(-)
diff --git a/libs/chat/src/lib/testing/chat-agent-conformance.ts b/libs/chat/src/lib/testing/chat-agent-conformance.ts
index c3545338a..ddca43d28 100644
--- a/libs/chat/src/lib/testing/chat-agent-conformance.ts
+++ b/libs/chat/src/lib/testing/chat-agent-conformance.ts
@@ -1,4 +1,5 @@
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
+import { describe, it, expect } from 'vitest';
import type { ChatAgent } from '../agent';
/**
diff --git a/libs/chat/src/public-api.ts b/libs/chat/src/public-api.ts
index 23edbe012..58bf9e817 100644
--- a/libs/chat/src/public-api.ts
+++ b/libs/chat/src/public-api.ts
@@ -18,6 +18,7 @@ export type {
ChatSubagentStatus,
ChatSubmitInput,
ChatSubmitOptions,
+ ChatCustomEvent,
} from './lib/agent';
export {
isUserMessage,
diff --git a/libs/langgraph/src/lib/compositions/chat-debug/chat-debug.component.ts b/libs/langgraph/src/lib/compositions/chat-debug/chat-debug.component.ts
index c4cdcf520..e03ee0865 100644
--- a/libs/langgraph/src/lib/compositions/chat-debug/chat-debug.component.ts
+++ b/libs/langgraph/src/lib/compositions/chat-debug/chat-debug.component.ts
@@ -13,6 +13,7 @@ import {
} from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import type { AgentRef } from '../../agent.types';
+import { toChatAgent } from '../../to-chat-agent';
import { ChatMessagesComponent } from '@cacheplane/chat';
import { MessageTemplateDirective } from '@cacheplane/chat';
import { ChatInputComponent } from '@cacheplane/chat';
@@ -56,7 +57,7 @@ import { CHAT_MARKDOWN_STYLES, renderMarkdown } from '@cacheplane/chat';
aria-live="polite"
>
-
+
@@ -100,17 +101,17 @@ import { CHAT_MARKDOWN_STYLES, renderMarkdown } from '@cacheplane/chat';
-
+
-
+
@@ -192,6 +193,8 @@ export class ChatDebugComponent {
readonly ref = input.required
>();
+ protected readonly chatAgent = computed(() => toChatAgent(this.ref()));
+
readonly debugOpen = signal(true);
readonly selectedCheckpointIndex = signal(-1);
diff --git a/libs/langgraph/src/lib/to-chat-agent.ts b/libs/langgraph/src/lib/to-chat-agent.ts
index 6f4e5a7a1..4cc3df38d 100644
--- a/libs/langgraph/src/lib/to-chat-agent.ts
+++ b/libs/langgraph/src/lib/to-chat-agent.ts
@@ -80,7 +80,7 @@ export function toChatAgent(ref: AgentRef): ChatAgent {
* previously-seen length and emits only the tail slice.
*/
function buildCustomEvents$(
- ref: AgentRef,
+ ref: AgentRef,
): Observable {
const subject = new Subject();
let seen = 0;
From d0bd7962ab28950f20b9d5e2bda2b94d48b650bd Mon Sep 17 00:00:00 2001
From: Brian Love
Date: Tue, 21 Apr 2026 17:47:01 -0700
Subject: [PATCH 39/40] refactor(chat,langgraph): polish Phase-1 review nits
- Drop unused ref inputs on DebugControlsComponent and DebugSummaryComponent
- Consolidate fragmented @cacheplane/chat imports in chat-debug
- Route cockpit messages/input demos through chatAgent.submit
Co-Authored-By: Claude Opus 4.7
---
.../input/angular/src/app/input.component.ts | 2 +-
.../angular/src/app/messages.component.ts | 2 +-
.../chat-debug/chat-debug.component.ts | 22 ++++++++++---------
.../chat-debug/debug-controls.component.ts | 2 --
.../chat-debug/debug-summary.component.ts | 2 --
5 files changed, 14 insertions(+), 16 deletions(-)
diff --git a/cockpit/chat/input/angular/src/app/input.component.ts b/cockpit/chat/input/angular/src/app/input.component.ts
index 90dfb98f5..7e9952dc8 100644
--- a/cockpit/chat/input/angular/src/app/input.component.ts
+++ b/cockpit/chat/input/angular/src/app/input.component.ts
@@ -62,6 +62,6 @@ export class InputComponent {
protected readonly isLoading = computed(() => this.stream.isLoading());
submitMessage(content: string) {
- this.stream.submit([{ role: 'human', content }]);
+ this.chatAgent.submit({ message: content });
}
}
diff --git a/cockpit/chat/messages/angular/src/app/messages.component.ts b/cockpit/chat/messages/angular/src/app/messages.component.ts
index 6c39149c7..ef8a82904 100644
--- a/cockpit/chat/messages/angular/src/app/messages.component.ts
+++ b/cockpit/chat/messages/angular/src/app/messages.component.ts
@@ -54,6 +54,6 @@ export class MessagesComponent {
protected readonly chatAgent = toChatAgent(this.stream);
submitMessage(content: string) {
- this.stream.submit([{ role: 'human', content }]);
+ this.chatAgent.submit({ message: content });
}
}
diff --git a/libs/langgraph/src/lib/compositions/chat-debug/chat-debug.component.ts b/libs/langgraph/src/lib/compositions/chat-debug/chat-debug.component.ts
index e03ee0865..19272b095 100644
--- a/libs/langgraph/src/lib/compositions/chat-debug/chat-debug.component.ts
+++ b/libs/langgraph/src/lib/compositions/chat-debug/chat-debug.component.ts
@@ -14,20 +14,23 @@ import {
import { DomSanitizer } from '@angular/platform-browser';
import type { AgentRef } from '../../agent.types';
import { toChatAgent } from '../../to-chat-agent';
-import { ChatMessagesComponent } from '@cacheplane/chat';
-import { MessageTemplateDirective } from '@cacheplane/chat';
-import { ChatInputComponent } from '@cacheplane/chat';
-import { ChatTypingIndicatorComponent } from '@cacheplane/chat';
-import { ChatErrorComponent } from '@cacheplane/chat';
+import {
+ ChatMessagesComponent,
+ MessageTemplateDirective,
+ ChatInputComponent,
+ ChatTypingIndicatorComponent,
+ ChatErrorComponent,
+ messageContent,
+ CHAT_THEME_STYLES,
+ CHAT_MARKDOWN_STYLES,
+ renderMarkdown,
+} from '@cacheplane/chat';
import { DebugTimelineComponent } from './debug-timeline.component';
import { DebugDetailComponent } from './debug-detail.component';
import { DebugControlsComponent } from './debug-controls.component';
import { DebugSummaryComponent } from './debug-summary.component';
import type { DebugCheckpoint } from './debug-checkpoint-card.component';
import { toDebugCheckpoint, extractStateValues } from './debug-utils';
-import { messageContent } from '@cacheplane/chat';
-import { CHAT_THEME_STYLES } from '@cacheplane/chat';
-import { CHAT_MARKDOWN_STYLES, renderMarkdown } from '@cacheplane/chat';
@Component({
selector: 'chat-debug',
@@ -149,13 +152,12 @@ import { CHAT_MARKDOWN_STYLES, renderMarkdown } from '@cacheplane/chat';
-
+
>();
readonly checkpointCount = input(0);
readonly selectedIndex = input(-1);
readonly stepForward = output();
diff --git a/libs/langgraph/src/lib/compositions/chat-debug/debug-summary.component.ts b/libs/langgraph/src/lib/compositions/chat-debug/debug-summary.component.ts
index b6c8ff47d..20b30b19b 100644
--- a/libs/langgraph/src/lib/compositions/chat-debug/debug-summary.component.ts
+++ b/libs/langgraph/src/lib/compositions/chat-debug/debug-summary.component.ts
@@ -5,7 +5,6 @@ import {
input,
ChangeDetectionStrategy,
} from '@angular/core';
-import type { AgentRef } from '../../agent.types';
import type { DebugCheckpoint } from './debug-checkpoint-card.component';
@Component({
@@ -20,7 +19,6 @@ import type { DebugCheckpoint } from './debug-checkpoint-card.component';
`,
})
export class DebugSummaryComponent {
- readonly ref = input.required>();
readonly checkpoints = input([]);
readonly totalDuration = computed(() =>
From 73f7298c2c4d490dcc22d47786f8e68d57e81af8 Mon Sep 17 00:00:00 2001
From: Brian Love
Date: Tue, 21 Apr 2026 17:53:28 -0700
Subject: [PATCH 40/40] fix(langgraph): silence no-empty-function on AgentRef
test stubs
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
CI's Library job failed lint on these pre-existing stub methods. The
empty bodies are intentional — they implement the AgentRef surface
for the adapter under test. Scope the disable to the stub helpers.
Co-Authored-By: Claude Opus 4.7
---
libs/langgraph/src/lib/to-chat-agent.conformance.spec.ts | 1 +
libs/langgraph/src/lib/to-chat-agent.spec.ts | 1 +
2 files changed, 2 insertions(+)
diff --git a/libs/langgraph/src/lib/to-chat-agent.conformance.spec.ts b/libs/langgraph/src/lib/to-chat-agent.conformance.spec.ts
index 6a9b7cf47..c9a9dca60 100644
--- a/libs/langgraph/src/lib/to-chat-agent.conformance.spec.ts
+++ b/libs/langgraph/src/lib/to-chat-agent.conformance.spec.ts
@@ -6,6 +6,7 @@ import { signal } from '@angular/core';
import { ResourceStatus } from './agent.types';
import type { AgentRef } from './agent.types';
+/* eslint-disable @typescript-eslint/no-empty-function */
function minimalRef(): AgentRef {
return {
value: signal({}),
diff --git a/libs/langgraph/src/lib/to-chat-agent.spec.ts b/libs/langgraph/src/lib/to-chat-agent.spec.ts
index 79269840c..432d830ab 100644
--- a/libs/langgraph/src/lib/to-chat-agent.spec.ts
+++ b/libs/langgraph/src/lib/to-chat-agent.spec.ts
@@ -7,6 +7,7 @@ import type { AgentRef, CustomStreamEvent } from './agent.types';
import { ResourceStatus } from './agent.types';
import { toChatAgent } from './to-chat-agent';
+/* eslint-disable @typescript-eslint/no-empty-function */
function stubAgentRef(overrides: Partial> = {}): AgentRef {
return {
value: signal(null),