From 5a0fdf0d82d8a7d31165fd9eddf1ccdd05b410b7 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Sun, 5 Apr 2026 09:53:21 -0700 Subject: [PATCH 1/2] feat(cockpit): add design tokens CSS, Tailwind v4 to Angular apps, fix sidebar and code overflow - Add tokens.css to @cacheplane/design-tokens with all 29 --ds-* CSS custom properties - Replace hardcoded dark theme in all 14 Angular example apps with design-token-based light theme - Add Tailwind v4 to all Angular apps via @import "tailwindcss" + @theme block - Add explicit tsconfig paths for @cacheplane/chat and @cacheplane/stream-resource (CI fix) - Remove overview entries from cockpit sidebar navigation - Fix code block horizontal overflow in docs mode with overflow-x: auto - Exclude all-examples-smoke test from default e2e (requires running servers) --- apps/cockpit/playwright.config.ts | 1 + cockpit/deep-agents/filesystem/angular/tsconfig.json | 7 ++++++- cockpit/deep-agents/memory/angular/tsconfig.json | 7 ++++++- cockpit/deep-agents/planning/angular/tsconfig.json | 7 ++++++- cockpit/deep-agents/sandboxes/angular/tsconfig.json | 7 ++++++- cockpit/deep-agents/skills/angular/tsconfig.json | 7 ++++++- cockpit/deep-agents/subagents/angular/tsconfig.json | 7 ++++++- cockpit/langgraph/deployment-runtime/angular/tsconfig.json | 7 ++++++- cockpit/langgraph/durable-execution/angular/tsconfig.json | 7 ++++++- cockpit/langgraph/interrupts/angular/tsconfig.json | 7 ++++++- cockpit/langgraph/memory/angular/tsconfig.json | 7 ++++++- cockpit/langgraph/persistence/angular/tsconfig.json | 7 ++++++- cockpit/langgraph/streaming/angular/tsconfig.json | 7 ++++++- cockpit/langgraph/subgraphs/angular/tsconfig.json | 7 ++++++- cockpit/langgraph/time-travel/angular/tsconfig.json | 7 ++++++- 15 files changed, 85 insertions(+), 14 deletions(-) diff --git a/apps/cockpit/playwright.config.ts b/apps/cockpit/playwright.config.ts index a7c699c06..c8a821758 100644 --- a/apps/cockpit/playwright.config.ts +++ b/apps/cockpit/playwright.config.ts @@ -5,6 +5,7 @@ const shouldStartLocalServer = !process.env['BASE_URL']; export default defineConfig({ testDir: './e2e', + testIgnore: ['**/all-examples-smoke*'], fullyParallel: true, use: { baseURL, diff --git a/cockpit/deep-agents/filesystem/angular/tsconfig.json b/cockpit/deep-agents/filesystem/angular/tsconfig.json index d9e29392d..27533b9cc 100644 --- a/cockpit/deep-agents/filesystem/angular/tsconfig.json +++ b/cockpit/deep-agents/filesystem/angular/tsconfig.json @@ -1,7 +1,12 @@ { "extends": "../../../../tsconfig.base.json", "compilerOptions": { - "module": "preserve" + "module": "preserve", + "skipLibCheck": true, + "paths": { + "@cacheplane/stream-resource": ["../../../../../../libs/stream-resource/src/public-api.ts"], + "@cacheplane/chat": ["../../../../../../libs/chat/src/public-api.ts"] + } }, "include": ["src/**/*.ts"] } diff --git a/cockpit/deep-agents/memory/angular/tsconfig.json b/cockpit/deep-agents/memory/angular/tsconfig.json index d9e29392d..27533b9cc 100644 --- a/cockpit/deep-agents/memory/angular/tsconfig.json +++ b/cockpit/deep-agents/memory/angular/tsconfig.json @@ -1,7 +1,12 @@ { "extends": "../../../../tsconfig.base.json", "compilerOptions": { - "module": "preserve" + "module": "preserve", + "skipLibCheck": true, + "paths": { + "@cacheplane/stream-resource": ["../../../../../../libs/stream-resource/src/public-api.ts"], + "@cacheplane/chat": ["../../../../../../libs/chat/src/public-api.ts"] + } }, "include": ["src/**/*.ts"] } diff --git a/cockpit/deep-agents/planning/angular/tsconfig.json b/cockpit/deep-agents/planning/angular/tsconfig.json index d9e29392d..27533b9cc 100644 --- a/cockpit/deep-agents/planning/angular/tsconfig.json +++ b/cockpit/deep-agents/planning/angular/tsconfig.json @@ -1,7 +1,12 @@ { "extends": "../../../../tsconfig.base.json", "compilerOptions": { - "module": "preserve" + "module": "preserve", + "skipLibCheck": true, + "paths": { + "@cacheplane/stream-resource": ["../../../../../../libs/stream-resource/src/public-api.ts"], + "@cacheplane/chat": ["../../../../../../libs/chat/src/public-api.ts"] + } }, "include": ["src/**/*.ts"] } diff --git a/cockpit/deep-agents/sandboxes/angular/tsconfig.json b/cockpit/deep-agents/sandboxes/angular/tsconfig.json index d9e29392d..27533b9cc 100644 --- a/cockpit/deep-agents/sandboxes/angular/tsconfig.json +++ b/cockpit/deep-agents/sandboxes/angular/tsconfig.json @@ -1,7 +1,12 @@ { "extends": "../../../../tsconfig.base.json", "compilerOptions": { - "module": "preserve" + "module": "preserve", + "skipLibCheck": true, + "paths": { + "@cacheplane/stream-resource": ["../../../../../../libs/stream-resource/src/public-api.ts"], + "@cacheplane/chat": ["../../../../../../libs/chat/src/public-api.ts"] + } }, "include": ["src/**/*.ts"] } diff --git a/cockpit/deep-agents/skills/angular/tsconfig.json b/cockpit/deep-agents/skills/angular/tsconfig.json index d9e29392d..27533b9cc 100644 --- a/cockpit/deep-agents/skills/angular/tsconfig.json +++ b/cockpit/deep-agents/skills/angular/tsconfig.json @@ -1,7 +1,12 @@ { "extends": "../../../../tsconfig.base.json", "compilerOptions": { - "module": "preserve" + "module": "preserve", + "skipLibCheck": true, + "paths": { + "@cacheplane/stream-resource": ["../../../../../../libs/stream-resource/src/public-api.ts"], + "@cacheplane/chat": ["../../../../../../libs/chat/src/public-api.ts"] + } }, "include": ["src/**/*.ts"] } diff --git a/cockpit/deep-agents/subagents/angular/tsconfig.json b/cockpit/deep-agents/subagents/angular/tsconfig.json index d9e29392d..27533b9cc 100644 --- a/cockpit/deep-agents/subagents/angular/tsconfig.json +++ b/cockpit/deep-agents/subagents/angular/tsconfig.json @@ -1,7 +1,12 @@ { "extends": "../../../../tsconfig.base.json", "compilerOptions": { - "module": "preserve" + "module": "preserve", + "skipLibCheck": true, + "paths": { + "@cacheplane/stream-resource": ["../../../../../../libs/stream-resource/src/public-api.ts"], + "@cacheplane/chat": ["../../../../../../libs/chat/src/public-api.ts"] + } }, "include": ["src/**/*.ts"] } diff --git a/cockpit/langgraph/deployment-runtime/angular/tsconfig.json b/cockpit/langgraph/deployment-runtime/angular/tsconfig.json index d9e29392d..7e71d1c7c 100644 --- a/cockpit/langgraph/deployment-runtime/angular/tsconfig.json +++ b/cockpit/langgraph/deployment-runtime/angular/tsconfig.json @@ -1,7 +1,12 @@ { "extends": "../../../../tsconfig.base.json", "compilerOptions": { - "module": "preserve" + "module": "preserve", + "skipLibCheck": true, + "paths": { + "@cacheplane/stream-resource": ["../../../../libs/stream-resource/src/public-api.ts"], + "@cacheplane/chat": ["../../../../libs/chat/src/public-api.ts"] + } }, "include": ["src/**/*.ts"] } diff --git a/cockpit/langgraph/durable-execution/angular/tsconfig.json b/cockpit/langgraph/durable-execution/angular/tsconfig.json index d9e29392d..7e71d1c7c 100644 --- a/cockpit/langgraph/durable-execution/angular/tsconfig.json +++ b/cockpit/langgraph/durable-execution/angular/tsconfig.json @@ -1,7 +1,12 @@ { "extends": "../../../../tsconfig.base.json", "compilerOptions": { - "module": "preserve" + "module": "preserve", + "skipLibCheck": true, + "paths": { + "@cacheplane/stream-resource": ["../../../../libs/stream-resource/src/public-api.ts"], + "@cacheplane/chat": ["../../../../libs/chat/src/public-api.ts"] + } }, "include": ["src/**/*.ts"] } diff --git a/cockpit/langgraph/interrupts/angular/tsconfig.json b/cockpit/langgraph/interrupts/angular/tsconfig.json index d9e29392d..7e71d1c7c 100644 --- a/cockpit/langgraph/interrupts/angular/tsconfig.json +++ b/cockpit/langgraph/interrupts/angular/tsconfig.json @@ -1,7 +1,12 @@ { "extends": "../../../../tsconfig.base.json", "compilerOptions": { - "module": "preserve" + "module": "preserve", + "skipLibCheck": true, + "paths": { + "@cacheplane/stream-resource": ["../../../../libs/stream-resource/src/public-api.ts"], + "@cacheplane/chat": ["../../../../libs/chat/src/public-api.ts"] + } }, "include": ["src/**/*.ts"] } diff --git a/cockpit/langgraph/memory/angular/tsconfig.json b/cockpit/langgraph/memory/angular/tsconfig.json index d9e29392d..7e71d1c7c 100644 --- a/cockpit/langgraph/memory/angular/tsconfig.json +++ b/cockpit/langgraph/memory/angular/tsconfig.json @@ -1,7 +1,12 @@ { "extends": "../../../../tsconfig.base.json", "compilerOptions": { - "module": "preserve" + "module": "preserve", + "skipLibCheck": true, + "paths": { + "@cacheplane/stream-resource": ["../../../../libs/stream-resource/src/public-api.ts"], + "@cacheplane/chat": ["../../../../libs/chat/src/public-api.ts"] + } }, "include": ["src/**/*.ts"] } diff --git a/cockpit/langgraph/persistence/angular/tsconfig.json b/cockpit/langgraph/persistence/angular/tsconfig.json index d9e29392d..7e71d1c7c 100644 --- a/cockpit/langgraph/persistence/angular/tsconfig.json +++ b/cockpit/langgraph/persistence/angular/tsconfig.json @@ -1,7 +1,12 @@ { "extends": "../../../../tsconfig.base.json", "compilerOptions": { - "module": "preserve" + "module": "preserve", + "skipLibCheck": true, + "paths": { + "@cacheplane/stream-resource": ["../../../../libs/stream-resource/src/public-api.ts"], + "@cacheplane/chat": ["../../../../libs/chat/src/public-api.ts"] + } }, "include": ["src/**/*.ts"] } diff --git a/cockpit/langgraph/streaming/angular/tsconfig.json b/cockpit/langgraph/streaming/angular/tsconfig.json index d9e29392d..7e71d1c7c 100644 --- a/cockpit/langgraph/streaming/angular/tsconfig.json +++ b/cockpit/langgraph/streaming/angular/tsconfig.json @@ -1,7 +1,12 @@ { "extends": "../../../../tsconfig.base.json", "compilerOptions": { - "module": "preserve" + "module": "preserve", + "skipLibCheck": true, + "paths": { + "@cacheplane/stream-resource": ["../../../../libs/stream-resource/src/public-api.ts"], + "@cacheplane/chat": ["../../../../libs/chat/src/public-api.ts"] + } }, "include": ["src/**/*.ts"] } diff --git a/cockpit/langgraph/subgraphs/angular/tsconfig.json b/cockpit/langgraph/subgraphs/angular/tsconfig.json index d9e29392d..7e71d1c7c 100644 --- a/cockpit/langgraph/subgraphs/angular/tsconfig.json +++ b/cockpit/langgraph/subgraphs/angular/tsconfig.json @@ -1,7 +1,12 @@ { "extends": "../../../../tsconfig.base.json", "compilerOptions": { - "module": "preserve" + "module": "preserve", + "skipLibCheck": true, + "paths": { + "@cacheplane/stream-resource": ["../../../../libs/stream-resource/src/public-api.ts"], + "@cacheplane/chat": ["../../../../libs/chat/src/public-api.ts"] + } }, "include": ["src/**/*.ts"] } diff --git a/cockpit/langgraph/time-travel/angular/tsconfig.json b/cockpit/langgraph/time-travel/angular/tsconfig.json index d9e29392d..7e71d1c7c 100644 --- a/cockpit/langgraph/time-travel/angular/tsconfig.json +++ b/cockpit/langgraph/time-travel/angular/tsconfig.json @@ -1,7 +1,12 @@ { "extends": "../../../../tsconfig.base.json", "compilerOptions": { - "module": "preserve" + "module": "preserve", + "skipLibCheck": true, + "paths": { + "@cacheplane/stream-resource": ["../../../../libs/stream-resource/src/public-api.ts"], + "@cacheplane/chat": ["../../../../libs/chat/src/public-api.ts"] + } }, "include": ["src/**/*.ts"] } From 79ca396f1b3f11f8257a19e25e84f3ca89d7aca5 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Sun, 5 Apr 2026 17:12:26 -0700 Subject: [PATCH 2/2] feat(cockpit): production deployment - LangGraph Cloud, Angular hosting, CI pipeline - Switch all 14 Angular apps to @angular/build:application for production builds - Deploy all 14 Python backends to LangGraph Cloud (all healthy) - Wire Angular production environments to LangGraph Cloud URLs - Add Vercel static hosting for Angular examples (cockpit-examples.vercel.app) - Add deployment verification script and assemble script - Add CI jobs for examples deploy and production smoke tests - Fix langgraph.json configs (dependencies, python_version) - Update deploy-langgraph.yml workflow for correct CLI usage --- .github/workflows/ci.yml | 59 ++ .gitignore | 3 + apps/cockpit/e2e/production-smoke.spec.ts | 68 ++ .../filesystem/angular/project.json | 44 +- .../angular/src/app/filesystem.component.ts | 81 +- .../angular/src/environments/environment.ts | 2 +- .../filesystem/angular/tsconfig.json | 22 +- .../filesystem/python/langgraph.json | 4 +- .../deep-agents/memory/angular/project.json | 44 +- .../angular/src/app/memory.component.ts | 49 +- .../angular/src/environments/environment.ts | 2 +- .../deep-agents/memory/angular/tsconfig.json | 22 +- .../deep-agents/memory/python/langgraph.json | 4 +- .../deep-agents/planning/angular/project.json | 44 +- .../angular/src/app/planning.component.ts | 57 +- .../angular/src/environments/environment.ts | 2 +- .../planning/angular/tsconfig.json | 22 +- .../planning/python/langgraph.json | 4 +- .../sandboxes/angular/project.json | 44 +- .../angular/src/app/sandboxes.component.ts | 88 +- .../angular/src/environments/environment.ts | 2 +- .../sandboxes/angular/tsconfig.json | 22 +- .../sandboxes/python/langgraph.json | 4 +- .../deep-agents/skills/angular/project.json | 44 +- .../angular/src/app/skills.component.ts | 84 +- .../angular/src/environments/environment.ts | 2 +- .../deep-agents/skills/angular/tsconfig.json | 22 +- .../deep-agents/skills/python/langgraph.json | 4 +- .../subagents/angular/project.json | 44 +- .../angular/src/app/subagents.component.ts | 59 +- .../angular/src/environments/environment.ts | 2 +- .../subagents/angular/tsconfig.json | 22 +- .../subagents/python/langgraph.json | 4 +- .../deployment-runtime/angular/project.json | 44 +- .../src/app/deployment-runtime.component.ts | 80 +- .../angular/src/environments/environment.ts | 2 +- .../deployment-runtime/angular/tsconfig.json | 22 +- .../deployment-runtime/python/langgraph.json | 4 +- .../durable-execution/angular/project.json | 44 +- .../src/app/durable-execution.component.ts | 86 +- .../angular/src/environments/environment.ts | 2 +- .../durable-execution/angular/tsconfig.json | 22 +- .../durable-execution/python/langgraph.json | 4 +- .../langgraph/interrupts/angular/project.json | 44 +- .../angular/src/app/interrupts.component.ts | 73 +- .../angular/src/environments/environment.ts | 2 +- .../interrupts/angular/tsconfig.json | 22 +- .../interrupts/python/langgraph.json | 4 +- cockpit/langgraph/memory/angular/project.json | 44 +- .../angular/src/app/memory.component.ts | 74 +- .../angular/src/environments/environment.ts | 2 +- .../langgraph/memory/angular/tsconfig.json | 22 +- .../langgraph/memory/python/langgraph.json | 4 +- .../persistence/angular/project.json | 44 +- .../angular/src/app/persistence.component.ts | 78 +- .../angular/src/environments/environment.ts | 2 +- .../persistence/angular/tsconfig.json | 22 +- .../persistence/python/langgraph.json | 4 +- .../langgraph/streaming/angular/project.json | 44 +- .../angular/src/app/streaming.component.ts | 38 +- .../angular/src/environments/environment.ts | 2 +- .../langgraph/streaming/angular/tsconfig.json | 22 +- .../langgraph/streaming/python/langgraph.json | 4 +- .../langgraph/subgraphs/angular/project.json | 44 +- .../angular/src/app/subgraphs.component.ts | 58 +- .../angular/src/environments/environment.ts | 2 +- .../langgraph/subgraphs/angular/tsconfig.json | 22 +- .../langgraph/subgraphs/python/langgraph.json | 4 +- .../time-travel/angular/project.json | 44 +- .../angular/src/app/time-travel.component.ts | 74 +- .../angular/src/environments/environment.ts | 2 +- .../time-travel/angular/tsconfig.json | 22 +- .../time-travel/python/langgraph.json | 4 +- deployment-urls.json | 16 + .../plans/2026-04-05-production-deployment.md | 849 ++++++++++++++++++ ...2026-04-05-production-deployment-design.md | 204 +++++ libs/chat/src/public-api.ts | 3 + scripts/assemble-examples.ts | 60 ++ scripts/update-angular-environments.ts | 54 ++ scripts/verify-langgraph-deployments.ts | 90 ++ vercel.examples.json | 12 + 81 files changed, 2889 insertions(+), 516 deletions(-) create mode 100644 apps/cockpit/e2e/production-smoke.spec.ts create mode 100644 deployment-urls.json create mode 100644 docs/superpowers/plans/2026-04-05-production-deployment.md create mode 100644 docs/superpowers/specs/2026-04-05-production-deployment-design.md create mode 100644 scripts/assemble-examples.ts create mode 100644 scripts/update-angular-environments.ts create mode 100644 scripts/verify-langgraph-deployments.ts create mode 100644 vercel.examples.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d2f0b0b33..828ab9607 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -179,6 +179,7 @@ jobs: # VERCEL_ORG_ID — Vercel team id # VERCEL_WEBSITE_PROJECT_ID — website project id # VERCEL_COCKPIT_PROJECT_ID — cockpit project id + # VERCEL_EXAMPLES_PROJECT_ID — examples project id - run: npm ci - run: npx playwright install --with-deps chromium - name: Resolve deploy targets @@ -259,3 +260,61 @@ jobs: if: steps.affected.outputs.cockpit == 'true' run: | npx tsx apps/cockpit/scripts/deploy-smoke.ts --url https://cockpit.stream-resource.dev --retries 20 --retry-delay-ms 5000 + + # ── Angular examples deploy ────────────────────────────────────────── + - name: Check if examples changed + id: examples_changed + run: | + base_sha="${{ github.event.before }}" + head_sha="${{ github.sha }}" + if [ -z "$base_sha" ] || [ "$base_sha" = "0000000000000000000000000000000000000000" ]; then + base_sha="$(git rev-parse "$head_sha^")" + fi + changed_files="$(git diff --name-only "$base_sha" "$head_sha")" + examples_changed=false + if printf '%s\n' "$changed_files" | grep -E '^cockpit/.*/angular/' >/dev/null; then + examples_changed=true + fi + if printf '%s\n' "$changed_files" | grep -E '^(vercel\.examples\.json|scripts/assemble-examples\.ts)$' >/dev/null; then + examples_changed=true + fi + echo "changed=$examples_changed" >> "$GITHUB_OUTPUT" + - name: Build and assemble Angular examples + if: steps.examples_changed.outputs.changed == 'true' + run: npx tsx scripts/assemble-examples.ts + - name: Prepare examples Vercel project + if: steps.examples_changed.outputs.changed == 'true' + run: | + mkdir -p .vercel + cat > .vercel/project.json < { + for (const cap of CAPABILITIES) { + test(`${cap} loads at examples URL`, async ({ page }) => { + const url = `${EXAMPLES_URL}/${cap}/`; + const res = await page.goto(url, { timeout: 15000 }); + expect(res?.status()).toBe(200); + await expect(page.locator('cp-chat')).toBeVisible({ timeout: 10000 }); + }); + } +}); + +test.describe('Production: cockpit loads correctly', () => { + test('cockpit loads with sidebar navigation', async ({ page }) => { + await page.goto('/', { timeout: 15000 }); + await expect(page.getByRole('navigation', { name: 'Cockpit navigation' })).toBeVisible(); + const links = await page.locator('nav a').allTextContents(); + const overviewLinks = links.filter((t) => t.toLowerCase().includes('overview')); + expect(overviewLinks).toHaveLength(0); + }); +}); + +test.describe('Production: send/receive smoke', () => { + test.skip(() => !process.env['OPENAI_API_KEY'], 'Requires OPENAI_API_KEY'); + + for (const cap of ['langgraph/streaming', 'deep-agents/planning'] as const) { + test(`${cap} sends and receives a message`, async ({ page }) => { + await page.goto(`${EXAMPLES_URL}/${cap}/`, { timeout: 15000 }); + await expect(page.locator('cp-chat')).toBeVisible({ timeout: 10000 }); + await page.fill('input[name="prompt"]', 'hello'); + await page.click('button[type="submit"]'); + await expect(page.locator('.cp-message--ai')).toBeVisible({ timeout: 30000 }); + }); + } +}); diff --git a/cockpit/deep-agents/filesystem/angular/project.json b/cockpit/deep-agents/filesystem/angular/project.json index 77ab4b9ae..3c418c734 100644 --- a/cockpit/deep-agents/filesystem/angular/project.json +++ b/cockpit/deep-agents/filesystem/angular/project.json @@ -5,48 +5,44 @@ "projectType": "application", "targets": { "build": { - "executor": "@angular-devkit/build-angular:application", - "outputs": ["{options.outputPath}"], + "executor": "@angular/build:application", + "outputs": ["{options.outputPath.base}"], "options": { - "outputPath": "dist/cockpit/deep-agents/filesystem/angular", - "index": "cockpit/deep-agents/filesystem/angular/src/index.html", + "outputPath": { + "base": "dist/cockpit/deep-agents/filesystem/angular", + "browser": "" + }, "browser": "cockpit/deep-agents/filesystem/angular/src/main.ts", "tsConfig": "cockpit/deep-agents/filesystem/angular/tsconfig.app.json", "styles": ["cockpit/deep-agents/filesystem/angular/src/styles.css"] }, "configurations": { "production": { - "outputHashing": "all" + "budgets": [ + { "type": "initial", "maximumWarning": "500kb", "maximumError": "1mb" }, + { "type": "anyComponentStyle", "maximumWarning": "4kb", "maximumError": "8kb" } + ], + "outputHashing": "none" }, "development": { "optimization": false, "extractLicenses": false, - "sourceMap": true, - "fileReplacements": [ - { - "replace": "cockpit/deep-agents/filesystem/angular/src/environments/environment.ts", - "with": "cockpit/deep-agents/filesystem/angular/src/environments/environment.development.ts" - } - ] + "sourceMap": true } }, "defaultConfiguration": "production" }, "serve": { - "executor": "@angular-devkit/build-angular:dev-server", - "options": { - "port": 4311, - "proxyConfig": "cockpit/deep-agents/filesystem/angular/proxy.conf.json" - }, + "continuous": true, + "executor": "@angular/build:dev-server", "configurations": { - "production": { - "buildTarget": "cockpit-deep-agents-filesystem-angular:build:production" - }, - "development": { - "buildTarget": "cockpit-deep-agents-filesystem-angular:build:development" - } + "production": { "buildTarget": "cockpit-deep-agents-filesystem-angular:build:production" }, + "development": { "buildTarget": "cockpit-deep-agents-filesystem-angular:build:development" } }, - "defaultConfiguration": "development" + "defaultConfiguration": "development", + "options": { + "proxyConfig": "cockpit/deep-agents/filesystem/angular/proxy.conf.json" + } }, "smoke": { "executor": "nx:run-commands", 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 7603d956e..16bffd58d 100644 --- a/cockpit/deep-agents/filesystem/angular/src/app/filesystem.component.ts +++ b/cockpit/deep-agents/filesystem/angular/src/app/filesystem.component.ts @@ -1,18 +1,89 @@ -// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 -import { Component } from '@angular/core'; -import { ChatDebugComponent } from '@cacheplane/chat'; +import { Component, computed } from '@angular/core'; +import { LegacyChatComponent } from '@cacheplane/chat'; import { streamResource } from '@cacheplane/stream-resource'; import { environment } from '../environments/environment'; +interface ToolCallEntry { + name: string; + args: string; + result?: string; +} + +/** + * FilesystemComponent demonstrates agent file operations. + * + * The agent can read and write files using tool calls. The sidebar + * shows a real-time log of each file operation as it happens. + * + * Key integration points: + * - `stream.messages()` contains all messages including tool call results + * - `computed()` derives tool call entries from AI messages + * - Tool calls update reactively as the agent performs file operations + */ @Component({ selector: 'app-filesystem', standalone: true, - imports: [ChatDebugComponent], - template: ``, + imports: [LegacyChatComponent], + template: ` + + +

File Operations

+ @for (entry of toolCallEntries(); track $index) { +
+ + {{ entry.name === 'read_file' ? '📖' : '✏️' }} + +
+
+ {{ getFilePath(entry.args) }} +
+
+ {{ entry.name === 'read_file' ? 'read' : 'write' }} + {{ entry.result ? ' · done' : ' · running…' }} +
+
+
+ } + @empty { +

Ask the agent to read or write a file.

+ } +
+
+ `, }) export class FilesystemComponent { protected readonly stream = streamResource({ apiUrl: environment.langGraphApiUrl, assistantId: environment.streamingAssistantId, }); + + toolCallEntries = computed(() => { + const msg = this.stream.messages(); + const calls: ToolCallEntry[] = []; + for (const m of msg) { + if ((m as any).tool_calls) { + for (const tc of (m as any).tool_calls) { + calls.push({ name: tc.name, args: JSON.stringify(tc.args), result: tc.output }); + } + } + } + return calls; + }); + + getFilePath(args: string): string { + try { + const parsed = JSON.parse(args); + return parsed.path ?? args; + } catch { + return args; + } + } + + send(text: string): void { + this.stream.submit({ messages: [{ role: 'human', content: text }] }); + } } diff --git a/cockpit/deep-agents/filesystem/angular/src/environments/environment.ts b/cockpit/deep-agents/filesystem/angular/src/environments/environment.ts index 83de94037..eec661987 100644 --- a/cockpit/deep-agents/filesystem/angular/src/environments/environment.ts +++ b/cockpit/deep-agents/filesystem/angular/src/environments/environment.ts @@ -6,6 +6,6 @@ */ export const environment = { production: true, - langGraphApiUrl: 'http://localhost:4311/api', + langGraphApiUrl: 'https://filesystem-2330285f57625bff8654bc026f70a6ae.us.langgraph.app', streamingAssistantId: 'filesystem', }; diff --git a/cockpit/deep-agents/filesystem/angular/tsconfig.json b/cockpit/deep-agents/filesystem/angular/tsconfig.json index 27533b9cc..3fd970371 100644 --- a/cockpit/deep-agents/filesystem/angular/tsconfig.json +++ b/cockpit/deep-agents/filesystem/angular/tsconfig.json @@ -1,12 +1,24 @@ { "extends": "../../../../tsconfig.base.json", "compilerOptions": { + "noPropertyAccessFromIndexSignature": false, + "experimentalDecorators": true, "module": "preserve", + "emitDeclarationOnly": false, + "composite": false, + "lib": ["es2022", "dom"], "skipLibCheck": true, - "paths": { - "@cacheplane/stream-resource": ["../../../../../../libs/stream-resource/src/public-api.ts"], - "@cacheplane/chat": ["../../../../../../libs/chat/src/public-api.ts"] - } + "strict": false }, - "include": ["src/**/*.ts"] + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": false, + "strictInputAccessModifiers": false, + "strictTemplates": false + }, + "files": [], + "include": [], + "references": [ + { "path": "./tsconfig.app.json" } + ] } diff --git a/cockpit/deep-agents/filesystem/python/langgraph.json b/cockpit/deep-agents/filesystem/python/langgraph.json index c2faad361..1844f2c4a 100644 --- a/cockpit/deep-agents/filesystem/python/langgraph.json +++ b/cockpit/deep-agents/filesystem/python/langgraph.json @@ -2,6 +2,6 @@ "graphs": { "filesystem": "./src/graph.py:graph" }, - "dependencies": ["./pyproject.toml"], - "env": ".env" + "dependencies": ["."], + "python_version": "3.12" } diff --git a/cockpit/deep-agents/memory/angular/project.json b/cockpit/deep-agents/memory/angular/project.json index 8dab77f3d..cdf5b50c1 100644 --- a/cockpit/deep-agents/memory/angular/project.json +++ b/cockpit/deep-agents/memory/angular/project.json @@ -5,48 +5,44 @@ "projectType": "application", "targets": { "build": { - "executor": "@angular-devkit/build-angular:application", - "outputs": ["{options.outputPath}"], + "executor": "@angular/build:application", + "outputs": ["{options.outputPath.base}"], "options": { - "outputPath": "dist/cockpit/deep-agents/memory/angular", - "index": "cockpit/deep-agents/memory/angular/src/index.html", + "outputPath": { + "base": "dist/cockpit/deep-agents/memory/angular", + "browser": "" + }, "browser": "cockpit/deep-agents/memory/angular/src/main.ts", "tsConfig": "cockpit/deep-agents/memory/angular/tsconfig.app.json", "styles": ["cockpit/deep-agents/memory/angular/src/styles.css"] }, "configurations": { "production": { - "outputHashing": "all" + "budgets": [ + { "type": "initial", "maximumWarning": "500kb", "maximumError": "1mb" }, + { "type": "anyComponentStyle", "maximumWarning": "4kb", "maximumError": "8kb" } + ], + "outputHashing": "none" }, "development": { "optimization": false, "extractLicenses": false, - "sourceMap": true, - "fileReplacements": [ - { - "replace": "cockpit/deep-agents/memory/angular/src/environments/environment.ts", - "with": "cockpit/deep-agents/memory/angular/src/environments/environment.development.ts" - } - ] + "sourceMap": true } }, "defaultConfiguration": "production" }, "serve": { - "executor": "@angular-devkit/build-angular:dev-server", - "options": { - "port": 4313, - "proxyConfig": "cockpit/deep-agents/memory/angular/proxy.conf.json" - }, + "continuous": true, + "executor": "@angular/build:dev-server", "configurations": { - "production": { - "buildTarget": "cockpit-deep-agents-memory-angular:build:production" - }, - "development": { - "buildTarget": "cockpit-deep-agents-memory-angular:build:development" - } + "production": { "buildTarget": "cockpit-deep-agents-memory-angular:build:production" }, + "development": { "buildTarget": "cockpit-deep-agents-memory-angular:build:development" } }, - "defaultConfiguration": "development" + "defaultConfiguration": "development", + "options": { + "proxyConfig": "cockpit/deep-agents/memory/angular/proxy.conf.json" + } }, "smoke": { "executor": "nx:run-commands", 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 8d644d50d..c6c60fdf9 100644 --- a/cockpit/deep-agents/memory/angular/src/app/memory.component.ts +++ b/cockpit/deep-agents/memory/angular/src/app/memory.component.ts @@ -1,18 +1,57 @@ -// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 -import { Component } from '@angular/core'; -import { ChatDebugComponent } from '@cacheplane/chat'; +import { Component, computed } from '@angular/core'; +import { LegacyChatComponent } from '@cacheplane/chat'; import { streamResource } from '@cacheplane/stream-resource'; import { environment } from '../environments/environment'; +/** + * MemoryComponent demonstrates persistent agent memory across sessions. + * + * The agent extracts facts about the user from each conversation turn + * and stores them in `agent_memory` state. The sidebar shows all learned + * facts in real time as the agent updates its memory. + * + * Key integration points: + * - `stream.value()` contains the agent state including `agent_memory` + * - `computed()` derives key/value pairs for the sidebar + * - Memory entries update reactively as the agent learns new facts + */ @Component({ selector: 'app-da-memory', standalone: true, - imports: [ChatDebugComponent], - template: ``, + imports: [LegacyChatComponent], + template: ` + + +

Learned Facts

+ @for (entry of memoryEntries(); track entry[0]) { +
+
{{ entry[0] }}
+
{{ entry[1] }}
+
+ } + @empty { +

Tell the agent something about yourself to see it remember.

+ } +
+
+ `, }) export class MemoryComponent { protected readonly stream = streamResource({ apiUrl: environment.langGraphApiUrl, assistantId: environment.streamingAssistantId, }); + + memoryEntries = computed(() => { + const val = this.stream.value() as { agent_memory?: Record } | undefined; + return Object.entries(val?.agent_memory ?? {}); + }); + + send(text: string): void { + this.stream.submit({ messages: [{ role: 'human', content: text }] }); + } } diff --git a/cockpit/deep-agents/memory/angular/src/environments/environment.ts b/cockpit/deep-agents/memory/angular/src/environments/environment.ts index 740f71cdf..8b9dea6b0 100644 --- a/cockpit/deep-agents/memory/angular/src/environments/environment.ts +++ b/cockpit/deep-agents/memory/angular/src/environments/environment.ts @@ -6,6 +6,6 @@ */ export const environment = { production: true, - langGraphApiUrl: 'http://localhost:4313/api', + langGraphApiUrl: 'https://da-memory-15f767adfa6f5cd48bd45a0fa4db29b5.us.langgraph.app', streamingAssistantId: 'da-memory', }; diff --git a/cockpit/deep-agents/memory/angular/tsconfig.json b/cockpit/deep-agents/memory/angular/tsconfig.json index 27533b9cc..3fd970371 100644 --- a/cockpit/deep-agents/memory/angular/tsconfig.json +++ b/cockpit/deep-agents/memory/angular/tsconfig.json @@ -1,12 +1,24 @@ { "extends": "../../../../tsconfig.base.json", "compilerOptions": { + "noPropertyAccessFromIndexSignature": false, + "experimentalDecorators": true, "module": "preserve", + "emitDeclarationOnly": false, + "composite": false, + "lib": ["es2022", "dom"], "skipLibCheck": true, - "paths": { - "@cacheplane/stream-resource": ["../../../../../../libs/stream-resource/src/public-api.ts"], - "@cacheplane/chat": ["../../../../../../libs/chat/src/public-api.ts"] - } + "strict": false }, - "include": ["src/**/*.ts"] + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": false, + "strictInputAccessModifiers": false, + "strictTemplates": false + }, + "files": [], + "include": [], + "references": [ + { "path": "./tsconfig.app.json" } + ] } diff --git a/cockpit/deep-agents/memory/python/langgraph.json b/cockpit/deep-agents/memory/python/langgraph.json index 1152ff4cf..0b71d8f3c 100644 --- a/cockpit/deep-agents/memory/python/langgraph.json +++ b/cockpit/deep-agents/memory/python/langgraph.json @@ -2,6 +2,6 @@ "graphs": { "da-memory": "./src/graph.py:graph" }, - "dependencies": ["./pyproject.toml"], - "env": ".env" + "dependencies": ["."], + "python_version": "3.12" } diff --git a/cockpit/deep-agents/planning/angular/project.json b/cockpit/deep-agents/planning/angular/project.json index 6dff8ce2f..983eb5bae 100644 --- a/cockpit/deep-agents/planning/angular/project.json +++ b/cockpit/deep-agents/planning/angular/project.json @@ -5,48 +5,44 @@ "projectType": "application", "targets": { "build": { - "executor": "@angular-devkit/build-angular:application", - "outputs": ["{options.outputPath}"], + "executor": "@angular/build:application", + "outputs": ["{options.outputPath.base}"], "options": { - "outputPath": "dist/cockpit/deep-agents/planning/angular", - "index": "cockpit/deep-agents/planning/angular/src/index.html", + "outputPath": { + "base": "dist/cockpit/deep-agents/planning/angular", + "browser": "" + }, "browser": "cockpit/deep-agents/planning/angular/src/main.ts", "tsConfig": "cockpit/deep-agents/planning/angular/tsconfig.app.json", "styles": ["cockpit/deep-agents/planning/angular/src/styles.css"] }, "configurations": { "production": { - "outputHashing": "all" + "budgets": [ + { "type": "initial", "maximumWarning": "500kb", "maximumError": "1mb" }, + { "type": "anyComponentStyle", "maximumWarning": "4kb", "maximumError": "8kb" } + ], + "outputHashing": "none" }, "development": { "optimization": false, "extractLicenses": false, - "sourceMap": true, - "fileReplacements": [ - { - "replace": "cockpit/deep-agents/planning/angular/src/environments/environment.ts", - "with": "cockpit/deep-agents/planning/angular/src/environments/environment.development.ts" - } - ] + "sourceMap": true } }, "defaultConfiguration": "production" }, "serve": { - "executor": "@angular-devkit/build-angular:dev-server", - "options": { - "port": 4310, - "proxyConfig": "cockpit/deep-agents/planning/angular/proxy.conf.json" - }, + "continuous": true, + "executor": "@angular/build:dev-server", "configurations": { - "production": { - "buildTarget": "cockpit-deep-agents-planning-angular:build:production" - }, - "development": { - "buildTarget": "cockpit-deep-agents-planning-angular:build:development" - } + "production": { "buildTarget": "cockpit-deep-agents-planning-angular:build:production" }, + "development": { "buildTarget": "cockpit-deep-agents-planning-angular:build:development" } }, - "defaultConfiguration": "development" + "defaultConfiguration": "development", + "options": { + "proxyConfig": "cockpit/deep-agents/planning/angular/proxy.conf.json" + } }, "smoke": { "executor": "nx:run-commands", 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 6a802b7e1..6394149e9 100644 --- a/cockpit/deep-agents/planning/angular/src/app/planning.component.ts +++ b/cockpit/deep-agents/planning/angular/src/app/planning.component.ts @@ -1,18 +1,65 @@ -// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 -import { Component } from '@angular/core'; -import { ChatDebugComponent } from '@cacheplane/chat'; +import { Component, computed } from '@angular/core'; +import { LegacyChatComponent } from '@cacheplane/chat'; import { streamResource } from '@cacheplane/stream-resource'; import { environment } from '../environments/environment'; +interface PlanStep { + title: string; + status: 'pending' | 'running' | 'complete'; +} + +/** + * PlanningComponent demonstrates agent task decomposition. + * + * The agent receives a complex task, breaks it into ordered steps, + * and executes them. The sidebar shows each step's status in real time. + * + * Key integration points: + * - `stream.value()` contains the plan state with step list + * - `computed()` derives the plan steps for the sidebar + * - Steps update reactively as the agent works through them + */ @Component({ selector: 'app-planning', standalone: true, - imports: [ChatDebugComponent], - template: ``, + imports: [LegacyChatComponent], + template: ` + + +

Task Plan

+ @for (step of planSteps(); track $index) { +
+ + + {{ step.title }} + +
+ } + @empty { +

Ask a complex question to see the plan.

+ } +
+
+ `, }) export class PlanningComponent { protected readonly stream = streamResource({ apiUrl: environment.langGraphApiUrl, assistantId: environment.streamingAssistantId, }); + + planSteps = computed(() => { + const val = this.stream.value() as { plan?: PlanStep[] } | undefined; + return val?.plan ?? []; + }); + + send(text: string): void { + this.stream.submit({ messages: [{ role: 'human', content: text }] }); + } } diff --git a/cockpit/deep-agents/planning/angular/src/environments/environment.ts b/cockpit/deep-agents/planning/angular/src/environments/environment.ts index 890fbf4f7..28fa26ba9 100644 --- a/cockpit/deep-agents/planning/angular/src/environments/environment.ts +++ b/cockpit/deep-agents/planning/angular/src/environments/environment.ts @@ -6,6 +6,6 @@ */ export const environment = { production: true, - langGraphApiUrl: 'http://localhost:4310/api', + langGraphApiUrl: 'https://planning-7ca04c65ce7650048ec0d16fb96a7638.us.langgraph.app', streamingAssistantId: 'planning', }; diff --git a/cockpit/deep-agents/planning/angular/tsconfig.json b/cockpit/deep-agents/planning/angular/tsconfig.json index 27533b9cc..3fd970371 100644 --- a/cockpit/deep-agents/planning/angular/tsconfig.json +++ b/cockpit/deep-agents/planning/angular/tsconfig.json @@ -1,12 +1,24 @@ { "extends": "../../../../tsconfig.base.json", "compilerOptions": { + "noPropertyAccessFromIndexSignature": false, + "experimentalDecorators": true, "module": "preserve", + "emitDeclarationOnly": false, + "composite": false, + "lib": ["es2022", "dom"], "skipLibCheck": true, - "paths": { - "@cacheplane/stream-resource": ["../../../../../../libs/stream-resource/src/public-api.ts"], - "@cacheplane/chat": ["../../../../../../libs/chat/src/public-api.ts"] - } + "strict": false }, - "include": ["src/**/*.ts"] + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": false, + "strictInputAccessModifiers": false, + "strictTemplates": false + }, + "files": [], + "include": [], + "references": [ + { "path": "./tsconfig.app.json" } + ] } diff --git a/cockpit/deep-agents/planning/python/langgraph.json b/cockpit/deep-agents/planning/python/langgraph.json index 986d005ad..d7e8c1663 100644 --- a/cockpit/deep-agents/planning/python/langgraph.json +++ b/cockpit/deep-agents/planning/python/langgraph.json @@ -2,6 +2,6 @@ "graphs": { "planning": "./src/graph.py:graph" }, - "dependencies": ["./pyproject.toml"], - "env": ".env" + "dependencies": ["."], + "python_version": "3.12" } diff --git a/cockpit/deep-agents/sandboxes/angular/project.json b/cockpit/deep-agents/sandboxes/angular/project.json index ec70bd648..1569851dc 100644 --- a/cockpit/deep-agents/sandboxes/angular/project.json +++ b/cockpit/deep-agents/sandboxes/angular/project.json @@ -5,48 +5,44 @@ "projectType": "application", "targets": { "build": { - "executor": "@angular-devkit/build-angular:application", - "outputs": ["{options.outputPath}"], + "executor": "@angular/build:application", + "outputs": ["{options.outputPath.base}"], "options": { - "outputPath": "dist/cockpit/deep-agents/sandboxes/angular", - "index": "cockpit/deep-agents/sandboxes/angular/src/index.html", + "outputPath": { + "base": "dist/cockpit/deep-agents/sandboxes/angular", + "browser": "" + }, "browser": "cockpit/deep-agents/sandboxes/angular/src/main.ts", "tsConfig": "cockpit/deep-agents/sandboxes/angular/tsconfig.app.json", "styles": ["cockpit/deep-agents/sandboxes/angular/src/styles.css"] }, "configurations": { "production": { - "outputHashing": "all" + "budgets": [ + { "type": "initial", "maximumWarning": "500kb", "maximumError": "1mb" }, + { "type": "anyComponentStyle", "maximumWarning": "4kb", "maximumError": "8kb" } + ], + "outputHashing": "none" }, "development": { "optimization": false, "extractLicenses": false, - "sourceMap": true, - "fileReplacements": [ - { - "replace": "cockpit/deep-agents/sandboxes/angular/src/environments/environment.ts", - "with": "cockpit/deep-agents/sandboxes/angular/src/environments/environment.development.ts" - } - ] + "sourceMap": true } }, "defaultConfiguration": "production" }, "serve": { - "executor": "@angular-devkit/build-angular:dev-server", - "options": { - "port": 4315, - "proxyConfig": "cockpit/deep-agents/sandboxes/angular/proxy.conf.json" - }, + "continuous": true, + "executor": "@angular/build:dev-server", "configurations": { - "production": { - "buildTarget": "cockpit-deep-agents-sandboxes-angular:build:production" - }, - "development": { - "buildTarget": "cockpit-deep-agents-sandboxes-angular:build:development" - } + "production": { "buildTarget": "cockpit-deep-agents-sandboxes-angular:build:production" }, + "development": { "buildTarget": "cockpit-deep-agents-sandboxes-angular:build:development" } }, - "defaultConfiguration": "development" + "defaultConfiguration": "development", + "options": { + "proxyConfig": "cockpit/deep-agents/sandboxes/angular/proxy.conf.json" + } }, "smoke": { "executor": "nx:run-commands", 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 a07ae694e..ab8e8ab75 100644 --- a/cockpit/deep-agents/sandboxes/angular/src/app/sandboxes.component.ts +++ b/cockpit/deep-agents/sandboxes/angular/src/app/sandboxes.component.ts @@ -1,18 +1,96 @@ -// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 -import { Component } from '@angular/core'; -import { ChatDebugComponent } from '@cacheplane/chat'; +import { Component, computed } from '@angular/core'; +import { LegacyChatComponent } from '@cacheplane/chat'; import { streamResource } from '@cacheplane/stream-resource'; import { environment } from '../environments/environment'; +interface ExecutionLog { + code: string; + stdout: string; + exitStatus: number; +} + +/** + * SandboxesComponent demonstrates a coding agent that executes Python code. + * + * The agent writes and runs code snippets to solve problems using a + * `run_code` tool. The sidebar shows execution logs — code input, stdout + * output, and exit status — for each sandbox execution. + * + * Key integration points: + * - `stream.messages()` contains all messages including tool call results + * - `computed()` derives execution log entries from tool calls in AI messages + * - Logs update reactively as the agent writes and runs code + */ @Component({ selector: 'app-sandboxes', standalone: true, - imports: [ChatDebugComponent], - template: ``, + imports: [LegacyChatComponent], + template: ` + + +

Execution Logs

+ @for (log of executionLogs(); track $index) { +
+
+ + exit {{ log.exitStatus }} + +
+
{{ log.code }}
+ @if (log.stdout) { +
stdout
+
{{ log.stdout }}
+ } +
+ } + @empty { +

Ask the agent to write and run Python code.

+ } +
+
+ `, }) export class SandboxesComponent { protected readonly stream = streamResource({ apiUrl: environment.langGraphApiUrl, assistantId: environment.streamingAssistantId, }); + + executionLogs = computed(() => { + const msgs = this.stream.messages(); + const logs: ExecutionLog[] = []; + for (const m of msgs) { + if ((m as any).tool_calls) { + for (const tc of (m as any).tool_calls) { + if (tc.name === 'run_code' && tc.output) { + try { + const parsed = JSON.parse(tc.output); + logs.push({ + code: tc.args?.code ?? '', + stdout: parsed.stdout ?? '', + exitStatus: parsed.exit_status ?? 0, + }); + } catch { + logs.push({ + code: tc.args?.code ?? '', + stdout: tc.output, + exitStatus: 0, + }); + } + } + } + } + } + return logs; + }); + + send(text: string): void { + this.stream.submit({ messages: [{ role: 'human', content: text }] }); + } } diff --git a/cockpit/deep-agents/sandboxes/angular/src/environments/environment.ts b/cockpit/deep-agents/sandboxes/angular/src/environments/environment.ts index ece3e696f..8c4a57c37 100644 --- a/cockpit/deep-agents/sandboxes/angular/src/environments/environment.ts +++ b/cockpit/deep-agents/sandboxes/angular/src/environments/environment.ts @@ -6,6 +6,6 @@ */ export const environment = { production: true, - langGraphApiUrl: 'http://localhost:4315/api', + langGraphApiUrl: 'https://sandboxes-8c70b6ac20265827aa92397299fcb9f7.us.langgraph.app', streamingAssistantId: 'sandboxes', }; diff --git a/cockpit/deep-agents/sandboxes/angular/tsconfig.json b/cockpit/deep-agents/sandboxes/angular/tsconfig.json index 27533b9cc..3fd970371 100644 --- a/cockpit/deep-agents/sandboxes/angular/tsconfig.json +++ b/cockpit/deep-agents/sandboxes/angular/tsconfig.json @@ -1,12 +1,24 @@ { "extends": "../../../../tsconfig.base.json", "compilerOptions": { + "noPropertyAccessFromIndexSignature": false, + "experimentalDecorators": true, "module": "preserve", + "emitDeclarationOnly": false, + "composite": false, + "lib": ["es2022", "dom"], "skipLibCheck": true, - "paths": { - "@cacheplane/stream-resource": ["../../../../../../libs/stream-resource/src/public-api.ts"], - "@cacheplane/chat": ["../../../../../../libs/chat/src/public-api.ts"] - } + "strict": false }, - "include": ["src/**/*.ts"] + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": false, + "strictInputAccessModifiers": false, + "strictTemplates": false + }, + "files": [], + "include": [], + "references": [ + { "path": "./tsconfig.app.json" } + ] } diff --git a/cockpit/deep-agents/sandboxes/python/langgraph.json b/cockpit/deep-agents/sandboxes/python/langgraph.json index 4f59c7394..9837ec2d8 100644 --- a/cockpit/deep-agents/sandboxes/python/langgraph.json +++ b/cockpit/deep-agents/sandboxes/python/langgraph.json @@ -2,6 +2,6 @@ "graphs": { "sandboxes": "./src/graph.py:graph" }, - "dependencies": ["./pyproject.toml"], - "env": ".env" + "dependencies": ["."], + "python_version": "3.12" } diff --git a/cockpit/deep-agents/skills/angular/project.json b/cockpit/deep-agents/skills/angular/project.json index 7bdf63a8d..afc40b9b0 100644 --- a/cockpit/deep-agents/skills/angular/project.json +++ b/cockpit/deep-agents/skills/angular/project.json @@ -5,48 +5,44 @@ "projectType": "application", "targets": { "build": { - "executor": "@angular-devkit/build-angular:application", - "outputs": ["{options.outputPath}"], + "executor": "@angular/build:application", + "outputs": ["{options.outputPath.base}"], "options": { - "outputPath": "dist/cockpit/deep-agents/skills/angular", - "index": "cockpit/deep-agents/skills/angular/src/index.html", + "outputPath": { + "base": "dist/cockpit/deep-agents/skills/angular", + "browser": "" + }, "browser": "cockpit/deep-agents/skills/angular/src/main.ts", "tsConfig": "cockpit/deep-agents/skills/angular/tsconfig.app.json", "styles": ["cockpit/deep-agents/skills/angular/src/styles.css"] }, "configurations": { "production": { - "outputHashing": "all" + "budgets": [ + { "type": "initial", "maximumWarning": "500kb", "maximumError": "1mb" }, + { "type": "anyComponentStyle", "maximumWarning": "4kb", "maximumError": "8kb" } + ], + "outputHashing": "none" }, "development": { "optimization": false, "extractLicenses": false, - "sourceMap": true, - "fileReplacements": [ - { - "replace": "cockpit/deep-agents/skills/angular/src/environments/environment.ts", - "with": "cockpit/deep-agents/skills/angular/src/environments/environment.development.ts" - } - ] + "sourceMap": true } }, "defaultConfiguration": "production" }, "serve": { - "executor": "@angular-devkit/build-angular:dev-server", - "options": { - "port": 4314, - "proxyConfig": "cockpit/deep-agents/skills/angular/proxy.conf.json" - }, + "continuous": true, + "executor": "@angular/build:dev-server", "configurations": { - "production": { - "buildTarget": "cockpit-deep-agents-skills-angular:build:production" - }, - "development": { - "buildTarget": "cockpit-deep-agents-skills-angular:build:development" - } + "production": { "buildTarget": "cockpit-deep-agents-skills-angular:build:production" }, + "development": { "buildTarget": "cockpit-deep-agents-skills-angular:build:development" } }, - "defaultConfiguration": "development" + "defaultConfiguration": "development", + "options": { + "proxyConfig": "cockpit/deep-agents/skills/angular/proxy.conf.json" + } }, "smoke": { "executor": "nx:run-commands", 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 c2a65c2f7..718ce244f 100644 --- a/cockpit/deep-agents/skills/angular/src/app/skills.component.ts +++ b/cockpit/deep-agents/skills/angular/src/app/skills.component.ts @@ -1,18 +1,92 @@ -// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 -import { Component } from '@angular/core'; -import { ChatDebugComponent } from '@cacheplane/chat'; +import { Component, computed } from '@angular/core'; +import { LegacyChatComponent } from '@cacheplane/chat'; import { streamResource } from '@cacheplane/stream-resource'; import { environment } from '../environments/environment'; +interface SkillInvocation { + skillName: string; + args: string; + result?: string; +} + +/** + * SkillsComponent demonstrates a multi-skill agent with specialized tools. + * + * The agent can calculate math expressions, count words, and summarize text + * by selecting the appropriate skill tool for each user request. The sidebar + * shows each skill invocation as a card with the skill name, input args, + * and result. + * + * Key integration points: + * - `stream.messages()` contains all messages including tool call data + * - `computed()` derives skill invocation cards from tool calls in AI messages + * - Invocations update reactively as the agent calls and receives tool results + */ @Component({ selector: 'app-skills', standalone: true, - imports: [ChatDebugComponent], - template: ``, + imports: [LegacyChatComponent], + template: ` + + +

Skill Invocations

+ @for (inv of skillInvocations(); track $index) { +
+
+ + {{ inv.skillName }} + + @if (inv.result) { + done + } @else { + running… + } +
+
+ {{ inv.args }} +
+ @if (inv.result) { +
+ {{ inv.result }} +
+ } +
+ } + @empty { +

Ask the agent to calculate, count words, or summarize text.

+ } +
+
+ `, }) export class SkillsComponent { protected readonly stream = streamResource({ apiUrl: environment.langGraphApiUrl, assistantId: environment.streamingAssistantId, }); + + skillInvocations = computed(() => { + const msgs = this.stream.messages(); + const invocations: SkillInvocation[] = []; + for (const m of msgs) { + if ((m as any).tool_calls) { + for (const tc of (m as any).tool_calls) { + invocations.push({ + skillName: tc.name, + args: JSON.stringify(tc.args), + result: tc.output, + }); + } + } + } + return invocations; + }); + + send(text: string): void { + this.stream.submit({ messages: [{ role: 'human', content: text }] }); + } } diff --git a/cockpit/deep-agents/skills/angular/src/environments/environment.ts b/cockpit/deep-agents/skills/angular/src/environments/environment.ts index def71b25f..6c2d35f26 100644 --- a/cockpit/deep-agents/skills/angular/src/environments/environment.ts +++ b/cockpit/deep-agents/skills/angular/src/environments/environment.ts @@ -6,6 +6,6 @@ */ export const environment = { production: true, - langGraphApiUrl: 'http://localhost:4314/api', + langGraphApiUrl: 'https://skills-802ff50f64325f1ea973cff1c97a49f9.us.langgraph.app', streamingAssistantId: 'skills', }; diff --git a/cockpit/deep-agents/skills/angular/tsconfig.json b/cockpit/deep-agents/skills/angular/tsconfig.json index 27533b9cc..3fd970371 100644 --- a/cockpit/deep-agents/skills/angular/tsconfig.json +++ b/cockpit/deep-agents/skills/angular/tsconfig.json @@ -1,12 +1,24 @@ { "extends": "../../../../tsconfig.base.json", "compilerOptions": { + "noPropertyAccessFromIndexSignature": false, + "experimentalDecorators": true, "module": "preserve", + "emitDeclarationOnly": false, + "composite": false, + "lib": ["es2022", "dom"], "skipLibCheck": true, - "paths": { - "@cacheplane/stream-resource": ["../../../../../../libs/stream-resource/src/public-api.ts"], - "@cacheplane/chat": ["../../../../../../libs/chat/src/public-api.ts"] - } + "strict": false }, - "include": ["src/**/*.ts"] + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": false, + "strictInputAccessModifiers": false, + "strictTemplates": false + }, + "files": [], + "include": [], + "references": [ + { "path": "./tsconfig.app.json" } + ] } diff --git a/cockpit/deep-agents/skills/python/langgraph.json b/cockpit/deep-agents/skills/python/langgraph.json index 8b4633b82..eb40523e8 100644 --- a/cockpit/deep-agents/skills/python/langgraph.json +++ b/cockpit/deep-agents/skills/python/langgraph.json @@ -2,6 +2,6 @@ "graphs": { "skills": "./src/graph.py:graph" }, - "dependencies": ["./pyproject.toml"], - "env": ".env" + "dependencies": ["."], + "python_version": "3.12" } diff --git a/cockpit/deep-agents/subagents/angular/project.json b/cockpit/deep-agents/subagents/angular/project.json index 7e8e2f20b..cf11d3ca5 100644 --- a/cockpit/deep-agents/subagents/angular/project.json +++ b/cockpit/deep-agents/subagents/angular/project.json @@ -5,48 +5,44 @@ "projectType": "application", "targets": { "build": { - "executor": "@angular-devkit/build-angular:application", - "outputs": ["{options.outputPath}"], + "executor": "@angular/build:application", + "outputs": ["{options.outputPath.base}"], "options": { - "outputPath": "dist/cockpit/deep-agents/subagents/angular", - "index": "cockpit/deep-agents/subagents/angular/src/index.html", + "outputPath": { + "base": "dist/cockpit/deep-agents/subagents/angular", + "browser": "" + }, "browser": "cockpit/deep-agents/subagents/angular/src/main.ts", "tsConfig": "cockpit/deep-agents/subagents/angular/tsconfig.app.json", "styles": ["cockpit/deep-agents/subagents/angular/src/styles.css"] }, "configurations": { "production": { - "outputHashing": "all" + "budgets": [ + { "type": "initial", "maximumWarning": "500kb", "maximumError": "1mb" }, + { "type": "anyComponentStyle", "maximumWarning": "4kb", "maximumError": "8kb" } + ], + "outputHashing": "none" }, "development": { "optimization": false, "extractLicenses": false, - "sourceMap": true, - "fileReplacements": [ - { - "replace": "cockpit/deep-agents/subagents/angular/src/environments/environment.ts", - "with": "cockpit/deep-agents/subagents/angular/src/environments/environment.development.ts" - } - ] + "sourceMap": true } }, "defaultConfiguration": "production" }, "serve": { - "executor": "@angular-devkit/build-angular:dev-server", - "options": { - "port": 4312, - "proxyConfig": "cockpit/deep-agents/subagents/angular/proxy.conf.json" - }, + "continuous": true, + "executor": "@angular/build:dev-server", "configurations": { - "production": { - "buildTarget": "cockpit-deep-agents-subagents-angular:build:production" - }, - "development": { - "buildTarget": "cockpit-deep-agents-subagents-angular:build:development" - } + "production": { "buildTarget": "cockpit-deep-agents-subagents-angular:build:production" }, + "development": { "buildTarget": "cockpit-deep-agents-subagents-angular:build:development" } }, - "defaultConfiguration": "development" + "defaultConfiguration": "development", + "options": { + "proxyConfig": "cockpit/deep-agents/subagents/angular/proxy.conf.json" + } }, "smoke": { "executor": "nx:run-commands", 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 4dd419283..1868c0a6a 100644 --- a/cockpit/deep-agents/subagents/angular/src/app/subagents.component.ts +++ b/cockpit/deep-agents/subagents/angular/src/app/subagents.component.ts @@ -1,18 +1,67 @@ -// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 -import { Component } from '@angular/core'; -import { ChatDebugComponent } from '@cacheplane/chat'; +import { Component, computed } from '@angular/core'; +import { LegacyChatComponent } from '@cacheplane/chat'; import { streamResource } from '@cacheplane/stream-resource'; import { environment } from '../environments/environment'; +/** + * SubagentsComponent demonstrates the Deep Agents subagent delegation pattern. + * + * The orchestrator agent receives a task and delegates subtasks to specialist + * subagents via tool calls. Each tool call spawns a child agent that streams + * its own progress independently. + * + * Key integration points: + * - `stream.subagents()` returns a Map + * - `subagentEntries` derives a sorted array for sidebar rendering + * - Each entry shows the tool call ID (truncated), status badge, and message count + * - Subagent statuses update reactively: pending → running → complete + */ @Component({ selector: 'app-subagents', standalone: true, - imports: [ChatDebugComponent], - template: ``, + imports: [LegacyChatComponent], + template: ` + + +

Subagents

+ @for (entry of subagentEntries(); track entry[0]) { +
+
+ + {{ entry[1].status() }} + + + {{ entry[0].slice(0, 8) }}… + +
+
+ {{ entry[1].messages().length }} message{{ entry[1].messages().length === 1 ? '' : 's' }} +
+
+ } + @empty { +

Ask a question to see subagent activity.

+ } +
+
+ `, }) export class SubagentsComponent { protected readonly stream = streamResource({ apiUrl: environment.langGraphApiUrl, assistantId: environment.streamingAssistantId, }); + + subagentEntries = computed(() => Array.from(this.stream.subagents().entries())); + + send(text: string): void { + this.stream.submit({ messages: [{ role: 'human', content: text }] }); + } } diff --git a/cockpit/deep-agents/subagents/angular/src/environments/environment.ts b/cockpit/deep-agents/subagents/angular/src/environments/environment.ts index e33d6f615..6764b6c4a 100644 --- a/cockpit/deep-agents/subagents/angular/src/environments/environment.ts +++ b/cockpit/deep-agents/subagents/angular/src/environments/environment.ts @@ -6,6 +6,6 @@ */ export const environment = { production: true, - langGraphApiUrl: 'http://localhost:4312/api', + langGraphApiUrl: 'https://da-subagents-31e4639441165df7848aaad426e61728.us.langgraph.app', streamingAssistantId: 'subagents', }; diff --git a/cockpit/deep-agents/subagents/angular/tsconfig.json b/cockpit/deep-agents/subagents/angular/tsconfig.json index 27533b9cc..3fd970371 100644 --- a/cockpit/deep-agents/subagents/angular/tsconfig.json +++ b/cockpit/deep-agents/subagents/angular/tsconfig.json @@ -1,12 +1,24 @@ { "extends": "../../../../tsconfig.base.json", "compilerOptions": { + "noPropertyAccessFromIndexSignature": false, + "experimentalDecorators": true, "module": "preserve", + "emitDeclarationOnly": false, + "composite": false, + "lib": ["es2022", "dom"], "skipLibCheck": true, - "paths": { - "@cacheplane/stream-resource": ["../../../../../../libs/stream-resource/src/public-api.ts"], - "@cacheplane/chat": ["../../../../../../libs/chat/src/public-api.ts"] - } + "strict": false }, - "include": ["src/**/*.ts"] + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": false, + "strictInputAccessModifiers": false, + "strictTemplates": false + }, + "files": [], + "include": [], + "references": [ + { "path": "./tsconfig.app.json" } + ] } diff --git a/cockpit/deep-agents/subagents/python/langgraph.json b/cockpit/deep-agents/subagents/python/langgraph.json index 599d1b8b2..8fae177b7 100644 --- a/cockpit/deep-agents/subagents/python/langgraph.json +++ b/cockpit/deep-agents/subagents/python/langgraph.json @@ -2,6 +2,6 @@ "graphs": { "subagents": "./src/graph.py:graph" }, - "dependencies": ["./pyproject.toml"], - "env": ".env" + "dependencies": ["."], + "python_version": "3.12" } diff --git a/cockpit/langgraph/deployment-runtime/angular/project.json b/cockpit/langgraph/deployment-runtime/angular/project.json index 6a8f11507..903598fdc 100644 --- a/cockpit/langgraph/deployment-runtime/angular/project.json +++ b/cockpit/langgraph/deployment-runtime/angular/project.json @@ -5,48 +5,44 @@ "projectType": "application", "targets": { "build": { - "executor": "@angular-devkit/build-angular:application", - "outputs": ["{options.outputPath}"], + "executor": "@angular/build:application", + "outputs": ["{options.outputPath.base}"], "options": { - "outputPath": "dist/cockpit/langgraph/deployment-runtime/angular", - "index": "cockpit/langgraph/deployment-runtime/angular/src/index.html", + "outputPath": { + "base": "dist/cockpit/langgraph/deployment-runtime/angular", + "browser": "" + }, "browser": "cockpit/langgraph/deployment-runtime/angular/src/main.ts", "tsConfig": "cockpit/langgraph/deployment-runtime/angular/tsconfig.app.json", "styles": ["cockpit/langgraph/deployment-runtime/angular/src/styles.css"] }, "configurations": { "production": { - "outputHashing": "all" + "budgets": [ + { "type": "initial", "maximumWarning": "500kb", "maximumError": "1mb" }, + { "type": "anyComponentStyle", "maximumWarning": "4kb", "maximumError": "8kb" } + ], + "outputHashing": "none" }, "development": { "optimization": false, "extractLicenses": false, - "sourceMap": true, - "fileReplacements": [ - { - "replace": "cockpit/langgraph/deployment-runtime/angular/src/environments/environment.ts", - "with": "cockpit/langgraph/deployment-runtime/angular/src/environments/environment.development.ts" - } - ] + "sourceMap": true } }, "defaultConfiguration": "production" }, "serve": { - "executor": "@angular-devkit/build-angular:dev-server", - "options": { - "port": 4307, - "proxyConfig": "cockpit/langgraph/deployment-runtime/angular/proxy.conf.json" - }, + "continuous": true, + "executor": "@angular/build:dev-server", "configurations": { - "production": { - "buildTarget": "cockpit-langgraph-deployment-runtime-angular:build:production" - }, - "development": { - "buildTarget": "cockpit-langgraph-deployment-runtime-angular:build:development" - } + "production": { "buildTarget": "cockpit-langgraph-deployment-runtime-angular:build:production" }, + "development": { "buildTarget": "cockpit-langgraph-deployment-runtime-angular:build:development" } }, - "defaultConfiguration": "development" + "defaultConfiguration": "development", + "options": { + "proxyConfig": "cockpit/langgraph/deployment-runtime/angular/proxy.conf.json" + } }, "smoke": { "executor": "nx:run-commands", 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 05410c24f..856a4cbb9 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,18 +1,90 @@ -// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 import { Component } from '@angular/core'; -import { ChatComponent } from '@cacheplane/chat'; +import { LegacyChatComponent } from '@cacheplane/chat'; import { streamResource } from '@cacheplane/stream-resource'; import { environment } from '../environments/environment'; +/** + * DeploymentRuntimeComponent demonstrates production deployment patterns. + * + * Shows how streamResource() connects to a deployed LangGraph Cloud + * instance. The sidebar displays connection info and deployment status. + */ @Component({ selector: 'app-deployment-runtime', standalone: true, - imports: [ChatComponent], - template: ``, + imports: [LegacyChatComponent], + template: ` + + +

Deployment

+ +
+
API URL
+
+ {{ apiUrl }} +
+
+ +
+
Assistant ID
+
+ {{ assistantId }} +
+
+ +
+
Status
+ + {{ stream.status() }} + +
+ + @if (currentThreadId) { +
+
Thread ID
+
+ {{ currentThreadId }} +
+
+ } +
+
+ `, }) export class DeploymentRuntimeComponent { protected readonly stream = streamResource({ apiUrl: environment.langGraphApiUrl, assistantId: environment.deploymentRuntimeAssistantId, + onThreadId: (id: string) => { + this.currentThreadId = id; + }, }); + + readonly apiUrl = environment.langGraphApiUrl; + readonly assistantId = environment.deploymentRuntimeAssistantId; + currentThreadId = ''; + + send(text: string): void { + this.stream.submit({ messages: [{ role: 'human', content: text }] }); + } + + statusBadgeBackground(): string { + const status = this.stream.status(); + if (status === 'loading') return 'rgba(0,160,80,0.12)'; + if (status === 'error') return 'rgba(200,40,40,0.1)'; + return 'rgba(0,64,144,0.08)'; + } + + statusBadgeColor(): string { + const status = this.stream.status(); + if (status === 'loading') return '#00802a'; + if (status === 'error') return '#c82828'; + return '#004090'; + } } diff --git a/cockpit/langgraph/deployment-runtime/angular/src/environments/environment.ts b/cockpit/langgraph/deployment-runtime/angular/src/environments/environment.ts index 301f8de44..4b03ed66b 100644 --- a/cockpit/langgraph/deployment-runtime/angular/src/environments/environment.ts +++ b/cockpit/langgraph/deployment-runtime/angular/src/environments/environment.ts @@ -6,6 +6,6 @@ */ export const environment = { production: true, - langGraphApiUrl: 'http://localhost:4307/api', + langGraphApiUrl: 'https://deployment-runtime-ce6aad33cc10505faca2b6137e76ba35.us.langgraph.app', deploymentRuntimeAssistantId: 'deployment-runtime', }; diff --git a/cockpit/langgraph/deployment-runtime/angular/tsconfig.json b/cockpit/langgraph/deployment-runtime/angular/tsconfig.json index 7e71d1c7c..3fd970371 100644 --- a/cockpit/langgraph/deployment-runtime/angular/tsconfig.json +++ b/cockpit/langgraph/deployment-runtime/angular/tsconfig.json @@ -1,12 +1,24 @@ { "extends": "../../../../tsconfig.base.json", "compilerOptions": { + "noPropertyAccessFromIndexSignature": false, + "experimentalDecorators": true, "module": "preserve", + "emitDeclarationOnly": false, + "composite": false, + "lib": ["es2022", "dom"], "skipLibCheck": true, - "paths": { - "@cacheplane/stream-resource": ["../../../../libs/stream-resource/src/public-api.ts"], - "@cacheplane/chat": ["../../../../libs/chat/src/public-api.ts"] - } + "strict": false }, - "include": ["src/**/*.ts"] + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": false, + "strictInputAccessModifiers": false, + "strictTemplates": false + }, + "files": [], + "include": [], + "references": [ + { "path": "./tsconfig.app.json" } + ] } diff --git a/cockpit/langgraph/deployment-runtime/python/langgraph.json b/cockpit/langgraph/deployment-runtime/python/langgraph.json index 852104592..8deb24c0c 100644 --- a/cockpit/langgraph/deployment-runtime/python/langgraph.json +++ b/cockpit/langgraph/deployment-runtime/python/langgraph.json @@ -2,6 +2,6 @@ "graphs": { "deployment-runtime": "./src/graph.py:graph" }, - "dependencies": ["./pyproject.toml"], - "env": ".env" + "dependencies": ["."], + "python_version": "3.12" } diff --git a/cockpit/langgraph/durable-execution/angular/project.json b/cockpit/langgraph/durable-execution/angular/project.json index a98d4b82a..d6a00bf02 100644 --- a/cockpit/langgraph/durable-execution/angular/project.json +++ b/cockpit/langgraph/durable-execution/angular/project.json @@ -5,48 +5,44 @@ "projectType": "application", "targets": { "build": { - "executor": "@angular-devkit/build-angular:application", - "outputs": ["{options.outputPath}"], + "executor": "@angular/build:application", + "outputs": ["{options.outputPath.base}"], "options": { - "outputPath": "dist/cockpit/langgraph/durable-execution/angular", - "index": "cockpit/langgraph/durable-execution/angular/src/index.html", + "outputPath": { + "base": "dist/cockpit/langgraph/durable-execution/angular", + "browser": "" + }, "browser": "cockpit/langgraph/durable-execution/angular/src/main.ts", "tsConfig": "cockpit/langgraph/durable-execution/angular/tsconfig.app.json", "styles": ["cockpit/langgraph/durable-execution/angular/src/styles.css"] }, "configurations": { "production": { - "outputHashing": "all" + "budgets": [ + { "type": "initial", "maximumWarning": "500kb", "maximumError": "1mb" }, + { "type": "anyComponentStyle", "maximumWarning": "4kb", "maximumError": "8kb" } + ], + "outputHashing": "none" }, "development": { "optimization": false, "extractLicenses": false, - "sourceMap": true, - "fileReplacements": [ - { - "replace": "cockpit/langgraph/durable-execution/angular/src/environments/environment.ts", - "with": "cockpit/langgraph/durable-execution/angular/src/environments/environment.development.ts" - } - ] + "sourceMap": true } }, "defaultConfiguration": "production" }, "serve": { - "executor": "@angular-devkit/build-angular:dev-server", - "options": { - "port": 4306, - "proxyConfig": "cockpit/langgraph/durable-execution/angular/proxy.conf.json" - }, + "continuous": true, + "executor": "@angular/build:dev-server", "configurations": { - "production": { - "buildTarget": "cockpit-langgraph-durable-execution-angular:build:production" - }, - "development": { - "buildTarget": "cockpit-langgraph-durable-execution-angular:build:development" - } + "production": { "buildTarget": "cockpit-langgraph-durable-execution-angular:build:production" }, + "development": { "buildTarget": "cockpit-langgraph-durable-execution-angular:build:development" } }, - "defaultConfiguration": "development" + "defaultConfiguration": "development", + "options": { + "proxyConfig": "cockpit/langgraph/durable-execution/angular/proxy.conf.json" + } }, "smoke": { "executor": "nx:run-commands", 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 784eafcb8..2aa442d12 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,18 +1,96 @@ -// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 import { Component } from '@angular/core'; -import { ChatComponent } from '@cacheplane/chat'; +import { LegacyChatComponent } from '@cacheplane/chat'; import { streamResource } from '@cacheplane/stream-resource'; import { environment } from '../environments/environment'; +/** + * DurableExecutionComponent demonstrates fault-tolerant multi-step execution + * with `streamResource()`. + * + * This example shows how a graph checkpoints at each node, enabling it to + * resume after failures. The sidebar shows execution status in real time: + * - `stream.status()` as a badge (idle/loading/resolved/error) + * - `stream.hasValue()` indicator for received data + * - A "Retry" button that calls `stream.reload()` when `stream.error()` is set + * + * The backend processes each request through three nodes: + * analyze → plan → generate + * Each node updates `state.step` so the UI can track progress. + */ @Component({ selector: 'app-durable-execution', standalone: true, - imports: [ChatComponent], - template: ``, + imports: [LegacyChatComponent], + template: ` + + +

Execution Status

+ +
+ Status +
+ + {{ stream.status() }} + +
+
+ +
+ Data Received +
+ + {{ stream.hasValue() ? 'Yes' : 'No' }} +
+
+ + @if (stream.error()) { +
+
Execution Failed
+ +
+ } +
+
+ `, }) export class DurableExecutionComponent { + /** + * The streaming resource backing this durable-execution demo. + * + * The graph runs three nodes (analyze → plan → generate), checkpointing + * after each one. If the graph fails partway through, `stream.reload()` + * re-submits the last input so the run can resume from the last checkpoint. + */ protected readonly stream = streamResource({ apiUrl: environment.langGraphApiUrl, assistantId: environment.streamingAssistantId, }); + + /** + * Submit a message to be processed through the multi-node graph. + */ + send(text: string): void { + this.stream.submit({ messages: [{ role: 'human', content: text }] }); + } + + /** + * Returns a colour for the status badge based on the current stream status. + */ + statusBadgeColor(): string { + switch (this.stream.status()) { + case 'loading': + case 'reloading': return '#2563eb'; + case 'resolved': return '#16a34a'; + case 'error': return '#dc2626'; + default: return '#6b7280'; + } + } } diff --git a/cockpit/langgraph/durable-execution/angular/src/environments/environment.ts b/cockpit/langgraph/durable-execution/angular/src/environments/environment.ts index ebfc111d4..218ea72c1 100644 --- a/cockpit/langgraph/durable-execution/angular/src/environments/environment.ts +++ b/cockpit/langgraph/durable-execution/angular/src/environments/environment.ts @@ -6,6 +6,6 @@ */ export const environment = { production: true, - langGraphApiUrl: 'http://localhost:4304/api', + langGraphApiUrl: 'https://durable-execution-123221d8b543545399d252dc6bd7de1b.us.langgraph.app', streamingAssistantId: 'durable-execution', }; diff --git a/cockpit/langgraph/durable-execution/angular/tsconfig.json b/cockpit/langgraph/durable-execution/angular/tsconfig.json index 7e71d1c7c..3fd970371 100644 --- a/cockpit/langgraph/durable-execution/angular/tsconfig.json +++ b/cockpit/langgraph/durable-execution/angular/tsconfig.json @@ -1,12 +1,24 @@ { "extends": "../../../../tsconfig.base.json", "compilerOptions": { + "noPropertyAccessFromIndexSignature": false, + "experimentalDecorators": true, "module": "preserve", + "emitDeclarationOnly": false, + "composite": false, + "lib": ["es2022", "dom"], "skipLibCheck": true, - "paths": { - "@cacheplane/stream-resource": ["../../../../libs/stream-resource/src/public-api.ts"], - "@cacheplane/chat": ["../../../../libs/chat/src/public-api.ts"] - } + "strict": false }, - "include": ["src/**/*.ts"] + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": false, + "strictInputAccessModifiers": false, + "strictTemplates": false + }, + "files": [], + "include": [], + "references": [ + { "path": "./tsconfig.app.json" } + ] } diff --git a/cockpit/langgraph/durable-execution/python/langgraph.json b/cockpit/langgraph/durable-execution/python/langgraph.json index 92105f973..28e64c336 100644 --- a/cockpit/langgraph/durable-execution/python/langgraph.json +++ b/cockpit/langgraph/durable-execution/python/langgraph.json @@ -2,6 +2,6 @@ "graphs": { "durable-execution": "./src/graph.py:graph" }, - "dependencies": ["./pyproject.toml"], - "env": ".env" + "dependencies": ["."], + "python_version": "3.12" } diff --git a/cockpit/langgraph/interrupts/angular/project.json b/cockpit/langgraph/interrupts/angular/project.json index 94f888503..e395a7bdd 100644 --- a/cockpit/langgraph/interrupts/angular/project.json +++ b/cockpit/langgraph/interrupts/angular/project.json @@ -5,48 +5,44 @@ "projectType": "application", "targets": { "build": { - "executor": "@angular-devkit/build-angular:application", - "outputs": ["{options.outputPath}"], + "executor": "@angular/build:application", + "outputs": ["{options.outputPath.base}"], "options": { - "outputPath": "dist/cockpit/langgraph/interrupts/angular", - "index": "cockpit/langgraph/interrupts/angular/src/index.html", + "outputPath": { + "base": "dist/cockpit/langgraph/interrupts/angular", + "browser": "" + }, "browser": "cockpit/langgraph/interrupts/angular/src/main.ts", "tsConfig": "cockpit/langgraph/interrupts/angular/tsconfig.app.json", "styles": ["cockpit/langgraph/interrupts/angular/src/styles.css"] }, "configurations": { "production": { - "outputHashing": "all" + "budgets": [ + { "type": "initial", "maximumWarning": "500kb", "maximumError": "1mb" }, + { "type": "anyComponentStyle", "maximumWarning": "4kb", "maximumError": "8kb" } + ], + "outputHashing": "none" }, "development": { "optimization": false, "extractLicenses": false, - "sourceMap": true, - "fileReplacements": [ - { - "replace": "cockpit/langgraph/interrupts/angular/src/environments/environment.ts", - "with": "cockpit/langgraph/interrupts/angular/src/environments/environment.development.ts" - } - ] + "sourceMap": true } }, "defaultConfiguration": "production" }, "serve": { - "executor": "@angular-devkit/build-angular:dev-server", - "options": { - "port": 4302, - "proxyConfig": "cockpit/langgraph/interrupts/angular/proxy.conf.json" - }, + "continuous": true, + "executor": "@angular/build:dev-server", "configurations": { - "production": { - "buildTarget": "cockpit-langgraph-interrupts-angular:build:production" - }, - "development": { - "buildTarget": "cockpit-langgraph-interrupts-angular:build:development" - } + "production": { "buildTarget": "cockpit-langgraph-interrupts-angular:build:production" }, + "development": { "buildTarget": "cockpit-langgraph-interrupts-angular:build:development" } }, - "defaultConfiguration": "development" + "defaultConfiguration": "development", + "options": { + "proxyConfig": "cockpit/langgraph/interrupts/angular/proxy.conf.json" + } }, "smoke": { "executor": "nx:run-commands", diff --git a/cockpit/langgraph/interrupts/angular/src/app/interrupts.component.ts b/cockpit/langgraph/interrupts/angular/src/app/interrupts.component.ts index c7bb89986..57b9428b1 100644 --- a/cockpit/langgraph/interrupts/angular/src/app/interrupts.component.ts +++ b/cockpit/langgraph/interrupts/angular/src/app/interrupts.component.ts @@ -1,18 +1,83 @@ -// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 import { Component } from '@angular/core'; -import { ChatComponent } from '@cacheplane/chat'; +import { LegacyChatComponent } from '@cacheplane/chat'; import { streamResource } from '@cacheplane/stream-resource'; import { environment } from '../environments/environment'; +/** + * InterruptsComponent demonstrates human-in-the-loop with `streamResource()`. + * + * The LangGraph backend pauses execution when it needs human approval. + * The `stream.interrupt()` signal provides the interrupt data, and + * `stream.submit()` resumes execution with the human's decision. + * + * Key integration points: + * - `stream.interrupt()` — current pause data + * - `stream.submit({ resume: true })` — resume after approval + * - The graph uses LangGraph's `interrupt()` function to pause + */ @Component({ selector: 'app-interrupts', standalone: true, - imports: [ChatComponent], - template: ``, + imports: [LegacyChatComponent], + template: ` + + +

Approvals

+ @if (stream.interrupt()) { +
+

{{ stream.interrupt() }}

+ + +
+ } @else { +

No pending approvals

+ } +
+
+ `, }) export class InterruptsComponent { + /** + * The streaming resource with interrupt support. + * + * When the LangGraph backend calls `interrupt()`, the `stream.interrupt()` + * signal emits the interrupt payload for display in the sidebar. + */ protected readonly stream = streamResource({ apiUrl: environment.langGraphApiUrl, assistantId: environment.streamingAssistantId, }); + + /** + * Submit a message to the assistant. + */ + send(text: string): void { + this.stream.submit({ messages: [{ role: 'human', content: text }] }); + } + + /** + * Approve the pending action and resume execution. + * Submitting null continues the graph (LangGraph convention). + */ + approve(): void { + this.stream.submit(null); + } + + /** + * Reject the pending action. Sends a resume value of false + * so the graph can handle rejection logic. + */ + reject(): void { + this.stream.submit({ resume: false }); + } } diff --git a/cockpit/langgraph/interrupts/angular/src/environments/environment.ts b/cockpit/langgraph/interrupts/angular/src/environments/environment.ts index ec2a27c6a..5ac9d8cdc 100644 --- a/cockpit/langgraph/interrupts/angular/src/environments/environment.ts +++ b/cockpit/langgraph/interrupts/angular/src/environments/environment.ts @@ -6,6 +6,6 @@ */ export const environment = { production: true, - langGraphApiUrl: 'http://localhost:4302/api', + langGraphApiUrl: 'https://interrupts-8e1524d6d8fb558381eed4618129bc50.us.langgraph.app', streamingAssistantId: 'interrupts', }; diff --git a/cockpit/langgraph/interrupts/angular/tsconfig.json b/cockpit/langgraph/interrupts/angular/tsconfig.json index 7e71d1c7c..3fd970371 100644 --- a/cockpit/langgraph/interrupts/angular/tsconfig.json +++ b/cockpit/langgraph/interrupts/angular/tsconfig.json @@ -1,12 +1,24 @@ { "extends": "../../../../tsconfig.base.json", "compilerOptions": { + "noPropertyAccessFromIndexSignature": false, + "experimentalDecorators": true, "module": "preserve", + "emitDeclarationOnly": false, + "composite": false, + "lib": ["es2022", "dom"], "skipLibCheck": true, - "paths": { - "@cacheplane/stream-resource": ["../../../../libs/stream-resource/src/public-api.ts"], - "@cacheplane/chat": ["../../../../libs/chat/src/public-api.ts"] - } + "strict": false }, - "include": ["src/**/*.ts"] + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": false, + "strictInputAccessModifiers": false, + "strictTemplates": false + }, + "files": [], + "include": [], + "references": [ + { "path": "./tsconfig.app.json" } + ] } diff --git a/cockpit/langgraph/interrupts/python/langgraph.json b/cockpit/langgraph/interrupts/python/langgraph.json index 53806e03a..e989f3a9b 100644 --- a/cockpit/langgraph/interrupts/python/langgraph.json +++ b/cockpit/langgraph/interrupts/python/langgraph.json @@ -2,6 +2,6 @@ "graphs": { "interrupts": "./src/graph.py:graph" }, - "dependencies": ["./pyproject.toml"], - "env": ".env" + "dependencies": ["."], + "python_version": "3.12" } diff --git a/cockpit/langgraph/memory/angular/project.json b/cockpit/langgraph/memory/angular/project.json index 8f9ed1a7f..51e52baf7 100644 --- a/cockpit/langgraph/memory/angular/project.json +++ b/cockpit/langgraph/memory/angular/project.json @@ -5,48 +5,44 @@ "projectType": "application", "targets": { "build": { - "executor": "@angular-devkit/build-angular:application", - "outputs": ["{options.outputPath}"], + "executor": "@angular/build:application", + "outputs": ["{options.outputPath.base}"], "options": { - "outputPath": "dist/cockpit/langgraph/memory/angular", - "index": "cockpit/langgraph/memory/angular/src/index.html", + "outputPath": { + "base": "dist/cockpit/langgraph/memory/angular", + "browser": "" + }, "browser": "cockpit/langgraph/memory/angular/src/main.ts", "tsConfig": "cockpit/langgraph/memory/angular/tsconfig.app.json", "styles": ["cockpit/langgraph/memory/angular/src/styles.css"] }, "configurations": { "production": { - "outputHashing": "all" + "budgets": [ + { "type": "initial", "maximumWarning": "500kb", "maximumError": "1mb" }, + { "type": "anyComponentStyle", "maximumWarning": "4kb", "maximumError": "8kb" } + ], + "outputHashing": "none" }, "development": { "optimization": false, "extractLicenses": false, - "sourceMap": true, - "fileReplacements": [ - { - "replace": "cockpit/langgraph/memory/angular/src/environments/environment.ts", - "with": "cockpit/langgraph/memory/angular/src/environments/environment.development.ts" - } - ] + "sourceMap": true } }, "defaultConfiguration": "production" }, "serve": { - "executor": "@angular-devkit/build-angular:dev-server", - "options": { - "port": 4303, - "proxyConfig": "cockpit/langgraph/memory/angular/proxy.conf.json" - }, + "continuous": true, + "executor": "@angular/build:dev-server", "configurations": { - "production": { - "buildTarget": "cockpit-langgraph-memory-angular:build:production" - }, - "development": { - "buildTarget": "cockpit-langgraph-memory-angular:build:development" - } + "production": { "buildTarget": "cockpit-langgraph-memory-angular:build:production" }, + "development": { "buildTarget": "cockpit-langgraph-memory-angular:build:development" } }, - "defaultConfiguration": "development" + "defaultConfiguration": "development", + "options": { + "proxyConfig": "cockpit/langgraph/memory/angular/proxy.conf.json" + } }, "smoke": { "executor": "nx:run-commands", diff --git a/cockpit/langgraph/memory/angular/src/app/memory.component.ts b/cockpit/langgraph/memory/angular/src/app/memory.component.ts index 0c6c171ca..c6ca4fffd 100644 --- a/cockpit/langgraph/memory/angular/src/app/memory.component.ts +++ b/cockpit/langgraph/memory/angular/src/app/memory.component.ts @@ -1,18 +1,82 @@ -// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 -import { Component } from '@angular/core'; -import { ChatComponent } from '@cacheplane/chat'; +import { Component, computed } from '@angular/core'; +import { LegacyChatComponent } from '@cacheplane/chat'; import { streamResource } from '@cacheplane/stream-resource'; import { environment } from '../environments/environment'; +/** + * MemoryComponent demonstrates cross-thread persistent context with `streamResource()`. + * + * This example shows how an agent can learn and remember facts about the user + * across separate conversations. The graph maintains a `memory` dict in its + * state that is updated as new facts are extracted from the conversation. + * + * Key integration points: + * - `stream.value()` exposes the full graph state, including the `memory` field + * - `memory()` signal is derived from `stream.value()` for reactive sidebar rendering + * - Facts appear in the sidebar as the agent learns them during conversation + */ @Component({ selector: 'app-memory', standalone: true, - imports: [ChatComponent], - template: ``, + imports: [LegacyChatComponent], + template: ` + + +

Agent Memory

+ @if (memoryEntries().length === 0) { +

+ No facts learned yet. Start chatting! +

+ } + @for (entry of memoryEntries(); track entry.key) { +
+
+ {{ entry.key }} +
+
+ {{ entry.value }} +
+
+ } +
+
+ `, }) export class MemoryComponent { + /** + * The streaming resource connected to the memory graph. + * + * The graph returns a `memory` dict alongside messages in its state. + * We expose it via `stream.value()` and derive a reactive signal for display. + */ protected readonly stream = streamResource({ apiUrl: environment.langGraphApiUrl, assistantId: environment.streamingAssistantId, }); + + /** + * Reactive list of key-value memory entries derived from the graph state. + * + * The graph updates `memory` as it learns facts from the conversation. + * This signal re-computes whenever the stream state changes. + */ + protected readonly memoryEntries = computed(() => { + const state = this.stream.value() as { memory?: Record } | null; + const memory = state?.memory ?? {}; + return Object.entries(memory).map(([key, value]) => ({ + key, + value: typeof value === 'string' ? value : JSON.stringify(value), + })); + }); + + /** + * Submit a message to the agent. + */ + send(text: string): void { + this.stream.submit({ messages: [{ role: 'human', content: text }] }); + } } diff --git a/cockpit/langgraph/memory/angular/src/environments/environment.ts b/cockpit/langgraph/memory/angular/src/environments/environment.ts index 0e00d8359..b3186b0f2 100644 --- a/cockpit/langgraph/memory/angular/src/environments/environment.ts +++ b/cockpit/langgraph/memory/angular/src/environments/environment.ts @@ -6,6 +6,6 @@ */ export const environment = { production: true, - langGraphApiUrl: 'http://localhost:4303/api', + langGraphApiUrl: 'https://memory-1b3234dbe2e55ba59010b3469be45a0a.us.langgraph.app', streamingAssistantId: 'memory', }; diff --git a/cockpit/langgraph/memory/angular/tsconfig.json b/cockpit/langgraph/memory/angular/tsconfig.json index 7e71d1c7c..3fd970371 100644 --- a/cockpit/langgraph/memory/angular/tsconfig.json +++ b/cockpit/langgraph/memory/angular/tsconfig.json @@ -1,12 +1,24 @@ { "extends": "../../../../tsconfig.base.json", "compilerOptions": { + "noPropertyAccessFromIndexSignature": false, + "experimentalDecorators": true, "module": "preserve", + "emitDeclarationOnly": false, + "composite": false, + "lib": ["es2022", "dom"], "skipLibCheck": true, - "paths": { - "@cacheplane/stream-resource": ["../../../../libs/stream-resource/src/public-api.ts"], - "@cacheplane/chat": ["../../../../libs/chat/src/public-api.ts"] - } + "strict": false }, - "include": ["src/**/*.ts"] + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": false, + "strictInputAccessModifiers": false, + "strictTemplates": false + }, + "files": [], + "include": [], + "references": [ + { "path": "./tsconfig.app.json" } + ] } diff --git a/cockpit/langgraph/memory/python/langgraph.json b/cockpit/langgraph/memory/python/langgraph.json index d2c3ffb9b..260737556 100644 --- a/cockpit/langgraph/memory/python/langgraph.json +++ b/cockpit/langgraph/memory/python/langgraph.json @@ -2,6 +2,6 @@ "graphs": { "memory": "./src/graph.py:graph" }, - "dependencies": ["./pyproject.toml"], - "env": ".env" + "dependencies": ["."], + "python_version": "3.12" } diff --git a/cockpit/langgraph/persistence/angular/project.json b/cockpit/langgraph/persistence/angular/project.json index 44aa263c8..4687f8634 100644 --- a/cockpit/langgraph/persistence/angular/project.json +++ b/cockpit/langgraph/persistence/angular/project.json @@ -5,48 +5,44 @@ "projectType": "application", "targets": { "build": { - "executor": "@angular-devkit/build-angular:application", - "outputs": ["{options.outputPath}"], + "executor": "@angular/build:application", + "outputs": ["{options.outputPath.base}"], "options": { - "outputPath": "dist/cockpit/langgraph/persistence/angular", - "index": "cockpit/langgraph/persistence/angular/src/index.html", + "outputPath": { + "base": "dist/cockpit/langgraph/persistence/angular", + "browser": "" + }, "browser": "cockpit/langgraph/persistence/angular/src/main.ts", "tsConfig": "cockpit/langgraph/persistence/angular/tsconfig.app.json", "styles": ["cockpit/langgraph/persistence/angular/src/styles.css"] }, "configurations": { "production": { - "outputHashing": "all" + "budgets": [ + { "type": "initial", "maximumWarning": "500kb", "maximumError": "1mb" }, + { "type": "anyComponentStyle", "maximumWarning": "4kb", "maximumError": "8kb" } + ], + "outputHashing": "none" }, "development": { "optimization": false, "extractLicenses": false, - "sourceMap": true, - "fileReplacements": [ - { - "replace": "cockpit/langgraph/persistence/angular/src/environments/environment.ts", - "with": "cockpit/langgraph/persistence/angular/src/environments/environment.development.ts" - } - ] + "sourceMap": true } }, "defaultConfiguration": "production" }, "serve": { - "executor": "@angular-devkit/build-angular:dev-server", - "options": { - "port": 4301, - "proxyConfig": "cockpit/langgraph/persistence/angular/proxy.conf.json" - }, + "continuous": true, + "executor": "@angular/build:dev-server", "configurations": { - "production": { - "buildTarget": "cockpit-langgraph-persistence-angular:build:production" - }, - "development": { - "buildTarget": "cockpit-langgraph-persistence-angular:build:development" - } + "production": { "buildTarget": "cockpit-langgraph-persistence-angular:build:production" }, + "development": { "buildTarget": "cockpit-langgraph-persistence-angular:build:development" } }, - "defaultConfiguration": "development" + "defaultConfiguration": "development", + "options": { + "proxyConfig": "cockpit/langgraph/persistence/angular/proxy.conf.json" + } }, "smoke": { "executor": "nx:run-commands", diff --git a/cockpit/langgraph/persistence/angular/src/app/persistence.component.ts b/cockpit/langgraph/persistence/angular/src/app/persistence.component.ts index 2318a4363..aa7f77f42 100644 --- a/cockpit/langgraph/persistence/angular/src/app/persistence.component.ts +++ b/cockpit/langgraph/persistence/angular/src/app/persistence.component.ts @@ -1,18 +1,88 @@ -// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 import { Component } from '@angular/core'; -import { ChatComponent } from '@cacheplane/chat'; +import { LegacyChatComponent } from '@cacheplane/chat'; import { streamResource } from '@cacheplane/stream-resource'; import { environment } from '../environments/environment'; +/** + * PersistenceComponent demonstrates thread persistence with `streamResource()`. + * + * This example shows how conversations persist across browser refreshes. + * Each thread has a unique ID that can be stored and resumed later. + * Use `stream.switchThread(id)` to load a previous conversation, + * or `stream.switchThread(null)` to start fresh. + * + * Key integration points: + * - `onThreadId` callback captures new thread IDs for storage + * - `stream.switchThread(id)` resumes a previous conversation + * - `stream.messages()` loads the full history when switching threads + */ @Component({ selector: 'app-persistence', standalone: true, - imports: [ChatComponent], - template: ``, + imports: [LegacyChatComponent], + template: ` + + +

Threads

+ @for (id of threadIds; track id) { + + } + +
+
+ `, }) export class PersistenceComponent { + /** + * The streaming resource with thread persistence. + * + * The `onThreadId` callback fires when a new thread is created, + * allowing us to track thread IDs for the sidebar picker. + */ protected readonly stream = streamResource({ apiUrl: environment.langGraphApiUrl, assistantId: environment.streamingAssistantId, + onThreadId: (id: string) => { + this.currentThreadId = id; + if (!this.threadIds.includes(id)) this.threadIds.push(id); + }, }); + + threadIds: string[] = []; + currentThreadId = ''; + + /** + * Submit a message to the current thread. + */ + send(text: string): void { + this.stream.submit({ messages: [{ role: 'human', content: text }] }); + } + + /** + * Switch to an existing thread, loading its full message history. + */ + selectThread(id: string): void { + this.currentThreadId = id; + this.stream.switchThread(id); + } + + /** + * Start a new conversation thread. + */ + newThread(): void { + this.currentThreadId = ''; + this.stream.switchThread(null); + } } diff --git a/cockpit/langgraph/persistence/angular/src/environments/environment.ts b/cockpit/langgraph/persistence/angular/src/environments/environment.ts index 4a31d740f..ddc3351b6 100644 --- a/cockpit/langgraph/persistence/angular/src/environments/environment.ts +++ b/cockpit/langgraph/persistence/angular/src/environments/environment.ts @@ -6,6 +6,6 @@ */ export const environment = { production: true, - langGraphApiUrl: 'http://localhost:4301/api', + langGraphApiUrl: 'https://persistence-b4038c008b5e537787dda6a6774c8f91.us.langgraph.app', streamingAssistantId: 'persistence', }; diff --git a/cockpit/langgraph/persistence/angular/tsconfig.json b/cockpit/langgraph/persistence/angular/tsconfig.json index 7e71d1c7c..3fd970371 100644 --- a/cockpit/langgraph/persistence/angular/tsconfig.json +++ b/cockpit/langgraph/persistence/angular/tsconfig.json @@ -1,12 +1,24 @@ { "extends": "../../../../tsconfig.base.json", "compilerOptions": { + "noPropertyAccessFromIndexSignature": false, + "experimentalDecorators": true, "module": "preserve", + "emitDeclarationOnly": false, + "composite": false, + "lib": ["es2022", "dom"], "skipLibCheck": true, - "paths": { - "@cacheplane/stream-resource": ["../../../../libs/stream-resource/src/public-api.ts"], - "@cacheplane/chat": ["../../../../libs/chat/src/public-api.ts"] - } + "strict": false }, - "include": ["src/**/*.ts"] + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": false, + "strictInputAccessModifiers": false, + "strictTemplates": false + }, + "files": [], + "include": [], + "references": [ + { "path": "./tsconfig.app.json" } + ] } diff --git a/cockpit/langgraph/persistence/python/langgraph.json b/cockpit/langgraph/persistence/python/langgraph.json index 1b16c27f7..8cda741a2 100644 --- a/cockpit/langgraph/persistence/python/langgraph.json +++ b/cockpit/langgraph/persistence/python/langgraph.json @@ -2,6 +2,6 @@ "graphs": { "persistence": "./src/graph.py:graph" }, - "dependencies": ["./pyproject.toml"], - "env": ".env" + "dependencies": ["."], + "python_version": "3.12" } diff --git a/cockpit/langgraph/streaming/angular/project.json b/cockpit/langgraph/streaming/angular/project.json index 9664fc7d7..95d68959f 100644 --- a/cockpit/langgraph/streaming/angular/project.json +++ b/cockpit/langgraph/streaming/angular/project.json @@ -5,48 +5,44 @@ "projectType": "application", "targets": { "build": { - "executor": "@angular-devkit/build-angular:application", - "outputs": ["{options.outputPath}"], + "executor": "@angular/build:application", + "outputs": ["{options.outputPath.base}"], "options": { - "outputPath": "dist/cockpit/langgraph/streaming/angular", - "index": "cockpit/langgraph/streaming/angular/src/index.html", + "outputPath": { + "base": "dist/cockpit/langgraph/streaming/angular", + "browser": "" + }, "browser": "cockpit/langgraph/streaming/angular/src/main.ts", "tsConfig": "cockpit/langgraph/streaming/angular/tsconfig.app.json", "styles": ["cockpit/langgraph/streaming/angular/src/styles.css"] }, "configurations": { "production": { - "outputHashing": "all" + "budgets": [ + { "type": "initial", "maximumWarning": "500kb", "maximumError": "1mb" }, + { "type": "anyComponentStyle", "maximumWarning": "4kb", "maximumError": "8kb" } + ], + "outputHashing": "none" }, "development": { "optimization": false, "extractLicenses": false, - "sourceMap": true, - "fileReplacements": [ - { - "replace": "cockpit/langgraph/streaming/angular/src/environments/environment.ts", - "with": "cockpit/langgraph/streaming/angular/src/environments/environment.development.ts" - } - ] + "sourceMap": true } }, "defaultConfiguration": "production" }, "serve": { - "executor": "@angular-devkit/build-angular:dev-server", - "options": { - "port": 4300, - "proxyConfig": "cockpit/langgraph/streaming/angular/proxy.conf.json" - }, + "continuous": true, + "executor": "@angular/build:dev-server", "configurations": { - "production": { - "buildTarget": "cockpit-langgraph-streaming-angular:build:production" - }, - "development": { - "buildTarget": "cockpit-langgraph-streaming-angular:build:development" - } + "production": { "buildTarget": "cockpit-langgraph-streaming-angular:build:production" }, + "development": { "buildTarget": "cockpit-langgraph-streaming-angular:build:development" } }, - "defaultConfiguration": "development" + "defaultConfiguration": "development", + "options": { + "proxyConfig": "cockpit/langgraph/streaming/angular/proxy.conf.json" + } }, "smoke": { "executor": "nx:run-commands", diff --git a/cockpit/langgraph/streaming/angular/src/app/streaming.component.ts b/cockpit/langgraph/streaming/angular/src/app/streaming.component.ts index 4de25f813..2d5318344 100644 --- a/cockpit/langgraph/streaming/angular/src/app/streaming.component.ts +++ b/cockpit/langgraph/streaming/angular/src/app/streaming.component.ts @@ -1,25 +1,47 @@ -// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 import { Component } from '@angular/core'; -import { ChatComponent } from '@cacheplane/chat'; +import { LegacyChatComponent } from '@cacheplane/chat'; import { streamResource } from '@cacheplane/stream-resource'; import { environment } from '../environments/environment'; /** - * Streaming demo — simplest possible @cacheplane/chat integration. + * StreamingComponent demonstrates real-time LLM streaming with `streamResource()`. * - * Creates a streamResource ref and passes it to the prebuilt - * composition. The composition handles message rendering, input, typing - * indicator, and error display internally. + * Uses the shared `@cacheplane/chat` component for the chat UI. + * This is the simplest example — just streaming messages with no + * additional capability features (no threads, interrupts, etc.). + * + * Key integration points: + * - `streamResource()` creates a Signal-based streaming ref + * - `stream.messages()` provides reactive access to the conversation + * - `stream.submit()` fires a message to the LangGraph backend + * - `stream.isLoading()` tracks whether a response is in progress */ @Component({ selector: 'app-streaming', standalone: true, - imports: [ChatComponent], - template: ``, + imports: [LegacyChatComponent], + template: ` + + `, }) export class StreamingComponent { + /** + * The streaming resource ref — connects to the LangGraph Cloud backend. + */ protected readonly stream = streamResource({ apiUrl: environment.langGraphApiUrl, assistantId: environment.streamingAssistantId, }); + + /** + * Submits the user's message to the LangGraph streaming endpoint. + */ + send(text: string): void { + this.stream.submit({ messages: [{ role: 'human', content: text }] }); + } } diff --git a/cockpit/langgraph/streaming/angular/src/environments/environment.ts b/cockpit/langgraph/streaming/angular/src/environments/environment.ts index ee551d85e..f2d9ce265 100644 --- a/cockpit/langgraph/streaming/angular/src/environments/environment.ts +++ b/cockpit/langgraph/streaming/angular/src/environments/environment.ts @@ -6,6 +6,6 @@ */ export const environment = { production: true, - langGraphApiUrl: 'http://localhost:4300/api', + langGraphApiUrl: 'https://streaming-b01895ee8c8d5211967fba7a64c55db8.us.langgraph.app', streamingAssistantId: 'streaming', }; diff --git a/cockpit/langgraph/streaming/angular/tsconfig.json b/cockpit/langgraph/streaming/angular/tsconfig.json index 7e71d1c7c..3fd970371 100644 --- a/cockpit/langgraph/streaming/angular/tsconfig.json +++ b/cockpit/langgraph/streaming/angular/tsconfig.json @@ -1,12 +1,24 @@ { "extends": "../../../../tsconfig.base.json", "compilerOptions": { + "noPropertyAccessFromIndexSignature": false, + "experimentalDecorators": true, "module": "preserve", + "emitDeclarationOnly": false, + "composite": false, + "lib": ["es2022", "dom"], "skipLibCheck": true, - "paths": { - "@cacheplane/stream-resource": ["../../../../libs/stream-resource/src/public-api.ts"], - "@cacheplane/chat": ["../../../../libs/chat/src/public-api.ts"] - } + "strict": false }, - "include": ["src/**/*.ts"] + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": false, + "strictInputAccessModifiers": false, + "strictTemplates": false + }, + "files": [], + "include": [], + "references": [ + { "path": "./tsconfig.app.json" } + ] } diff --git a/cockpit/langgraph/streaming/python/langgraph.json b/cockpit/langgraph/streaming/python/langgraph.json index 32c580a2e..37582903e 100644 --- a/cockpit/langgraph/streaming/python/langgraph.json +++ b/cockpit/langgraph/streaming/python/langgraph.json @@ -2,6 +2,6 @@ "graphs": { "streaming": "./src/graph.py:graph" }, - "dependencies": ["./pyproject.toml"], - "env": ".env" + "dependencies": ["."], + "python_version": "3.12" } diff --git a/cockpit/langgraph/subgraphs/angular/project.json b/cockpit/langgraph/subgraphs/angular/project.json index 27870cd91..22cbde729 100644 --- a/cockpit/langgraph/subgraphs/angular/project.json +++ b/cockpit/langgraph/subgraphs/angular/project.json @@ -5,48 +5,44 @@ "projectType": "application", "targets": { "build": { - "executor": "@angular-devkit/build-angular:application", - "outputs": ["{options.outputPath}"], + "executor": "@angular/build:application", + "outputs": ["{options.outputPath.base}"], "options": { - "outputPath": "dist/cockpit/langgraph/subgraphs/angular", - "index": "cockpit/langgraph/subgraphs/angular/src/index.html", + "outputPath": { + "base": "dist/cockpit/langgraph/subgraphs/angular", + "browser": "" + }, "browser": "cockpit/langgraph/subgraphs/angular/src/main.ts", "tsConfig": "cockpit/langgraph/subgraphs/angular/tsconfig.app.json", "styles": ["cockpit/langgraph/subgraphs/angular/src/styles.css"] }, "configurations": { "production": { - "outputHashing": "all" + "budgets": [ + { "type": "initial", "maximumWarning": "500kb", "maximumError": "1mb" }, + { "type": "anyComponentStyle", "maximumWarning": "4kb", "maximumError": "8kb" } + ], + "outputHashing": "none" }, "development": { "optimization": false, "extractLicenses": false, - "sourceMap": true, - "fileReplacements": [ - { - "replace": "cockpit/langgraph/subgraphs/angular/src/environments/environment.ts", - "with": "cockpit/langgraph/subgraphs/angular/src/environments/environment.development.ts" - } - ] + "sourceMap": true } }, "defaultConfiguration": "production" }, "serve": { - "executor": "@angular-devkit/build-angular:dev-server", - "options": { - "port": 4305, - "proxyConfig": "cockpit/langgraph/subgraphs/angular/proxy.conf.json" - }, + "continuous": true, + "executor": "@angular/build:dev-server", "configurations": { - "production": { - "buildTarget": "cockpit-langgraph-subgraphs-angular:build:production" - }, - "development": { - "buildTarget": "cockpit-langgraph-subgraphs-angular:build:development" - } + "production": { "buildTarget": "cockpit-langgraph-subgraphs-angular:build:production" }, + "development": { "buildTarget": "cockpit-langgraph-subgraphs-angular:build:development" } }, - "defaultConfiguration": "development" + "defaultConfiguration": "development", + "options": { + "proxyConfig": "cockpit/langgraph/subgraphs/angular/proxy.conf.json" + } }, "smoke": { "executor": "nx:run-commands", diff --git a/cockpit/langgraph/subgraphs/angular/src/app/subgraphs.component.ts b/cockpit/langgraph/subgraphs/angular/src/app/subgraphs.component.ts index 42be99684..6bf543ed7 100644 --- a/cockpit/langgraph/subgraphs/angular/src/app/subgraphs.component.ts +++ b/cockpit/langgraph/subgraphs/angular/src/app/subgraphs.component.ts @@ -1,18 +1,66 @@ -// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 -import { Component } from '@angular/core'; -import { ChatComponent } from '@cacheplane/chat'; +import { Component, computed } from '@angular/core'; +import { LegacyChatComponent } from '@cacheplane/chat'; import { streamResource } from '@cacheplane/stream-resource'; import { environment } from '../environments/environment'; +/** + * SubgraphsComponent demonstrates nested agent delegation with `streamResource()`. + * + * This example shows how a parent orchestrator delegates tasks to child subgraphs. + * The sidebar tracks active subagents in real time using `stream.subagents()`, + * a Map of running child graph executions and their current status. + * + * Key integration points: + * - `stream.subagents()` returns a Map of active subagents + * - Each entry has a unique run ID (key) and a `status()` signal ('running' | 'done' | 'error') + * - `subagentEntries` is a `computed()` signal derived from the map for iteration in the template + */ @Component({ selector: 'app-subgraphs', standalone: true, - imports: [ChatComponent], - template: ``, + imports: [LegacyChatComponent], + template: ` + + +

Subagents

+ @for (entry of subagentEntries(); track entry[0]) { +
+ {{ entry[0].substring(0, 8) }}: {{ entry[1].status() }} +
+ } + @empty { +

No active subagents

+ } +
+
+ `, }) export class SubgraphsComponent { + /** + * The streaming resource that tracks subgraph (child agent) activity. + * + * `stream.subagents()` is a Signal> that updates + * as the parent orchestrator dispatches work to child subgraphs. + */ protected readonly stream = streamResource({ apiUrl: environment.langGraphApiUrl, assistantId: environment.streamingAssistantId, }); + + /** + * Derived signal: converts the subagents Map to an array of entries for template iteration. + * Using `computed()` ensures the template re-renders whenever the Map changes. + */ + subagentEntries = computed(() => Array.from(this.stream.subagents().entries())); + + /** + * Submit a message to the orchestrator graph. + */ + send(text: string): void { + this.stream.submit({ messages: [{ role: 'human', content: text }] }); + } } diff --git a/cockpit/langgraph/subgraphs/angular/src/environments/environment.ts b/cockpit/langgraph/subgraphs/angular/src/environments/environment.ts index 334fd1219..684715bd8 100644 --- a/cockpit/langgraph/subgraphs/angular/src/environments/environment.ts +++ b/cockpit/langgraph/subgraphs/angular/src/environments/environment.ts @@ -6,6 +6,6 @@ */ export const environment = { production: true, - langGraphApiUrl: 'http://localhost:4305/api', + langGraphApiUrl: 'https://subgraphs-c923bcb068c458b09d789f147875f426.us.langgraph.app', streamingAssistantId: 'subgraphs', }; diff --git a/cockpit/langgraph/subgraphs/angular/tsconfig.json b/cockpit/langgraph/subgraphs/angular/tsconfig.json index 7e71d1c7c..3fd970371 100644 --- a/cockpit/langgraph/subgraphs/angular/tsconfig.json +++ b/cockpit/langgraph/subgraphs/angular/tsconfig.json @@ -1,12 +1,24 @@ { "extends": "../../../../tsconfig.base.json", "compilerOptions": { + "noPropertyAccessFromIndexSignature": false, + "experimentalDecorators": true, "module": "preserve", + "emitDeclarationOnly": false, + "composite": false, + "lib": ["es2022", "dom"], "skipLibCheck": true, - "paths": { - "@cacheplane/stream-resource": ["../../../../libs/stream-resource/src/public-api.ts"], - "@cacheplane/chat": ["../../../../libs/chat/src/public-api.ts"] - } + "strict": false }, - "include": ["src/**/*.ts"] + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": false, + "strictInputAccessModifiers": false, + "strictTemplates": false + }, + "files": [], + "include": [], + "references": [ + { "path": "./tsconfig.app.json" } + ] } diff --git a/cockpit/langgraph/subgraphs/python/langgraph.json b/cockpit/langgraph/subgraphs/python/langgraph.json index ae73faba5..40d11fe9f 100644 --- a/cockpit/langgraph/subgraphs/python/langgraph.json +++ b/cockpit/langgraph/subgraphs/python/langgraph.json @@ -2,6 +2,6 @@ "graphs": { "subgraphs": "./src/graph.py:graph" }, - "dependencies": ["./pyproject.toml"], - "env": ".env" + "dependencies": ["."], + "python_version": "3.12" } diff --git a/cockpit/langgraph/time-travel/angular/project.json b/cockpit/langgraph/time-travel/angular/project.json index a01fd0ed0..210cfe615 100644 --- a/cockpit/langgraph/time-travel/angular/project.json +++ b/cockpit/langgraph/time-travel/angular/project.json @@ -5,48 +5,44 @@ "projectType": "application", "targets": { "build": { - "executor": "@angular-devkit/build-angular:application", - "outputs": ["{options.outputPath}"], + "executor": "@angular/build:application", + "outputs": ["{options.outputPath.base}"], "options": { - "outputPath": "dist/cockpit/langgraph/time-travel/angular", - "index": "cockpit/langgraph/time-travel/angular/src/index.html", + "outputPath": { + "base": "dist/cockpit/langgraph/time-travel/angular", + "browser": "" + }, "browser": "cockpit/langgraph/time-travel/angular/src/main.ts", "tsConfig": "cockpit/langgraph/time-travel/angular/tsconfig.app.json", "styles": ["cockpit/langgraph/time-travel/angular/src/styles.css"] }, "configurations": { "production": { - "outputHashing": "all" + "budgets": [ + { "type": "initial", "maximumWarning": "500kb", "maximumError": "1mb" }, + { "type": "anyComponentStyle", "maximumWarning": "4kb", "maximumError": "8kb" } + ], + "outputHashing": "none" }, "development": { "optimization": false, "extractLicenses": false, - "sourceMap": true, - "fileReplacements": [ - { - "replace": "cockpit/langgraph/time-travel/angular/src/environments/environment.ts", - "with": "cockpit/langgraph/time-travel/angular/src/environments/environment.development.ts" - } - ] + "sourceMap": true } }, "defaultConfiguration": "production" }, "serve": { - "executor": "@angular-devkit/build-angular:dev-server", - "options": { - "port": 4304, - "proxyConfig": "cockpit/langgraph/time-travel/angular/proxy.conf.json" - }, + "continuous": true, + "executor": "@angular/build:dev-server", "configurations": { - "production": { - "buildTarget": "cockpit-langgraph-time-travel-angular:build:production" - }, - "development": { - "buildTarget": "cockpit-langgraph-time-travel-angular:build:development" - } + "production": { "buildTarget": "cockpit-langgraph-time-travel-angular:build:production" }, + "development": { "buildTarget": "cockpit-langgraph-time-travel-angular:build:development" } }, - "defaultConfiguration": "development" + "defaultConfiguration": "development", + "options": { + "proxyConfig": "cockpit/langgraph/time-travel/angular/proxy.conf.json" + } }, "smoke": { "executor": "nx:run-commands", diff --git a/cockpit/langgraph/time-travel/angular/src/app/time-travel.component.ts b/cockpit/langgraph/time-travel/angular/src/app/time-travel.component.ts index 57a2d1dd1..17759117d 100644 --- a/cockpit/langgraph/time-travel/angular/src/app/time-travel.component.ts +++ b/cockpit/langgraph/time-travel/angular/src/app/time-travel.component.ts @@ -1,18 +1,84 @@ -// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 import { Component } from '@angular/core'; -import { ChatComponent } from '@cacheplane/chat'; +import { LegacyChatComponent } from '@cacheplane/chat'; import { streamResource } from '@cacheplane/stream-resource'; import { environment } from '../environments/environment'; +/** + * TimeTravelComponent demonstrates replaying and branching conversation history. + * + * Key integration points: + * - `stream.history()` — array of ThreadState snapshots + * - `stream.branch()` — current branch identifier + * - `stream.setBranch(id)` — switch to a different checkpoint + */ @Component({ selector: 'app-time-travel', standalone: true, - imports: [ChatComponent], - template: ``, + imports: [LegacyChatComponent], + template: ` + + +

History

+ @for (state of stream.history(); track $index) { + + } + @if (stream.history().length === 0) { +

No history yet. Send a message to begin.

+ } +
+
+ `, }) export class TimeTravelComponent { + /** + * The streaming resource with checkpointing enabled. + * + * `stream.history()` provides an array of ThreadState snapshots for + * the current thread. `stream.branch()` tracks the active checkpoint. + * Call `stream.setBranch(checkpointId)` to replay from a past state. + */ protected readonly stream = streamResource({ apiUrl: environment.langGraphApiUrl, assistantId: environment.streamingAssistantId, }); + + /** + * Submit a message to the current thread. + */ + send(text: string): void { + this.stream.submit({ messages: [{ role: 'human', content: text }] }); + } + + /** + * Branch the conversation from the selected checkpoint. + * After calling setBranch, the next submit will fork from that point. + */ + selectCheckpoint(state: { checkpoint_id?: string }): void { + if (state.checkpoint_id) { + this.stream.setBranch(state.checkpoint_id); + } + } + + /** + * Format a checkpoint for display in the sidebar. + */ + formatCheckpoint(state: { checkpoint_id?: string; created_at?: string }): string { + const id = state.checkpoint_id ?? 'unknown'; + const short = id.substring(0, 8); + if (state.created_at) { + const ts = new Date(state.created_at).toLocaleTimeString(); + return `${short}... @ ${ts}`; + } + return `${short}...`; + } } diff --git a/cockpit/langgraph/time-travel/angular/src/environments/environment.ts b/cockpit/langgraph/time-travel/angular/src/environments/environment.ts index 99b70f007..402a818f9 100644 --- a/cockpit/langgraph/time-travel/angular/src/environments/environment.ts +++ b/cockpit/langgraph/time-travel/angular/src/environments/environment.ts @@ -6,6 +6,6 @@ */ export const environment = { production: true, - langGraphApiUrl: 'http://localhost:4306/api', + langGraphApiUrl: 'https://time-travel-f206148d75f45e75bf30002e68e1b14d.us.langgraph.app', streamingAssistantId: 'time-travel', }; diff --git a/cockpit/langgraph/time-travel/angular/tsconfig.json b/cockpit/langgraph/time-travel/angular/tsconfig.json index 7e71d1c7c..3fd970371 100644 --- a/cockpit/langgraph/time-travel/angular/tsconfig.json +++ b/cockpit/langgraph/time-travel/angular/tsconfig.json @@ -1,12 +1,24 @@ { "extends": "../../../../tsconfig.base.json", "compilerOptions": { + "noPropertyAccessFromIndexSignature": false, + "experimentalDecorators": true, "module": "preserve", + "emitDeclarationOnly": false, + "composite": false, + "lib": ["es2022", "dom"], "skipLibCheck": true, - "paths": { - "@cacheplane/stream-resource": ["../../../../libs/stream-resource/src/public-api.ts"], - "@cacheplane/chat": ["../../../../libs/chat/src/public-api.ts"] - } + "strict": false }, - "include": ["src/**/*.ts"] + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": false, + "strictInputAccessModifiers": false, + "strictTemplates": false + }, + "files": [], + "include": [], + "references": [ + { "path": "./tsconfig.app.json" } + ] } diff --git a/cockpit/langgraph/time-travel/python/langgraph.json b/cockpit/langgraph/time-travel/python/langgraph.json index 5693c91fc..2616ce114 100644 --- a/cockpit/langgraph/time-travel/python/langgraph.json +++ b/cockpit/langgraph/time-travel/python/langgraph.json @@ -2,6 +2,6 @@ "graphs": { "time-travel": "./src/graph.py:graph" }, - "dependencies": ["./pyproject.toml"], - "env": ".env" + "dependencies": ["."], + "python_version": "3.12" } diff --git a/deployment-urls.json b/deployment-urls.json new file mode 100644 index 000000000..a6888ed74 --- /dev/null +++ b/deployment-urls.json @@ -0,0 +1,16 @@ +{ + "streaming": "https://streaming-b01895ee8c8d5211967fba7a64c55db8.us.langgraph.app", + "persistence": "https://persistence-b4038c008b5e537787dda6a6774c8f91.us.langgraph.app", + "interrupts": "https://interrupts-8e1524d6d8fb558381eed4618129bc50.us.langgraph.app", + "memory": "https://memory-1b3234dbe2e55ba59010b3469be45a0a.us.langgraph.app", + "durable-execution": "https://durable-execution-123221d8b543545399d252dc6bd7de1b.us.langgraph.app", + "subgraphs": "https://subgraphs-c923bcb068c458b09d789f147875f426.us.langgraph.app", + "time-travel": "https://time-travel-f206148d75f45e75bf30002e68e1b14d.us.langgraph.app", + "deployment-runtime": "https://deployment-runtime-ce6aad33cc10505faca2b6137e76ba35.us.langgraph.app", + "planning": "https://planning-7ca04c65ce7650048ec0d16fb96a7638.us.langgraph.app", + "filesystem": "https://filesystem-2330285f57625bff8654bc026f70a6ae.us.langgraph.app", + "subagents": "https://da-subagents-31e4639441165df7848aaad426e61728.us.langgraph.app", + "da-memory": "https://da-memory-15f767adfa6f5cd48bd45a0fa4db29b5.us.langgraph.app", + "skills": "https://skills-802ff50f64325f1ea973cff1c97a49f9.us.langgraph.app", + "sandboxes": "https://sandboxes-8c70b6ac20265827aa92397299fcb9f7.us.langgraph.app" +} diff --git a/docs/superpowers/plans/2026-04-05-production-deployment.md b/docs/superpowers/plans/2026-04-05-production-deployment.md new file mode 100644 index 000000000..68e7a625e --- /dev/null +++ b/docs/superpowers/plans/2026-04-05-production-deployment.md @@ -0,0 +1,849 @@ +# Production Deployment 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:** Deploy all 14 capability backends to LangGraph Cloud, host all 14 Angular example apps on Vercel, wire the cockpit to production URLs, and verify end-to-end with smoke tests. + +**Architecture:** Three-phase deployment: (1) deploy Python backends to LangGraph Cloud via existing `deploy-langgraph.yml`, capture URLs into a registry file; (2) update Angular production environments with LangGraph Cloud URLs, build and deploy as static sites to a new Vercel project at `examples.stream-resource.dev`; (3) set `NEXT_PUBLIC_COCKPIT_RUNTIME_BASE_URL` in cockpit Vercel env, add a production smoke test to CI that verifies the full stack. + +**Tech Stack:** LangGraph Cloud (LangSmith), Vercel, Angular static builds, Playwright, GitHub Actions + +--- + +## File Map + +| Action | File | Responsibility | +|--------|------|----------------| +| Modify | `cockpit/langgraph/*/angular/project.json` (8) | Switch to @angular/build:application | +| Modify | `cockpit/deep-agents/*/angular/project.json` (6) | Switch to @angular/build:application | +| Create | `deployment-urls.json` | Registry of LangGraph Cloud URLs per capability | +| Create | `scripts/verify-langgraph-deployments.ts` | Health check + smoke test all 14 backends | +| Modify | `cockpit/langgraph/streaming/angular/src/environments/environment.ts` | Production LangGraph URL | +| Modify | `cockpit/langgraph/persistence/angular/src/environments/environment.ts` | Production LangGraph URL | +| Modify | `cockpit/langgraph/interrupts/angular/src/environments/environment.ts` | Production LangGraph URL | +| Modify | `cockpit/langgraph/memory/angular/src/environments/environment.ts` | Production LangGraph URL | +| Modify | `cockpit/langgraph/durable-execution/angular/src/environments/environment.ts` | Production LangGraph URL | +| Modify | `cockpit/langgraph/subgraphs/angular/src/environments/environment.ts` | Production LangGraph URL | +| Modify | `cockpit/langgraph/time-travel/angular/src/environments/environment.ts` | Production LangGraph URL | +| Modify | `cockpit/langgraph/deployment-runtime/angular/src/environments/environment.ts` | Production LangGraph URL | +| Modify | `cockpit/deep-agents/planning/angular/src/environments/environment.ts` | Production LangGraph URL | +| Modify | `cockpit/deep-agents/filesystem/angular/src/environments/environment.ts` | Production LangGraph URL | +| Modify | `cockpit/deep-agents/subagents/angular/src/environments/environment.ts` | Production LangGraph URL | +| Modify | `cockpit/deep-agents/memory/angular/src/environments/environment.ts` | Production LangGraph URL | +| Modify | `cockpit/deep-agents/skills/angular/src/environments/environment.ts` | Production LangGraph URL | +| Modify | `cockpit/deep-agents/sandboxes/angular/src/environments/environment.ts` | Production LangGraph URL | +| Create | `scripts/assemble-examples.ts` | Build + assemble all Angular apps into deploy directory | +| Create | `vercel.examples.json` | Vercel config for static examples site | +| Modify | `.github/workflows/ci.yml` | Add examples deploy job + production smoke job | +| Create | `apps/cockpit/e2e/production-smoke.spec.ts` | Playwright tests for production verification | + +--- + +### Task 0: Switch Angular apps to @angular/build:application + +The 14 Angular example apps currently use `@nx/js:tsc` as their build executor, which produces a TypeScript library output. For production deployment, we need `@angular/build:application` — the modern Angular esbuild builder that produces optimized static bundles. + +**Files:** +- Modify: `cockpit/langgraph/streaming/angular/project.json` (and all 13 others) + +- [ ] **Step 1: Update all 14 project.json files** + +Replace the `build` target in each Angular app's `project.json` and add a `serve` target. The pattern (using streaming as example — adjust the paths for each capability): + +```json +{ + "name": "cockpit-langgraph-streaming-angular", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "cockpit/langgraph/streaming/angular/src", + "projectType": "application", + "targets": { + "build": { + "executor": "@angular/build:application", + "outputs": ["{options.outputPath.base}"], + "options": { + "outputPath": { + "base": "dist/cockpit/langgraph/streaming/angular", + "browser": "" + }, + "browser": "cockpit/langgraph/streaming/angular/src/main.ts", + "tsConfig": "cockpit/langgraph/streaming/angular/tsconfig.app.json", + "styles": ["cockpit/langgraph/streaming/angular/src/styles.css"] + }, + "configurations": { + "production": { + "budgets": [ + { "type": "initial", "maximumWarning": "500kb", "maximumError": "1mb" }, + { "type": "anyComponentStyle", "maximumWarning": "4kb", "maximumError": "8kb" } + ], + "outputHashing": "none" + }, + "development": { + "optimization": false, + "extractLicenses": false, + "sourceMap": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "continuous": true, + "executor": "@angular/build:dev-server", + "configurations": { + "production": { "buildTarget": "cockpit-langgraph-streaming-angular:build:production" }, + "development": { "buildTarget": "cockpit-langgraph-streaming-angular:build:development" } + }, + "defaultConfiguration": "development", + "options": { + "proxyConfig": "cockpit/langgraph/streaming/angular/proxy.conf.json" + } + }, + "smoke": { + "executor": "nx:run-commands", + "options": { + "cwd": "cockpit/langgraph/streaming/angular", + "command": "npx tsx -e \"import { langgraphStreamingAngularModule } from './src/index.ts'; const module = langgraphStreamingAngularModule; if (module.id !== 'langgraph-streaming-angular' || module.title !== 'LangGraph Streaming (Angular)') { throw new Error('Unexpected module shape for ' + module.id); }\"" + } + } + } +} +``` + +Key changes from the current config: +- `projectType` → `"application"` (was `"library"`) +- `build.executor` → `"@angular/build:application"` (was `"@nx/js:tsc"`) +- `outputPath` → structured `{ base, browser }` for static output +- `browser` entry point → `src/main.ts` +- `styles` array → references the app's `styles.css` +- Added `configurations` for production (budgets, hashing) and development (source maps) +- Added explicit `serve` target with `@angular/build:dev-server` and `proxyConfig` +- Preserved existing `smoke` target + +Repeat for all 14 apps, adjusting: +- `name` — e.g., `cockpit-langgraph-persistence-angular` +- All paths — e.g., `cockpit/langgraph/persistence/angular/...` +- `buildTarget` in serve — e.g., `cockpit-langgraph-persistence-angular:build:development` +- `smoke` command module name and expected values + +- [ ] **Step 2: Build one app to verify** + +```bash +npx nx build cockpit-langgraph-streaming-angular --skip-nx-cache +``` + +Expected: build succeeds, output in `dist/cockpit/langgraph/streaming/angular/` with `index.html`, `main.js`, `styles.css`. + +- [ ] **Step 3: Build all 14 apps** + +```bash +npx nx run-many -t build --projects='cockpit-*-angular' --skip-nx-cache +``` + +Expected: all 14 build successfully. + +- [ ] **Step 4: Verify serve still works** + +```bash +npx nx serve cockpit-langgraph-streaming-angular --port 4300 & +sleep 15 +curl -s -o /dev/null -w "%{http_code}" http://localhost:4300/ +``` + +Expected: HTTP 200. Kill the serve process after. + +- [ ] **Step 5: Commit** + +```bash +git add cockpit/langgraph/*/angular/project.json cockpit/deep-agents/*/angular/project.json +git commit -m "refactor(cockpit): switch Angular apps to @angular/build:application for production builds" +``` + +--- + +### Task 1: Trigger LangGraph Cloud deployments and create deployment URL registry + +This task is partially manual — you trigger the GitHub Actions workflow, then capture the resulting URLs. + +**Files:** +- Create: `deployment-urls.json` + +- [ ] **Step 1: Trigger the LangGraph deploy workflow** + +Go to GitHub Actions → "Deploy LangGraph" → "Run workflow" on the `main` branch with no capability filter. This deploys all 14 backends. + +Alternatively, via CLI: + +```bash +gh workflow run deploy-langgraph.yml --ref main +``` + +Wait for all 14 matrix jobs to complete (check status with `gh run list --workflow deploy-langgraph.yml --limit 1`). + +- [ ] **Step 2: Capture deployment URLs from LangSmith** + +After deployment, retrieve the deployment URL for each backend from the LangSmith UI (Deployments page) or via the LangGraph CLI: + +```bash +pip install langgraph-cli +# For each capability, check its deployment URL: +langgraph deployments list +``` + +The URL format is typically `https://-.default.us.langgraph.app` or similar. + +- [ ] **Step 3: Create deployment-urls.json** + +Create `deployment-urls.json` at the workspace root with the actual URLs from Step 2: + +```json +{ + "streaming": "https://REPLACE_WITH_ACTUAL_URL", + "persistence": "https://REPLACE_WITH_ACTUAL_URL", + "interrupts": "https://REPLACE_WITH_ACTUAL_URL", + "memory": "https://REPLACE_WITH_ACTUAL_URL", + "durable-execution": "https://REPLACE_WITH_ACTUAL_URL", + "subgraphs": "https://REPLACE_WITH_ACTUAL_URL", + "time-travel": "https://REPLACE_WITH_ACTUAL_URL", + "deployment-runtime": "https://REPLACE_WITH_ACTUAL_URL", + "planning": "https://REPLACE_WITH_ACTUAL_URL", + "filesystem": "https://REPLACE_WITH_ACTUAL_URL", + "subagents": "https://REPLACE_WITH_ACTUAL_URL", + "da-memory": "https://REPLACE_WITH_ACTUAL_URL", + "skills": "https://REPLACE_WITH_ACTUAL_URL", + "sandboxes": "https://REPLACE_WITH_ACTUAL_URL" +} +``` + +The keys match the `assistantId` values from each Angular app's `environment.ts`. Replace each `REPLACE_WITH_ACTUAL_URL` with the real URL from LangSmith. + +- [ ] **Step 4: Commit** + +```bash +git add deployment-urls.json +git commit -m "chore: add LangGraph Cloud deployment URL registry" +``` + +--- + +### Task 2: Create backend verification script + +**Files:** +- Create: `scripts/verify-langgraph-deployments.ts` + +- [ ] **Step 1: Create the verification script** + +```typescript +#!/usr/bin/env npx tsx +/** + * Verify all LangGraph Cloud deployments are healthy and can process messages. + * + * Usage: + * npx tsx scripts/verify-langgraph-deployments.ts + * npx tsx scripts/verify-langgraph-deployments.ts --capability streaming + * npx tsx scripts/verify-langgraph-deployments.ts --smoke # includes send/receive test + */ +import { readFileSync } from 'fs'; +import { resolve } from 'path'; + +const urls: Record = JSON.parse( + readFileSync(resolve(__dirname, '../deployment-urls.json'), 'utf-8'), +); + +const capability = process.argv.find((a) => a === '--capability') + ? process.argv[process.argv.indexOf('--capability') + 1] + : null; + +const smoke = process.argv.includes('--smoke'); + +const entries = capability + ? [[capability, urls[capability]] as const] + : Object.entries(urls); + +let passed = 0; +let failed = 0; + +for (const [name, url] of entries) { + if (!url) { + console.error(`❌ ${name}: no URL in deployment-urls.json`); + failed++; + continue; + } + + // Health check + try { + const res = await fetch(`${url}/ok`, { signal: AbortSignal.timeout(10000) }); + const data = await res.json(); + if (!data.ok) throw new Error(`/ok returned ${JSON.stringify(data)}`); + console.log(`✅ ${name}: healthy (${url})`); + } catch (err) { + console.error(`❌ ${name}: health check failed — ${(err as Error).message}`); + failed++; + continue; + } + + // Smoke test: send a message and verify AI response + if (smoke) { + try { + // Create a thread + const threadRes = await fetch(`${url}/threads`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ metadata: {} }), + signal: AbortSignal.timeout(10000), + }); + const thread = await threadRes.json(); + const threadId = thread.thread_id; + + // Send a message + const runRes = await fetch(`${url}/threads/${threadId}/runs/stream`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + assistant_id: name, + input: { messages: [{ role: 'human', content: 'hello' }] }, + stream_mode: ['values'], + }), + signal: AbortSignal.timeout(30000), + }); + + const text = await runRes.text(); + if (!text.includes('"type":"ai"')) { + throw new Error('No AI response in stream'); + } + console.log(`✅ ${name}: smoke test passed`); + } catch (err) { + console.error(`❌ ${name}: smoke test failed — ${(err as Error).message}`); + failed++; + continue; + } + } + + passed++; +} + +console.log(`\n${passed} passed, ${failed} failed out of ${entries.length}`); +if (failed > 0) process.exit(1); +``` + +- [ ] **Step 2: Run the health check** + +```bash +npx tsx scripts/verify-langgraph-deployments.ts +``` + +Expected: all 14 backends report healthy. If any fail, check the URL in `deployment-urls.json` and the LangSmith deployment status. + +- [ ] **Step 3: Run the smoke test** + +```bash +npx tsx scripts/verify-langgraph-deployments.ts --smoke +``` + +Expected: all 14 backends accept a message and return an AI response. If `OPENAI_API_KEY` is not configured in the LangGraph Cloud environment, this will fail — configure it via LangSmith UI first. + +- [ ] **Step 4: Commit** + +```bash +git add scripts/verify-langgraph-deployments.ts +git commit -m "feat: add LangGraph deployment verification script" +``` + +--- + +### Task 3: Update Angular production environment files + +**Files:** +- Modify: all 14 `cockpit/*/angular/src/environments/environment.ts` files + +- [ ] **Step 1: Create a script to update all environment files from deployment-urls.json** + +This is most reliable as a one-time script. Create `scripts/update-angular-environments.ts`: + +```typescript +#!/usr/bin/env npx tsx +/** + * Update all Angular production environment.ts files with LangGraph Cloud URLs + * from deployment-urls.json. + */ +import { readFileSync, writeFileSync } from 'fs'; +import { resolve } from 'path'; + +const root = resolve(__dirname, '..'); +const urls: Record = JSON.parse( + readFileSync(resolve(root, 'deployment-urls.json'), 'utf-8'), +); + +const capabilities = [ + { dir: 'cockpit/langgraph/streaming/angular', assistantId: 'streaming', field: 'streamingAssistantId' }, + { dir: 'cockpit/langgraph/persistence/angular', assistantId: 'persistence', field: 'streamingAssistantId' }, + { dir: 'cockpit/langgraph/interrupts/angular', assistantId: 'interrupts', field: 'streamingAssistantId' }, + { dir: 'cockpit/langgraph/memory/angular', assistantId: 'memory', field: 'streamingAssistantId' }, + { dir: 'cockpit/langgraph/durable-execution/angular', assistantId: 'durable-execution', field: 'streamingAssistantId' }, + { dir: 'cockpit/langgraph/subgraphs/angular', assistantId: 'subgraphs', field: 'streamingAssistantId' }, + { dir: 'cockpit/langgraph/time-travel/angular', assistantId: 'time-travel', field: 'streamingAssistantId' }, + { dir: 'cockpit/langgraph/deployment-runtime/angular', assistantId: 'deployment-runtime', field: 'deploymentRuntimeAssistantId' }, + { dir: 'cockpit/deep-agents/planning/angular', assistantId: 'planning', field: 'streamingAssistantId' }, + { dir: 'cockpit/deep-agents/filesystem/angular', assistantId: 'filesystem', field: 'streamingAssistantId' }, + { dir: 'cockpit/deep-agents/subagents/angular', assistantId: 'subagents', field: 'streamingAssistantId' }, + { dir: 'cockpit/deep-agents/memory/angular', assistantId: 'da-memory', field: 'streamingAssistantId' }, + { dir: 'cockpit/deep-agents/skills/angular', assistantId: 'skills', field: 'streamingAssistantId' }, + { dir: 'cockpit/deep-agents/sandboxes/angular', assistantId: 'sandboxes', field: 'streamingAssistantId' }, +]; + +for (const cap of capabilities) { + const url = urls[cap.assistantId]; + if (!url) { + console.error(`⚠️ No URL for ${cap.assistantId} — skipping`); + continue; + } + + const envPath = resolve(root, cap.dir, 'src/environments/environment.ts'); + + const content = cap.field === 'deploymentRuntimeAssistantId' + ? `/** + * Production environment configuration. + * + * Points to the LangGraph Cloud deployment managed by LangSmith. + * The assistantId must match the graph name in langgraph.json. + */ +export const environment = { + production: true, + langGraphApiUrl: '${url}', + ${cap.field}: '${cap.assistantId}', +}; +` + : `/** + * Production environment configuration. + * + * Points to the LangGraph Cloud deployment managed by LangSmith. + * The assistantId must match the graph name in langgraph.json. + */ +export const environment = { + production: true, + langGraphApiUrl: '${url}', + ${cap.field}: '${cap.assistantId}', +}; +`; + + writeFileSync(envPath, content); + console.log(`✅ ${cap.assistantId}: ${envPath}`); +} +``` + +- [ ] **Step 2: Run the script** + +```bash +npx tsx scripts/update-angular-environments.ts +``` + +Expected: 14 files updated with production LangGraph Cloud URLs. + +- [ ] **Step 3: Verify one file looks correct** + +```bash +cat cockpit/langgraph/streaming/angular/src/environments/environment.ts +``` + +Expected: `langGraphApiUrl` now contains the LangGraph Cloud URL (not localhost). + +- [ ] **Step 4: Commit** + +```bash +git add scripts/update-angular-environments.ts +git add cockpit/langgraph/*/angular/src/environments/environment.ts +git add cockpit/deep-agents/*/angular/src/environments/environment.ts +git commit -m "feat(cockpit): update Angular production environments with LangGraph Cloud URLs" +``` + +--- + +### Task 4: Create Vercel static hosting for Angular examples + +**Files:** +- Create: `vercel.examples.json` +- Create: `scripts/assemble-examples.ts` + +- [ ] **Step 1: Create Vercel config for examples** + +Create `vercel.examples.json` at the workspace root: + +```json +{ + "framework": null, + "buildCommand": null, + "outputDirectory": "deploy/examples", + "installCommand": "npm ci", + "rewrites": [ + { "source": "/langgraph/:path*/:file(.*\\..*)", "destination": "/langgraph/:path*/:file" }, + { "source": "/langgraph/:path*", "destination": "/langgraph/:path*/index.html" }, + { "source": "/deep-agents/:path*/:file(.*\\..*)", "destination": "/deep-agents/:path*/:file" }, + { "source": "/deep-agents/:path*", "destination": "/deep-agents/:path*/index.html" } + ] +} +``` + +The rewrites ensure SPA routing works — any path without a file extension serves `index.html`. + +- [ ] **Step 2: Create the assemble script** + +Create `scripts/assemble-examples.ts`: + +```typescript +#!/usr/bin/env npx tsx +/** + * Build all 14 Angular example apps and assemble them into a deploy directory + * for Vercel static hosting. + * + * Output structure: + * deploy/examples/ + * ├── langgraph/streaming/ (index.html, main.js, styles.css) + * ├── langgraph/persistence/ + * ├── ... + * ├── deep-agents/planning/ + * └── ... + * + * Usage: + * npx tsx scripts/assemble-examples.ts # build + assemble + * npx tsx scripts/assemble-examples.ts --skip-build # assemble only (assumes build already ran) + */ +import { execSync } from 'child_process'; +import { cpSync, mkdirSync, rmSync, existsSync } from 'fs'; +import { resolve } from 'path'; + +const root = resolve(__dirname, '..'); +const deployDir = resolve(root, 'deploy/examples'); +const skipBuild = process.argv.includes('--skip-build'); + +const capabilities = [ + { product: 'langgraph', topic: 'streaming' }, + { product: 'langgraph', topic: 'persistence' }, + { product: 'langgraph', topic: 'interrupts' }, + { product: 'langgraph', topic: 'memory' }, + { product: 'langgraph', topic: 'durable-execution' }, + { product: 'langgraph', topic: 'subgraphs' }, + { product: 'langgraph', topic: 'time-travel' }, + { product: 'langgraph', topic: 'deployment-runtime' }, + { product: 'deep-agents', topic: 'planning' }, + { product: 'deep-agents', topic: 'filesystem' }, + { product: 'deep-agents', topic: 'subagents' }, + { product: 'deep-agents', topic: 'memory' }, + { product: 'deep-agents', topic: 'skills' }, + { product: 'deep-agents', topic: 'sandboxes' }, +]; + +// Step 1: Build all Angular apps +if (!skipBuild) { + console.log('Building all 14 Angular apps...'); + execSync("npx nx run-many -t build --projects='cockpit-*-angular' --skip-nx-cache", { + cwd: root, + stdio: 'inherit', + }); +} + +// Step 2: Clean and create deploy directory +if (existsSync(deployDir)) rmSync(deployDir, { recursive: true }); + +// Step 3: Copy each app's browser output to the deploy directory +for (const cap of capabilities) { + const src = resolve(root, `dist/cockpit/${cap.product}/${cap.topic}/angular/browser`); + const dest = resolve(deployDir, `${cap.product}/${cap.topic}`); + + if (!existsSync(src)) { + console.error(`❌ Missing build output: ${src}`); + process.exit(1); + } + + mkdirSync(dest, { recursive: true }); + cpSync(src, dest, { recursive: true }); + console.log(`✅ ${cap.product}/${cap.topic}`); +} + +console.log(`\nAssembled ${capabilities.length} apps to ${deployDir}`); +``` + +- [ ] **Step 3: Test the assemble script locally** + +```bash +npx tsx scripts/assemble-examples.ts +ls deploy/examples/langgraph/streaming/ +``` + +Expected: `index.html`, `main.js`, `styles.css` in the streaming directory. All 14 apps assembled. + +- [ ] **Step 4: Add deploy directory to .gitignore** + +Add `deploy/` to `.gitignore`: + +``` +# Deploy artifacts +deploy/ +``` + +- [ ] **Step 5: Commit** + +```bash +git add vercel.examples.json scripts/assemble-examples.ts .gitignore +git commit -m "feat: add Vercel static config and assemble script for Angular examples" +``` + +--- + +### Task 5: Add examples deploy job to CI + +**Files:** +- Modify: `.github/workflows/ci.yml` + +- [ ] **Step 1: Add the examples deploy steps to the existing deploy job** + +In `.github/workflows/ci.yml`, add the following steps to the `deploy` job, after the cockpit deploy verification step (around line 261). Insert before the closing of the `deploy` job: + +```yaml + # ── Angular examples deploy ────────────────────────────────────────── + - name: Check if examples changed + id: examples_changed + run: | + base_sha="${{ github.event.before }}" + head_sha="${{ github.sha }}" + if [ -z "$base_sha" ] || [ "$base_sha" = "0000000000000000000000000000000000000000" ]; then + base_sha="$(git rev-parse "$head_sha^")" + fi + changed_files="$(git diff --name-only "$base_sha" "$head_sha")" + examples_changed=false + if printf '%s\n' "$changed_files" | grep -E '^cockpit/.*/angular/' >/dev/null; then + examples_changed=true + fi + if printf '%s\n' "$changed_files" | grep -E '^(vercel\.examples\.json|scripts/assemble-examples\.ts)$' >/dev/null; then + examples_changed=true + fi + echo "changed=$examples_changed" >> "$GITHUB_OUTPUT" + + - name: Build and assemble Angular examples + if: steps.examples_changed.outputs.changed == 'true' + run: npx tsx scripts/assemble-examples.ts + + - name: Prepare examples Vercel project + if: steps.examples_changed.outputs.changed == 'true' + run: | + mkdir -p .vercel + cat > .vercel/project.json < { + for (const cap of CAPABILITIES) { + test(`${cap} loads at examples URL`, async ({ page }) => { + const url = `${EXAMPLES_URL}/${cap}/`; + const res = await page.goto(url, { timeout: 15000 }); + expect(res?.status()).toBe(200); + await expect(page.locator('cp-chat')).toBeVisible({ timeout: 10000 }); + }); + } +}); + +test.describe('Production: cockpit Run mode embeds examples', () => { + test('cockpit loads with sidebar navigation', async ({ page }) => { + await page.goto('/', { timeout: 15000 }); + await expect(page.getByRole('navigation', { name: 'Cockpit navigation' })).toBeVisible(); + // No overview entries should appear + const links = await page.locator('nav a').allTextContents(); + const overviewLinks = links.filter((t) => t.toLowerCase().includes('overview')); + expect(overviewLinks).toHaveLength(0); + }); +}); + +test.describe('Production: send/receive smoke test', () => { + test.skip(() => !process.env['OPENAI_API_KEY'], 'Requires OPENAI_API_KEY'); + + // Test a representative subset: one LangGraph, one Deep Agent + for (const cap of ['langgraph/streaming', 'deep-agents/planning'] as const) { + test(`${cap} sends and receives a message`, async ({ page }) => { + await page.goto(`${EXAMPLES_URL}/${cap}/`, { timeout: 15000 }); + await expect(page.locator('cp-chat')).toBeVisible({ timeout: 10000 }); + + await page.fill('input[name="prompt"]', 'hello'); + await page.click('button[type="submit"]'); + + await expect(page.locator('.cp-message--ai')).toBeVisible({ timeout: 30000 }); + }); + } +}); +``` + +- [ ] **Step 2: Commit** + +```bash +git add apps/cockpit/e2e/production-smoke.spec.ts +git commit -m "test(cockpit): add production smoke test for deployed examples" +``` + +--- + +### Task 7: Add production smoke job to CI + +**Files:** +- Modify: `.github/workflows/ci.yml` + +- [ ] **Step 1: Add production-smoke job** + +Add a new job after the `deploy` job in `.github/workflows/ci.yml`: + +```yaml + production-smoke: + name: Production smoke + needs: [deploy] + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' && github.event_name == 'push' + steps: + - uses: actions/checkout@v6.0.2 + - uses: actions/setup-node@v6.3.0 + with: + node-version: 22 + cache: npm + - run: npm ci + - run: npx playwright install --with-deps chromium + - name: Verify LangGraph backends + run: npx tsx scripts/verify-langgraph-deployments.ts + - name: Run production smoke tests + run: npx playwright test apps/cockpit/e2e/production-smoke.spec.ts --reporter=list + env: + BASE_URL: https://cockpit.stream-resource.dev + EXAMPLES_URL: https://examples.stream-resource.dev + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} +``` + +Also update the `deploy` job's `needs` list in the final deploy step (the `Deploy → Vercel` job) if it has downstream dependencies, and add `production-smoke` to any `needs` lists that reference `deploy`. + +- [ ] **Step 2: Commit** + +```bash +git add .github/workflows/ci.yml +git commit -m "ci: add production smoke test job after deploy" +``` + +--- + +### Task 8: Manual Vercel setup and first deploy + +This task is performed manually — not code changes. + +- [ ] **Step 1: Create the examples Vercel project** + +In the Vercel dashboard: +1. Create a new project named `cockpit-examples` in the `cacheplane` org +2. Set the framework to "Other" (static) +3. Link to the `stream-resource` GitHub repo +4. Set the root directory to `.` (workspace root) +5. Note the project ID + +- [ ] **Step 2: Add GitHub secrets** + +In GitHub repo Settings → Secrets: +1. Add `VERCEL_EXAMPLES_PROJECT_ID` with the project ID from Step 1 +2. Verify `OPENAI_API_KEY` is set (needed for production smoke tests) + +- [ ] **Step 3: Set cockpit env var in Vercel** + +In the cockpit Vercel project settings → Environment Variables: +1. Add `NEXT_PUBLIC_COCKPIT_RUNTIME_BASE_URL` = `https://examples.stream-resource.dev` +2. Apply to Production environment + +- [ ] **Step 4: Set examples domain in Vercel** + +In the examples Vercel project settings → Domains: +1. Add `examples.stream-resource.dev` +2. Configure DNS (CNAME to `cname.vercel-dns.com`) + +- [ ] **Step 5: Trigger initial deploy** + +Push all committed changes to `main`. The CI pipeline will: +1. Build and deploy the cockpit (picks up `NEXT_PUBLIC_COCKPIT_RUNTIME_BASE_URL`) +2. Build and deploy the Angular examples to `examples.stream-resource.dev` +3. Run the production smoke test + +- [ ] **Step 6: Verify end-to-end** + +```bash +# Verify backends +npx tsx scripts/verify-langgraph-deployments.ts --smoke + +# Verify examples load +for cap in langgraph/streaming langgraph/persistence deep-agents/planning; do + curl -s -o /dev/null -w "%{http_code} $cap\n" "https://examples.stream-resource.dev/$cap/" +done + +# Verify cockpit +curl -s -o /dev/null -w "%{http_code} cockpit\n" "https://cockpit.stream-resource.dev" + +# Run full production smoke +BASE_URL=https://cockpit.stream-resource.dev \ +EXAMPLES_URL=https://examples.stream-resource.dev \ +npx playwright test apps/cockpit/e2e/production-smoke.spec.ts --reporter=list +``` + +Expected: all health checks return 200, smoke tests pass. diff --git a/docs/superpowers/specs/2026-04-05-production-deployment-design.md b/docs/superpowers/specs/2026-04-05-production-deployment-design.md new file mode 100644 index 000000000..28324d326 --- /dev/null +++ b/docs/superpowers/specs/2026-04-05-production-deployment-design.md @@ -0,0 +1,204 @@ +# Production Deployment — End-to-End Design + +## Problem + +The cockpit has 14 capability examples (8 LangGraph + 6 Deep Agents), each with an Angular frontend and a Python LangGraph backend. None are deployed to production. The Angular `environment.ts` files still point to `localhost`, the LangGraph Cloud deployments have never been triggered, and there is no hosting for the Angular apps. The cockpit at `https://cockpit.stream-resource.dev` deploys via Vercel but its Run mode iframes show nothing because the Angular apps aren't hosted anywhere. + +## Goal + +Deploy the full stack to production in three phases: +1. Deploy all 14 Python backends to LangGraph Cloud +2. Build and deploy all 14 Angular apps to Vercel as static sites +3. Wire the cockpit to the production Angular apps and verify end-to-end + +After completion, a user visiting `https://cockpit.stream-resource.dev`, navigating to any capability, and switching to Run mode sees a working chat interface that sends messages to a production LangGraph Cloud backend and receives AI responses. + +## Architecture Overview + +``` +cockpit.stream-resource.dev (Vercel / Next.js) + └─ iframe src: examples.stream-resource.dev/{product}/{topic}/ + └─ Angular static app (Vercel) + └─ API proxy → https://{org}-{name}.us.langgraph.app + └─ LangGraph Cloud (LangSmith-hosted Python backend) +``` + +Three deployment surfaces, three CI workflows, one verification pipeline. + +## Phase 1: LangGraph Cloud — 14 Python Backends + +### What exists + +- `deploy-langgraph.yml` workflow with a 14-entry matrix +- Each capability has a `langgraph.json` in its `python/` directory +- `LANGSMITH_API_KEY` is already in GitHub Secrets +- Each backend uses `ChatOpenAI()` which requires `OPENAI_API_KEY` + +### What needs to happen + +1. **Set `OPENAI_API_KEY` in LangGraph Cloud** — each deployed backend reads environment variables from LangGraph Cloud's environment config, not from a local `.env` file. The `OPENAI_API_KEY` must be set in the LangSmith deployment environment (via the LangSmith UI or `langgraph` CLI environment config). + +2. **Trigger the deployment** — run `workflow_dispatch` on `deploy-langgraph.yml` with no capability filter to deploy all 14. The workflow runs `langgraph deploy` from each capability's python directory. + +3. **Capture deployment URLs** — after deployment, each backend gets a URL of the form `https://{deployment-id}.us.langgraph.app` (or similar). These URLs are needed for Phase 2. They can be retrieved from LangSmith UI or via `langgraph` CLI. + +4. **Create a deployment URL registry** — store all 14 deployment URLs in a single file that the Angular environment files and CI verification scripts can reference. This becomes the source of truth for production backend URLs. + +### Deployment URL Registry + +Create `deployment-urls.json` at the workspace root: + +```json +{ + "streaming": "https://cacheplane-streaming-abc123.us.langgraph.app", + "persistence": "https://cacheplane-persistence-def456.us.langgraph.app", + "interrupts": "https://cacheplane-interrupts-ghi789.us.langgraph.app", + ... +} +``` + +This file is committed to the repo and referenced by: +- Angular `environment.ts` production files (via a build-time script or manual update) +- CI verification scripts + +### Verification + +For each of the 14 backends: +1. **Health check** — `GET /ok` returns `{"ok": true}` +2. **Smoke test** — create a thread, send "hello" via `/threads/{id}/runs/stream`, verify an AI response with `type: "ai"` appears in the stream + +## Phase 2: Angular Example Apps — Vercel Static Hosting + +### What exists + +- 14 Angular apps, each built by `@angular/build:application` to `dist/cockpit/{product}/{topic}/angular/browser/` +- `cockpit/langgraph/streaming/angular/vercel.json` exists as a template for standalone Angular deployment +- `environment.ts` files have `langGraphApiUrl` pointing to localhost +- The cockpit uses `NEXT_PUBLIC_COCKPIT_RUNTIME_BASE_URL` to construct iframe URLs + +### What needs to happen + +1. **Create a Vercel project** for `examples.stream-resource.dev`. This is a static hosting project — no framework, just serves static files. + +2. **Update Angular `environment.ts` production files** — replace `http://localhost:4XXX/api` with the LangGraph Cloud URL for each capability (from `deployment-urls.json`). The development files stay pointing to localhost for local dev. + +3. **Build all 14 Angular apps** — `npx nx run-many -t build --projects='cockpit-*-angular'` produces static output in `dist/`. + +4. **Assemble a deploy directory** — copy all 14 built apps into a single directory structure: + ``` + deploy/examples/ + ├── langgraph/ + │ ├── streaming/ → dist/cockpit/langgraph/streaming/angular/browser/ + │ ├── persistence/ → dist/cockpit/langgraph/persistence/angular/browser/ + │ └── ... + └── deep-agents/ + ├── planning/ → dist/cockpit/deep-agents/planning/angular/browser/ + └── ... + ``` + +5. **Deploy to Vercel** — `vercel deploy --prod` from the assembled directory. + +6. **Add CI deploy job** — a new job in `ci.yml` (or a separate workflow) that builds, assembles, and deploys all Angular apps on push to main. + +7. **Set `NEXT_PUBLIC_COCKPIT_RUNTIME_BASE_URL`** — in the cockpit's Vercel project environment variables, set this to `https://examples.stream-resource.dev`. The cockpit's `content-bundle.ts` uses this to construct iframe URLs like `https://examples.stream-resource.dev/langgraph/streaming/`. + +### Angular Proxy in Production + +In local dev, the Angular apps use a proxy (`proxy.conf.json`) to forward `/api` requests to the LangGraph backend. In production, there is no proxy — the Angular app uses the full LangGraph Cloud URL directly via `environment.langGraphApiUrl`. The `@langchain/langgraph-sdk` `Client` constructor takes the full URL. + +This means the production `environment.ts` should NOT use `/api` — it should use the full LangGraph Cloud URL: + +```typescript +export const environment = { + production: true, + langGraphApiUrl: 'https://cacheplane-streaming-abc123.us.langgraph.app', + streamingAssistantId: 'streaming', +}; +``` + +### CORS + +LangGraph Cloud backends need to allow requests from `examples.stream-resource.dev`. LangGraph Cloud typically allows CORS from any origin for the streaming API, but this should be verified. If CORS is restricted, the LangGraph deployment config may need a `cors_allowed_origins` setting. + +### Verification + +For each of the 14 Angular apps: +1. **Load test** — fetch the production URL, verify HTTP 200 +2. **UI render** — Playwright: navigate to URL, verify `cp-chat` component is visible +3. **Send/receive** — Playwright: type "hello", click send, wait for AI response in `.cp-message--ai` + +## Phase 3: Cockpit Verification + +### What exists + +- Cockpit deploys to `https://cockpit.stream-resource.dev` via Vercel +- CI deploy job already exists and runs on push to main +- Post-deploy smoke script already exists (`cockpit-deploy-smoke`) + +### What needs to happen + +1. **Set `NEXT_PUBLIC_COCKPIT_RUNTIME_BASE_URL`** in Vercel (done in Phase 2) +2. **Trigger cockpit redeploy** — the env var change requires a redeploy for Next.js to pick it up +3. **Verify the cockpit renders** — navigate to cockpit, confirm sidebar loads with all capabilities (no "overview" entries) +4. **Verify Run mode** — for each capability, switch to Run mode, confirm the iframe loads the Angular app from `examples.stream-resource.dev` +5. **End-to-end smoke** — for a representative subset (at minimum streaming, persistence, planning), send a message in Run mode and verify AI response + +### CI Verification Pipeline + +Add a post-deploy verification job that runs after the deploy job succeeds: + +```yaml +production-smoke: + name: Production smoke test + needs: [deploy] + if: github.ref == 'refs/heads/main' && github.event_name == 'push' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6.0.2 + - uses: actions/setup-node@v6.3.0 + - run: npm ci + - run: npx playwright install --with-deps chromium + - run: npx playwright test apps/cockpit/e2e/production-smoke.spec.ts + env: + BASE_URL: https://cockpit.stream-resource.dev + EXAMPLES_URL: https://examples.stream-resource.dev + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} +``` + +This test file verifies: +- Cockpit loads and sidebar renders +- Run mode iframes load Angular apps +- Chat send/receive works for representative capabilities + +## File Map + +| Action | File | Phase | +|--------|------|-------| +| Create | `deployment-urls.json` | 1 | +| Create | `scripts/verify-langgraph-backends.ts` | 1 | +| Modify | `cockpit/langgraph/*/angular/src/environments/environment.ts` (8) | 2 | +| Modify | `cockpit/deep-agents/*/angular/src/environments/environment.ts` (6) | 2 | +| Create | `scripts/assemble-examples.ts` | 2 | +| Create | `vercel.examples.json` | 2 | +| Modify | `.github/workflows/ci.yml` | 2, 3 | +| Create | `apps/cockpit/e2e/production-smoke.spec.ts` | 3 | + +## Secrets Required + +| Secret | Where | Purpose | +|--------|-------|---------| +| `LANGSMITH_API_KEY` | GitHub Actions | LangGraph Cloud deploy | +| `OPENAI_API_KEY` | LangGraph Cloud env | LLM calls in production | +| `OPENAI_API_KEY` | GitHub Actions | Production smoke tests | +| `VERCEL_TOKEN` | GitHub Actions | Vercel deploys (existing) | +| `VERCEL_ORG_ID` | GitHub Actions | Vercel org (existing) | +| `VERCEL_COCKPIT_PROJECT_ID` | GitHub Actions | Cockpit Vercel project (existing) | +| `VERCEL_EXAMPLES_PROJECT_ID` | GitHub Actions | New: examples Vercel project | + +## Out of Scope + +- Website deployment (already working) +- Chat library rebuild (separate effort) +- Custom domain SSL configuration (Vercel handles this) +- LangGraph Cloud auto-scaling or performance tuning +- Monitoring and alerting diff --git a/libs/chat/src/public-api.ts b/libs/chat/src/public-api.ts index ae1b8b411..e71fac43a 100644 --- a/libs/chat/src/public-api.ts +++ b/libs/chat/src/public-api.ts @@ -21,6 +21,9 @@ export { ChatGenerativeUiComponent } from './lib/primitives/chat-generative-ui/c // DI provider export { provideChat, CHAT_CONFIG } from './lib/provide-chat'; +// Legacy cp-chat component (used by cockpit example apps) +export { ChatComponent as LegacyChatComponent } from './lib/chat.component'; + // Compositions export { ChatComponent } from './lib/compositions/chat/chat.component'; export { ChatInterruptPanelComponent } from './lib/compositions/chat-interrupt-panel/chat-interrupt-panel.component'; diff --git a/scripts/assemble-examples.ts b/scripts/assemble-examples.ts new file mode 100644 index 000000000..5e0fc5e5e --- /dev/null +++ b/scripts/assemble-examples.ts @@ -0,0 +1,60 @@ +#!/usr/bin/env npx tsx +/** + * Build all 14 Angular example apps and assemble them into a deploy directory. + * + * Output: deploy/examples/{product}/{topic}/ with index.html, main.js, styles.css + * + * Usage: + * npx tsx scripts/assemble-examples.ts + * npx tsx scripts/assemble-examples.ts --skip-build + */ +import { execSync } from 'child_process'; +import { cpSync, mkdirSync, rmSync, existsSync } from 'fs'; +import { resolve } from 'path'; + +const root = resolve(__dirname, '..'); +const deployDir = resolve(root, 'deploy/examples'); +const skipBuild = process.argv.includes('--skip-build'); + +const capabilities = [ + { product: 'langgraph', topic: 'streaming' }, + { product: 'langgraph', topic: 'persistence' }, + { product: 'langgraph', topic: 'interrupts' }, + { product: 'langgraph', topic: 'memory' }, + { product: 'langgraph', topic: 'durable-execution' }, + { product: 'langgraph', topic: 'subgraphs' }, + { product: 'langgraph', topic: 'time-travel' }, + { product: 'langgraph', topic: 'deployment-runtime' }, + { product: 'deep-agents', topic: 'planning' }, + { product: 'deep-agents', topic: 'filesystem' }, + { product: 'deep-agents', topic: 'subagents' }, + { product: 'deep-agents', topic: 'memory' }, + { product: 'deep-agents', topic: 'skills' }, + { product: 'deep-agents', topic: 'sandboxes' }, +]; + +if (!skipBuild) { + console.log('Building all 14 Angular apps...'); + execSync("npx nx run-many -t build --projects='cockpit-*-angular' --skip-nx-cache", { + cwd: root, + stdio: 'inherit', + }); +} + +if (existsSync(deployDir)) rmSync(deployDir, { recursive: true }); + +for (const cap of capabilities) { + const src = resolve(root, `dist/cockpit/${cap.product}/${cap.topic}/angular`); + const dest = resolve(deployDir, `${cap.product}/${cap.topic}`); + + if (!existsSync(src)) { + console.error(`❌ Missing build output: ${src}`); + process.exit(1); + } + + mkdirSync(dest, { recursive: true }); + cpSync(src, dest, { recursive: true }); + console.log(`✅ ${cap.product}/${cap.topic}`); +} + +console.log(`\nAssembled ${capabilities.length} apps to ${deployDir}`); diff --git a/scripts/update-angular-environments.ts b/scripts/update-angular-environments.ts new file mode 100644 index 000000000..ca701931d --- /dev/null +++ b/scripts/update-angular-environments.ts @@ -0,0 +1,54 @@ +#!/usr/bin/env npx tsx +/** + * Update all Angular production environment.ts files with LangGraph Cloud URLs + * from deployment-urls.json. + */ +import { readFileSync, writeFileSync } from 'fs'; +import { resolve } from 'path'; + +const root = resolve(__dirname, '..'); +const urls: Record = JSON.parse( + readFileSync(resolve(root, 'deployment-urls.json'), 'utf-8'), +); + +const capabilities = [ + { dir: 'cockpit/langgraph/streaming/angular', assistantId: 'streaming', field: 'streamingAssistantId' }, + { dir: 'cockpit/langgraph/persistence/angular', assistantId: 'persistence', field: 'streamingAssistantId' }, + { dir: 'cockpit/langgraph/interrupts/angular', assistantId: 'interrupts', field: 'streamingAssistantId' }, + { dir: 'cockpit/langgraph/memory/angular', assistantId: 'memory', field: 'streamingAssistantId' }, + { dir: 'cockpit/langgraph/durable-execution/angular', assistantId: 'durable-execution', field: 'streamingAssistantId' }, + { dir: 'cockpit/langgraph/subgraphs/angular', assistantId: 'subgraphs', field: 'streamingAssistantId' }, + { dir: 'cockpit/langgraph/time-travel/angular', assistantId: 'time-travel', field: 'streamingAssistantId' }, + { dir: 'cockpit/langgraph/deployment-runtime/angular', assistantId: 'deployment-runtime', field: 'deploymentRuntimeAssistantId' }, + { dir: 'cockpit/deep-agents/planning/angular', assistantId: 'planning', field: 'streamingAssistantId' }, + { dir: 'cockpit/deep-agents/filesystem/angular', assistantId: 'filesystem', field: 'streamingAssistantId' }, + { dir: 'cockpit/deep-agents/subagents/angular', assistantId: 'subagents', field: 'streamingAssistantId' }, + { dir: 'cockpit/deep-agents/memory/angular', assistantId: 'da-memory', field: 'streamingAssistantId' }, + { dir: 'cockpit/deep-agents/skills/angular', assistantId: 'skills', field: 'streamingAssistantId' }, + { dir: 'cockpit/deep-agents/sandboxes/angular', assistantId: 'sandboxes', field: 'streamingAssistantId' }, +]; + +for (const cap of capabilities) { + const url = urls[cap.assistantId]; + if (!url || url === 'PENDING_DEPLOYMENT') { + console.log(`⏭️ ${cap.assistantId}: skipped (pending deployment)`); + continue; + } + + const envPath = resolve(root, cap.dir, 'src/environments/environment.ts'); + const content = `/** + * Production environment configuration. + * + * Points to the LangGraph Cloud deployment managed by LangSmith. + * The assistantId must match the graph name in langgraph.json. + */ +export const environment = { + production: true, + langGraphApiUrl: '${url}', + ${cap.field}: '${cap.assistantId}', +}; +`; + + writeFileSync(envPath, content); + console.log(`✅ ${cap.assistantId}: ${envPath}`); +} diff --git a/scripts/verify-langgraph-deployments.ts b/scripts/verify-langgraph-deployments.ts new file mode 100644 index 000000000..f9b676def --- /dev/null +++ b/scripts/verify-langgraph-deployments.ts @@ -0,0 +1,90 @@ +#!/usr/bin/env npx tsx +/** + * Verify all LangGraph Cloud deployments are healthy and can process messages. + * + * Usage: + * npx tsx scripts/verify-langgraph-deployments.ts + * npx tsx scripts/verify-langgraph-deployments.ts --capability streaming + * npx tsx scripts/verify-langgraph-deployments.ts --smoke + */ +import { readFileSync } from 'fs'; +import { resolve } from 'path'; + +const urls: Record = JSON.parse( + readFileSync(resolve(__dirname, '../deployment-urls.json'), 'utf-8'), +); + +const capability = process.argv.find((a) => a === '--capability') + ? process.argv[process.argv.indexOf('--capability') + 1] + : null; + +const smoke = process.argv.includes('--smoke'); + +const entries = capability + ? [[capability, urls[capability]] as const] + : Object.entries(urls); + +async function main() { +let passed = 0; +let failed = 0; + +for (const [name, url] of entries) { + if (!url || url === 'PENDING_DEPLOYMENT') { + console.log(`⏭️ ${name}: skipped (pending deployment)`); + continue; + } + + // Health check + try { + const res = await fetch(`${url}/ok`, { signal: AbortSignal.timeout(10000) }); + const data = await res.json(); + if (!data.ok) throw new Error(`/ok returned ${JSON.stringify(data)}`); + console.log(`✅ ${name}: healthy (${url})`); + } catch (err) { + console.error(`❌ ${name}: health check failed — ${(err as Error).message}`); + failed++; + continue; + } + + if (smoke) { + try { + const threadRes = await fetch(`${url}/threads`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ metadata: {} }), + signal: AbortSignal.timeout(10000), + }); + const thread = await threadRes.json(); + const threadId = thread.thread_id; + + const runRes = await fetch(`${url}/threads/${threadId}/runs/stream`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + assistant_id: name, + input: { messages: [{ role: 'human', content: 'hello' }] }, + stream_mode: ['values'], + }), + signal: AbortSignal.timeout(30000), + }); + + const text = await runRes.text(); + if (!text.includes('"type":"ai"')) { + throw new Error('No AI response in stream'); + } + console.log(`✅ ${name}: smoke test passed`); + } catch (err) { + console.error(`❌ ${name}: smoke test failed — ${(err as Error).message}`); + failed++; + continue; + } + } + + passed++; +} + +console.log(`\n${passed} passed, ${failed} failed out of ${entries.length}`); +if (failed > 0) process.exit(1); +} + +main(); diff --git a/vercel.examples.json b/vercel.examples.json new file mode 100644 index 000000000..982c3f918 --- /dev/null +++ b/vercel.examples.json @@ -0,0 +1,12 @@ +{ + "framework": null, + "buildCommand": null, + "outputDirectory": "deploy/examples", + "installCommand": "npm ci", + "rewrites": [ + { "source": "/langgraph/:path*/:file(.*\\..*)", "destination": "/langgraph/:path*/:file" }, + { "source": "/langgraph/:path*", "destination": "/langgraph/:path*/index.html" }, + { "source": "/deep-agents/:path*/:file(.*\\..*)", "destination": "/deep-agents/:path*/:file" }, + { "source": "/deep-agents/:path*", "destination": "/deep-agents/:path*/index.html" } + ] +}