Skip to content

Commit 64c86a2

Browse files
authored
feat(cockpit): production deployment - LangGraph Cloud, Angular hosting, CI (#19)
* 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) * 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
1 parent 40def35 commit 64c86a2

82 files changed

Lines changed: 2918 additions & 474 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ jobs:
179179
# VERCEL_ORG_ID — Vercel team id
180180
# VERCEL_WEBSITE_PROJECT_ID — website project id
181181
# VERCEL_COCKPIT_PROJECT_ID — cockpit project id
182+
# VERCEL_EXAMPLES_PROJECT_ID — examples project id
182183
- run: npm ci
183184
- run: npx playwright install --with-deps chromium
184185
- name: Resolve deploy targets
@@ -259,3 +260,61 @@ jobs:
259260
if: steps.affected.outputs.cockpit == 'true'
260261
run: |
261262
npx tsx apps/cockpit/scripts/deploy-smoke.ts --url https://cockpit.stream-resource.dev --retries 20 --retry-delay-ms 5000
263+
264+
# ── Angular examples deploy ──────────────────────────────────────────
265+
- name: Check if examples changed
266+
id: examples_changed
267+
run: |
268+
base_sha="${{ github.event.before }}"
269+
head_sha="${{ github.sha }}"
270+
if [ -z "$base_sha" ] || [ "$base_sha" = "0000000000000000000000000000000000000000" ]; then
271+
base_sha="$(git rev-parse "$head_sha^")"
272+
fi
273+
changed_files="$(git diff --name-only "$base_sha" "$head_sha")"
274+
examples_changed=false
275+
if printf '%s\n' "$changed_files" | grep -E '^cockpit/.*/angular/' >/dev/null; then
276+
examples_changed=true
277+
fi
278+
if printf '%s\n' "$changed_files" | grep -E '^(vercel\.examples\.json|scripts/assemble-examples\.ts)$' >/dev/null; then
279+
examples_changed=true
280+
fi
281+
echo "changed=$examples_changed" >> "$GITHUB_OUTPUT"
282+
- name: Build and assemble Angular examples
283+
if: steps.examples_changed.outputs.changed == 'true'
284+
run: npx tsx scripts/assemble-examples.ts
285+
- name: Prepare examples Vercel project
286+
if: steps.examples_changed.outputs.changed == 'true'
287+
run: |
288+
mkdir -p .vercel
289+
cat > .vercel/project.json <<EOF
290+
{"projectId":"${{ secrets.VERCEL_EXAMPLES_PROJECT_ID }}","orgId":"${{ secrets.VERCEL_ORG_ID }}","projectName":"cockpit-examples"}
291+
EOF
292+
npx vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
293+
rm -rf .vercel/output
294+
- name: Deploy Angular examples to Vercel (production)
295+
if: steps.examples_changed.outputs.changed == 'true'
296+
run: |
297+
npx vercel build --prod --local-config vercel.examples.json --token=${{ secrets.VERCEL_TOKEN }}
298+
npx vercel deploy --prebuilt --archive=tgz --prod --yes --token=${{ secrets.VERCEL_TOKEN }}
299+
300+
production-smoke:
301+
name: Production smoke
302+
needs: [deploy]
303+
runs-on: ubuntu-latest
304+
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
305+
steps:
306+
- uses: actions/checkout@v6.0.2
307+
- uses: actions/setup-node@v6.3.0
308+
with:
309+
node-version: 22
310+
cache: npm
311+
- run: npm ci
312+
- run: npx playwright install --with-deps chromium
313+
- name: Verify LangGraph backends
314+
run: npx tsx scripts/verify-langgraph-deployments.ts
315+
- name: Run production smoke tests
316+
run: npx playwright test apps/cockpit/e2e/production-smoke.spec.ts --reporter=list
317+
env:
318+
BASE_URL: https://cockpit.stream-resource.dev
319+
EXAMPLES_URL: https://examples.stream-resource.dev
320+
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,6 @@ langgraph-combined.json
3434

3535
# Playwright
3636
test-results/
37+
38+
# Deploy output
39+
deploy/
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { expect, test } from '@playwright/test';
2+
3+
/**
4+
* Production smoke test — verifies the deployed stack works end-to-end.
5+
*
6+
* Requires:
7+
* EXAMPLES_URL - e.g., https://examples.stream-resource.dev
8+
* OPENAI_API_KEY - for send/receive tests (optional)
9+
*
10+
* Run:
11+
* BASE_URL=https://cockpit.stream-resource.dev \
12+
* EXAMPLES_URL=https://examples.stream-resource.dev \
13+
* npx playwright test apps/cockpit/e2e/production-smoke.spec.ts
14+
*/
15+
16+
const EXAMPLES_URL = process.env['EXAMPLES_URL'] ?? 'https://examples.stream-resource.dev';
17+
18+
const CAPABILITIES = [
19+
'langgraph/streaming',
20+
'langgraph/persistence',
21+
'langgraph/interrupts',
22+
'langgraph/memory',
23+
'langgraph/durable-execution',
24+
'langgraph/subgraphs',
25+
'langgraph/time-travel',
26+
'langgraph/deployment-runtime',
27+
'deep-agents/planning',
28+
'deep-agents/filesystem',
29+
'deep-agents/subagents',
30+
'deep-agents/memory',
31+
'deep-agents/skills',
32+
'deep-agents/sandboxes',
33+
] as const;
34+
35+
test.describe('Production: Angular example apps load', () => {
36+
for (const cap of CAPABILITIES) {
37+
test(`${cap} loads at examples URL`, async ({ page }) => {
38+
const url = `${EXAMPLES_URL}/${cap}/`;
39+
const res = await page.goto(url, { timeout: 15000 });
40+
expect(res?.status()).toBe(200);
41+
await expect(page.locator('cp-chat')).toBeVisible({ timeout: 10000 });
42+
});
43+
}
44+
});
45+
46+
test.describe('Production: cockpit loads correctly', () => {
47+
test('cockpit loads with sidebar navigation', async ({ page }) => {
48+
await page.goto('/', { timeout: 15000 });
49+
await expect(page.getByRole('navigation', { name: 'Cockpit navigation' })).toBeVisible();
50+
const links = await page.locator('nav a').allTextContents();
51+
const overviewLinks = links.filter((t) => t.toLowerCase().includes('overview'));
52+
expect(overviewLinks).toHaveLength(0);
53+
});
54+
});
55+
56+
test.describe('Production: send/receive smoke', () => {
57+
test.skip(() => !process.env['OPENAI_API_KEY'], 'Requires OPENAI_API_KEY');
58+
59+
for (const cap of ['langgraph/streaming', 'deep-agents/planning'] as const) {
60+
test(`${cap} sends and receives a message`, async ({ page }) => {
61+
await page.goto(`${EXAMPLES_URL}/${cap}/`, { timeout: 15000 });
62+
await expect(page.locator('cp-chat')).toBeVisible({ timeout: 10000 });
63+
await page.fill('input[name="prompt"]', 'hello');
64+
await page.click('button[type="submit"]');
65+
await expect(page.locator('.cp-message--ai')).toBeVisible({ timeout: 30000 });
66+
});
67+
}
68+
});

apps/cockpit/playwright.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const shouldStartLocalServer = !process.env['BASE_URL'];
55

66
export default defineConfig({
77
testDir: './e2e',
8+
testIgnore: ['**/all-examples-smoke*'],
89
fullyParallel: true,
910
use: {
1011
baseURL,

cockpit/deep-agents/filesystem/angular/project.json

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,48 +5,44 @@
55
"projectType": "application",
66
"targets": {
77
"build": {
8-
"executor": "@angular-devkit/build-angular:application",
9-
"outputs": ["{options.outputPath}"],
8+
"executor": "@angular/build:application",
9+
"outputs": ["{options.outputPath.base}"],
1010
"options": {
11-
"outputPath": "dist/cockpit/deep-agents/filesystem/angular",
12-
"index": "cockpit/deep-agents/filesystem/angular/src/index.html",
11+
"outputPath": {
12+
"base": "dist/cockpit/deep-agents/filesystem/angular",
13+
"browser": ""
14+
},
1315
"browser": "cockpit/deep-agents/filesystem/angular/src/main.ts",
1416
"tsConfig": "cockpit/deep-agents/filesystem/angular/tsconfig.app.json",
1517
"styles": ["cockpit/deep-agents/filesystem/angular/src/styles.css"]
1618
},
1719
"configurations": {
1820
"production": {
19-
"outputHashing": "all"
21+
"budgets": [
22+
{ "type": "initial", "maximumWarning": "500kb", "maximumError": "1mb" },
23+
{ "type": "anyComponentStyle", "maximumWarning": "4kb", "maximumError": "8kb" }
24+
],
25+
"outputHashing": "none"
2026
},
2127
"development": {
2228
"optimization": false,
2329
"extractLicenses": false,
24-
"sourceMap": true,
25-
"fileReplacements": [
26-
{
27-
"replace": "cockpit/deep-agents/filesystem/angular/src/environments/environment.ts",
28-
"with": "cockpit/deep-agents/filesystem/angular/src/environments/environment.development.ts"
29-
}
30-
]
30+
"sourceMap": true
3131
}
3232
},
3333
"defaultConfiguration": "production"
3434
},
3535
"serve": {
36-
"executor": "@angular-devkit/build-angular:dev-server",
37-
"options": {
38-
"port": 4311,
39-
"proxyConfig": "cockpit/deep-agents/filesystem/angular/proxy.conf.json"
40-
},
36+
"continuous": true,
37+
"executor": "@angular/build:dev-server",
4138
"configurations": {
42-
"production": {
43-
"buildTarget": "cockpit-deep-agents-filesystem-angular:build:production"
44-
},
45-
"development": {
46-
"buildTarget": "cockpit-deep-agents-filesystem-angular:build:development"
47-
}
39+
"production": { "buildTarget": "cockpit-deep-agents-filesystem-angular:build:production" },
40+
"development": { "buildTarget": "cockpit-deep-agents-filesystem-angular:build:development" }
4841
},
49-
"defaultConfiguration": "development"
42+
"defaultConfiguration": "development",
43+
"options": {
44+
"proxyConfig": "cockpit/deep-agents/filesystem/angular/proxy.conf.json"
45+
}
5046
},
5147
"smoke": {
5248
"executor": "nx:run-commands",
Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,89 @@
1-
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
2-
import { Component } from '@angular/core';
3-
import { ChatDebugComponent } from '@cacheplane/chat';
1+
import { Component, computed } from '@angular/core';
2+
import { LegacyChatComponent } from '@cacheplane/chat';
43
import { streamResource } from '@cacheplane/stream-resource';
54
import { environment } from '../environments/environment';
65

6+
interface ToolCallEntry {
7+
name: string;
8+
args: string;
9+
result?: string;
10+
}
11+
12+
/**
13+
* FilesystemComponent demonstrates agent file operations.
14+
*
15+
* The agent can read and write files using tool calls. The sidebar
16+
* shows a real-time log of each file operation as it happens.
17+
*
18+
* Key integration points:
19+
* - `stream.messages()` contains all messages including tool call results
20+
* - `computed()` derives tool call entries from AI messages
21+
* - Tool calls update reactively as the agent performs file operations
22+
*/
723
@Component({
824
selector: 'app-filesystem',
925
standalone: true,
10-
imports: [ChatDebugComponent],
11-
template: `<chat-debug [ref]="stream" class="block h-screen" />`,
26+
imports: [LegacyChatComponent],
27+
template: `
28+
<cp-chat
29+
[messages]="stream.messages()"
30+
[isLoading]="stream.isLoading()"
31+
[error]="stream.error()"
32+
(sendMessage)="send($event)">
33+
<ng-template #sidebar>
34+
<h3 style="font-size: 0.8rem; font-weight: 600; margin-bottom: 0.75rem; color: #1a1a2e;">File Operations</h3>
35+
@for (entry of toolCallEntries(); track $index) {
36+
<div style="display: flex; align-items: flex-start; gap: 8px; padding: 6px 0; font-size: 0.8rem; border-bottom: 1px solid #e5e7eb;">
37+
<span style="flex-shrink: 0; font-size: 1rem; line-height: 1.2;">
38+
{{ entry.name === 'read_file' ? '📖' : '✏️' }}
39+
</span>
40+
<div style="min-width: 0;">
41+
<div style="font-weight: 500; color: #1a1a2e; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
42+
{{ getFilePath(entry.args) }}
43+
</div>
44+
<div style="color: #8b8fa3; font-size: 0.75rem; margin-top: 2px;">
45+
{{ entry.name === 'read_file' ? 'read' : 'write' }}
46+
{{ entry.result ? ' · done' : ' · running…' }}
47+
</div>
48+
</div>
49+
</div>
50+
}
51+
@empty {
52+
<p style="color: #8b8fa3; font-size: 0.8rem;">Ask the agent to read or write a file.</p>
53+
}
54+
</ng-template>
55+
</cp-chat>
56+
`,
1257
})
1358
export class FilesystemComponent {
1459
protected readonly stream = streamResource({
1560
apiUrl: environment.langGraphApiUrl,
1661
assistantId: environment.streamingAssistantId,
1762
});
63+
64+
toolCallEntries = computed(() => {
65+
const msg = this.stream.messages();
66+
const calls: ToolCallEntry[] = [];
67+
for (const m of msg) {
68+
if ((m as any).tool_calls) {
69+
for (const tc of (m as any).tool_calls) {
70+
calls.push({ name: tc.name, args: JSON.stringify(tc.args), result: tc.output });
71+
}
72+
}
73+
}
74+
return calls;
75+
});
76+
77+
getFilePath(args: string): string {
78+
try {
79+
const parsed = JSON.parse(args);
80+
return parsed.path ?? args;
81+
} catch {
82+
return args;
83+
}
84+
}
85+
86+
send(text: string): void {
87+
this.stream.submit({ messages: [{ role: 'human', content: text }] });
88+
}
1889
}

cockpit/deep-agents/filesystem/angular/src/environments/environment.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@
66
*/
77
export const environment = {
88
production: true,
9-
langGraphApiUrl: 'http://localhost:4311/api',
9+
langGraphApiUrl: 'https://filesystem-2330285f57625bff8654bc026f70a6ae.us.langgraph.app',
1010
streamingAssistantId: 'filesystem',
1111
};
Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,24 @@
11
{
22
"extends": "../../../../tsconfig.base.json",
33
"compilerOptions": {
4-
"module": "preserve"
4+
"noPropertyAccessFromIndexSignature": false,
5+
"experimentalDecorators": true,
6+
"module": "preserve",
7+
"emitDeclarationOnly": false,
8+
"composite": false,
9+
"lib": ["es2022", "dom"],
10+
"skipLibCheck": true,
11+
"strict": false
512
},
6-
"include": ["src/**/*.ts"]
13+
"angularCompilerOptions": {
14+
"enableI18nLegacyMessageIdFormat": false,
15+
"strictInjectionParameters": false,
16+
"strictInputAccessModifiers": false,
17+
"strictTemplates": false
18+
},
19+
"files": [],
20+
"include": [],
21+
"references": [
22+
{ "path": "./tsconfig.app.json" }
23+
]
724
}

cockpit/deep-agents/filesystem/python/langgraph.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
"graphs": {
33
"filesystem": "./src/graph.py:graph"
44
},
5-
"dependencies": ["./pyproject.toml"],
6-
"env": ".env"
5+
"dependencies": ["."],
6+
"python_version": "3.12"
77
}

0 commit comments

Comments
 (0)