Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
12c0e8b
docs(spec): README refactor & complete update design
blove May 28, 2026
00fb255
docs(spec): fill README refactor ground-truth inventory
blove May 28, 2026
eb620e5
docs(spec): correct LicenseTier values + add 4 missing chat exports t…
blove May 28, 2026
92f9032
docs(langgraph): rewrite README from verified API inventory
blove May 28, 2026
45f9626
docs(langgraph): correct subagents() type + extractCitations return type
blove May 28, 2026
c83bbdf
docs(ag-ui): rewrite README from verified API inventory
blove May 28, 2026
0cc1d85
docs(ag-ui): drop unwired interrupt/subagents claims; fix citations path
blove May 28, 2026
01bdea9
docs(chat): rewrite README from verified API inventory; collapse them…
blove May 28, 2026
cefa3bf
docs(chat): fix chatToolCallTemplate usage + AG-UI citations description
blove May 29, 2026
e5da432
docs(a2ui): add README from verified API inventory
blove May 29, 2026
705846a
docs(a2ui): fix dataModelUpdate field name (.contents)
blove May 29, 2026
d82478d
docs(telemetry): rewrite README from verified inventory; preserve tra…
blove May 29, 2026
e76a03c
docs(telemetry): match trust-test name to source
blove May 29, 2026
26f1062
docs(render): rewrite README from verified API inventory
blove May 29, 2026
5f8da15
docs(render): drop misleading provideViews from example; clarify regi…
blove May 29, 2026
6758355
docs(licensing): rewrite README from verified API inventory
blove May 29, 2026
362fd34
docs: refactor root README from verified inventory; add Packages tabl…
blove May 29, 2026
fbf89a1
docs: sync root README langchain peer-dep versions with package.json
blove May 29, 2026
9326ad2
docs: normalize license badge casing + npm href encoding across packa…
blove May 29, 2026
818d9af
docs(plan): README refactor implementation plan
blove May 29, 2026
93ebb26
Merge remote-tracking branch 'origin/main' into claude/interesting-mc…
blove May 29, 2026
03f3cbb
docs(spec): AG-UI interrupt support + cockpit ag-ui section design
blove May 29, 2026
69dfc7a
docs(plan): AG-UI interrupt support + cockpit ag-ui section implement…
blove May 29, 2026
e602513
feat(ag-ui): bridge on_interrupt CUSTOM event into reducer interrupt …
blove May 29, 2026
cd19835
feat(ag-ui): expose interrupt signal + submit({ resume }) resume path
blove May 29, 2026
bf10cc7
feat(cockpit-registry): register ag-ui section (streaming, interrupts…
blove May 29, 2026
2de604d
feat(cockpit): allocate ports for ag-ui interrupts + streaming examples
blove May 29, 2026
5c8328b
feat(e2e-harness): add createAgUiGlobalSetup for uvicorn ag-ui backends
blove May 29, 2026
b236fac
feat(cockpit): ag-ui/interrupts python backend (ag-ui-langgraph over …
blove May 29, 2026
9ffa7d5
feat(cockpit): ag-ui/interrupts angular app (provideAgent + approval …
blove May 29, 2026
1f22ee4
docs(cockpit): fix ag-ui/interrupts angular codeAssetPaths to real files
blove May 29, 2026
90e07ee
test(cockpit): ag-ui/interrupts e2e (createAgUiGlobalSetup + replayed…
blove May 29, 2026
d78b65c
feat(cockpit): ag-ui/streaming python backend (ag-ui-langgraph over s…
blove Jun 2, 2026
1bdba79
refactor(cockpit): ag-ui/streaming uses real ag-ui-langgraph backend …
blove Jun 2, 2026
38f8ed7
test(cockpit): ag-ui/streaming e2e (createAgUiGlobalSetup + replayed …
blove Jun 2, 2026
6189178
ci(cockpit): add ag-ui interrupts + streaming python to smoke list
blove Jun 2, 2026
9fa3049
Merge remote-tracking branch 'origin/main' into claude/interesting-mc…
blove Jun 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ jobs:
node-version: 22
cache: npm
- run: npm ci
- run: npx nx run-many -t smoke --projects=cockpit-deep-agents-planning-python,cockpit-deep-agents-filesystem-python,cockpit-deep-agents-subagents-python,cockpit-deep-agents-memory-python,cockpit-deep-agents-skills-python,cockpit-deep-agents-sandboxes-python,cockpit-langgraph-persistence-python,cockpit-langgraph-durable-execution-python,cockpit-langgraph-streaming-python,cockpit-langgraph-interrupts-python,cockpit-langgraph-memory-python,cockpit-langgraph-subgraphs-python,cockpit-langgraph-time-travel-python,cockpit-langgraph-deployment-runtime-python --skip-nx-cache
- run: npx nx run-many -t smoke --projects=cockpit-ag-ui-interrupts-python,cockpit-ag-ui-streaming-python,cockpit-deep-agents-planning-python,cockpit-deep-agents-filesystem-python,cockpit-deep-agents-subagents-python,cockpit-deep-agents-memory-python,cockpit-deep-agents-skills-python,cockpit-deep-agents-sandboxes-python,cockpit-langgraph-persistence-python,cockpit-langgraph-durable-execution-python,cockpit-langgraph-streaming-python,cockpit-langgraph-interrupts-python,cockpit-langgraph-memory-python,cockpit-langgraph-subgraphs-python,cockpit-langgraph-time-travel-python,cockpit-langgraph-deployment-runtime-python --skip-nx-cache

cockpit-secret-integration:
name: Cockpit — secret-gated integration
Expand Down
36 changes: 36 additions & 0 deletions cockpit/ag-ui/interrupts/angular/e2e/fixtures/interrupts.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"fixtures": [
{
"match": { "responseFormat": "json_schema", "userMessage": "cus_a8x2k" },
"response": {
"content": {
"customer_id": "cus_a8x2k",
"amount": 47.5,
"reason": "Customer was charged twice for the same order."
}
}
},
{
"match": { "responseFormat": "json_schema", "userMessage": "cus_z19fp" },
"response": {
"content": {
"customer_id": "cus_z19fp",
"amount": 129.0,
"reason": "Chargeback opened for unrecognized activity."
}
}
},
{
"match": { "systemMessage": "Refund Authorization Assistant", "userMessage": "cus_a8x2k" },
"response": {
"content": "Understood — a $47.50 refund to cus_a8x2k for a duplicate charge. Pausing for operator approval; no refund is issued until a human reviews and approves."
}
},
{
"match": { "systemMessage": "Refund Authorization Assistant", "userMessage": "cus_z19fp" },
"response": {
"content": "Understood — a $129.00 refund to cus_z19fp for a chargeback. Pausing for operator approval; no refund is issued until a human reviews and approves."
}
}
]
}
14 changes: 14 additions & 0 deletions cockpit/ag-ui/interrupts/angular/e2e/global-setup-impl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: MIT
import { resolve } from 'node:path';
import { portsFor } from '../../../../../cockpit/ports.mjs';
import { createAgUiGlobalSetup } from '@threadplane-internal/e2e-harness';

const ports = portsFor('cockpit-ag-ui-interrupts-angular');

export default createAgUiGlobalSetup({
pythonCwd: 'cockpit/ag-ui/interrupts/python',
backendPort: ports.langgraph,
angularProject: 'cockpit-ag-ui-interrupts-angular',
angularPort: ports.angular,
fixturesDir: resolve(__dirname, 'fixtures'),
});
30 changes: 30 additions & 0 deletions cockpit/ag-ui/interrupts/angular/e2e/interrupts.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-License-Identifier: MIT
import { test, expect } from '@playwright/test';

test.describe('cockpit interrupts: refund approval', () => {
test('approval card displays structured payload fields', async ({ page }) => {
await page.goto('/');
await page.getByText('Refund a duplicate charge').click();
const dialog = page.locator('dialog.chat-approval-card');
await expect(dialog).toBeVisible({ timeout: 20_000 });
await expect(dialog).toContainText('Refund approval required');
});

test('Approve issues the refund and the run finishes', async ({ page }) => {
await page.goto('/');
await page.getByText('Refund a duplicate charge').click();
const dialog = page.locator('dialog.chat-approval-card');
await expect(dialog).toBeVisible({ timeout: 20_000 });
await dialog.getByRole('button', { name: 'Approve' }).click();
await expect(page.getByText(/Refund of \$/i)).toBeVisible({ timeout: 20_000 });
});

test('Cancel skips the refund and confirms cancellation', async ({ page }) => {
await page.goto('/');
await page.getByText('Refund a duplicate charge').click();
const dialog = page.locator('dialog.chat-approval-card');
await expect(dialog).toBeVisible({ timeout: 20_000 });
await dialog.getByRole('button', { name: 'Cancel' }).click();
await expect(page.getByText(/Refund cancelled by operator/i)).toBeVisible({ timeout: 20_000 });
});
});
33 changes: 33 additions & 0 deletions cockpit/ag-ui/interrupts/angular/e2e/manual/interrupts.manual.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: MIT
// Manual record-mode harness. Run against a live OpenAI key to capture new
// fixture entries into cockpit/ag-ui/interrupts/angular/e2e/fixtures/interrupts.json.
//
// Prerequisites:
// 1. Start the uvicorn backend in record mode (OPENAI_API_KEY set, no aimock):
// cd cockpit/ag-ui/interrupts/python && uv run uvicorn src.server:app --port 5320
// 2. Start the Angular dev server:
// npx nx serve cockpit-ag-ui-interrupts-angular --port 4320
// 3. Run this harness via:
// npx playwright test --config cockpit/ag-ui/interrupts/angular/e2e/playwright.config.ts \
// manual/interrupts.manual.ts --headed
import { expect, test } from '@playwright/test';

test.describe('AG-UI Interrupts Example', () => {
test.beforeEach(async ({ page }) => {
await page.goto('http://localhost:4320');
await page.waitForSelector('app-interrupts', { state: 'attached' });
});

test('renders the chat interface with approvals sidebar', async ({ page }) => {
await expect(page.locator('chat')).toBeVisible();
await expect(page.locator('textarea[name="messageText"]')).toBeVisible();
await expect(page.locator('text=No pending approvals')).toBeVisible();
});

test('sends a message and receives a response', async ({ page }) => {
await page.fill('textarea[name="messageText"]', 'hello');
await page.click('button[type="submit"]');
await expect(page.locator('.chat-md').first()).toBeVisible({ timeout: 30000 });
await expect(page.locator('.chat-md').first()).not.toBeEmpty({ timeout: 30000 });
});
});
22 changes: 22 additions & 0 deletions cockpit/ag-ui/interrupts/angular/e2e/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: MIT
import { defineConfig, devices } from '@playwright/test';
import { portsFor } from '../../../../../cockpit/ports.mjs';

const { angular: angularPort } = portsFor('cockpit-ag-ui-interrupts-angular');


export default defineConfig({
testDir: '.',
testMatch: '**/*.spec.ts',
fullyParallel: false,
workers: 1,
retries: process.env.CI ? 2 : 0,
reporter: process.env.CI ? [['list'], ['html', { open: 'never' }]] : 'list',
use: {
baseURL: `http://localhost:${angularPort}`,
trace: 'retain-on-failure',
},
projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],
globalSetup: './global-setup-impl.ts',
globalTeardown: require.resolve('../../../../../libs/e2e-harness/src/global-teardown'),
});
32 changes: 32 additions & 0 deletions cockpit/ag-ui/interrupts/angular/e2e/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "Bundler",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"types": [
"node"
],
"baseUrl": "../../../../..",
"paths": {
"@threadplane-internal/e2e-harness": [
"libs/e2e-harness/src/index.ts"
],
"@threadplane-internal/e2e-harness/global-teardown": [
"libs/e2e-harness/src/global-teardown.ts"
]
},
"allowJs": true
},
"include": [
"**/*.ts"
],
"exclude": [
"node_modules",
"test-results",
"playwright-report"
]
}
11 changes: 11 additions & 0 deletions cockpit/ag-ui/interrupts/angular/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "@threadplane/cockpit-ag-ui-interrupts-angular",
"private": true,
"version": "0.0.1",
"peerDependencies": {
"@threadplane/chat": "*",
"@threadplane/ag-ui": "*"
},
"license": "MIT",
"sideEffects": false
}
102 changes: 102 additions & 0 deletions cockpit/ag-ui/interrupts/angular/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
{
"name": "cockpit-ag-ui-interrupts-angular",
"$schema": "../../../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "cockpit/ag-ui/interrupts/angular/src",
"projectType": "application",
"targets": {
"build": {
"executor": "@angular/build:application",
"outputs": [
"{options.outputPath.base}"
],
"options": {
"outputPath": {
"base": "dist/cockpit/ag-ui/interrupts/angular",
"browser": ""
},
"browser": "cockpit/ag-ui/interrupts/angular/src/main.ts",
"tsConfig": "cockpit/ag-ui/interrupts/angular/tsconfig.app.json",
"styles": [
"cockpit/ag-ui/interrupts/angular/src/styles.css"
]
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "10kb",
"maximumError": "16kb"
}
],
"outputHashing": "none"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true,
"fileReplacements": [
{
"replace": "cockpit/ag-ui/interrupts/angular/src/environments/environment.ts",
"with": "cockpit/ag-ui/interrupts/angular/src/environments/environment.development.ts"
}
]
},
"cockpit": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true,
"fileReplacements": [
{
"replace": "cockpit/ag-ui/interrupts/angular/src/environments/environment.ts",
"with": "cockpit/ag-ui/interrupts/angular/src/environments/environment.development.ts"
}
],
"browser": "cockpit/ag-ui/interrupts/angular/src/main.cockpit.ts"
}
},
"defaultConfiguration": "production"
},
"serve": {
"continuous": true,
"executor": "@angular/build:dev-server",
"configurations": {
"production": {
"buildTarget": "cockpit-ag-ui-interrupts-angular:build:production"
},
"development": {
"buildTarget": "cockpit-ag-ui-interrupts-angular:build:development"
},
"cockpit": {
"buildTarget": "cockpit-ag-ui-interrupts-angular:build:cockpit"
}
},
"defaultConfiguration": "development",
"options": {
"proxyConfig": "cockpit/ag-ui/interrupts/angular/proxy.conf.mjs"
}
},
"smoke": {
"executor": "nx:run-commands",
"options": {
"cwd": "cockpit/ag-ui/interrupts/angular",
"command": "npx tsx -e \"import { agUiInterruptsAngularModule } from './src/index.ts'; const module = agUiInterruptsAngularModule; if (module.id !== 'ag-ui-interrupts-angular' || module.title !== 'AG-UI Interrupts (Angular)') { throw new Error('Unexpected module shape for ' + module.id); }\""
}
},
"e2e": {
"executor": "@nx/playwright:playwright",
"options": {
"config": "cockpit/ag-ui/interrupts/angular/e2e/playwright.config.ts"
}
}
},
"tags": [
"scope:cockpit-e2e",
"scope:cockpit-examples"
]
}
5 changes: 5 additions & 0 deletions cockpit/ag-ui/interrupts/angular/prompts/interrupts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# LangGraph Interrupts (Angular)

This capability demonstrates human-in-the-loop interrupt handling using LangGraph's `interrupt()` primitive and the `@threadplane/chat` Angular component library. When the graph pauses at an interrupt node, the `<chat-interrupt-panel>` surfaces the pending decision to the user; their response is submitted back to the graph via `agent`'s `resume` helper.

Key components used: `<chat>`, `<chat-interrupt-panel>`. The interrupt panel renders inside the chat host and becomes visible automatically whenever the underlying agent detects a pending interrupt in the thread state.
5 changes: 5 additions & 0 deletions cockpit/ag-ui/interrupts/angular/proxy.conf.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { portsFor } from '../../../../cockpit/ports.mjs';
const { langgraph: backend } = portsFor('cockpit-ag-ui-interrupts-angular');
export default {
'/agent': { target: `http://localhost:${backend}`, secure: false, changeOrigin: true, ws: true },
};
11 changes: 11 additions & 0 deletions cockpit/ag-ui/interrupts/angular/src/app/app.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: MIT
import { ApplicationConfig } from '@angular/core';
import { provideAgent } from '@threadplane/ag-ui';
import { provideChat } from '@threadplane/chat';

export const appConfig: ApplicationConfig = {
providers: [
provideAgent({ url: '/agent' }),
provideChat({}),
],
};
Loading
Loading