Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 <<EOF
{"projectId":"${{ secrets.VERCEL_EXAMPLES_PROJECT_ID }}","orgId":"${{ secrets.VERCEL_ORG_ID }}","projectName":"cockpit-examples"}
EOF
npx vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
rm -rf .vercel/output
- name: Deploy Angular examples to Vercel (production)
if: steps.examples_changed.outputs.changed == 'true'
run: |
npx vercel build --prod --local-config vercel.examples.json --token=${{ secrets.VERCEL_TOKEN }}
npx vercel deploy --prebuilt --archive=tgz --prod --yes --token=${{ secrets.VERCEL_TOKEN }}

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 }}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,6 @@ langgraph-combined.json

# Playwright
test-results/

# Deploy output
deploy/
68 changes: 68 additions & 0 deletions apps/cockpit/e2e/production-smoke.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { expect, test } from '@playwright/test';

/**
* Production smoke test — verifies the deployed stack works end-to-end.
*
* Requires:
* EXAMPLES_URL - e.g., https://examples.stream-resource.dev
* OPENAI_API_KEY - for send/receive tests (optional)
*
* Run:
* BASE_URL=https://cockpit.stream-resource.dev \
* EXAMPLES_URL=https://examples.stream-resource.dev \
* npx playwright test apps/cockpit/e2e/production-smoke.spec.ts
*/

const EXAMPLES_URL = process.env['EXAMPLES_URL'] ?? 'https://examples.stream-resource.dev';

const CAPABILITIES = [
'langgraph/streaming',
'langgraph/persistence',
'langgraph/interrupts',
'langgraph/memory',
'langgraph/durable-execution',
'langgraph/subgraphs',
'langgraph/time-travel',
'langgraph/deployment-runtime',
'deep-agents/planning',
'deep-agents/filesystem',
'deep-agents/subagents',
'deep-agents/memory',
'deep-agents/skills',
'deep-agents/sandboxes',
] as const;

test.describe('Production: Angular example apps load', () => {
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 });
});
}
});
1 change: 1 addition & 0 deletions apps/cockpit/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const shouldStartLocalServer = !process.env['BASE_URL'];

export default defineConfig({
testDir: './e2e',
testIgnore: ['**/all-examples-smoke*'],
fullyParallel: true,
use: {
baseURL,
Expand Down
44 changes: 20 additions & 24 deletions cockpit/deep-agents/filesystem/angular/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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: `<chat-debug [ref]="stream" class="block h-screen" />`,
imports: [LegacyChatComponent],
template: `
<cp-chat
[messages]="stream.messages()"
[isLoading]="stream.isLoading()"
[error]="stream.error()"
(sendMessage)="send($event)">
<ng-template #sidebar>
<h3 style="font-size: 0.8rem; font-weight: 600; margin-bottom: 0.75rem; color: #1a1a2e;">File Operations</h3>
@for (entry of toolCallEntries(); track $index) {
<div style="display: flex; align-items: flex-start; gap: 8px; padding: 6px 0; font-size: 0.8rem; border-bottom: 1px solid #e5e7eb;">
<span style="flex-shrink: 0; font-size: 1rem; line-height: 1.2;">
{{ entry.name === 'read_file' ? '📖' : '✏️' }}
</span>
<div style="min-width: 0;">
<div style="font-weight: 500; color: #1a1a2e; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
{{ getFilePath(entry.args) }}
</div>
<div style="color: #8b8fa3; font-size: 0.75rem; margin-top: 2px;">
{{ entry.name === 'read_file' ? 'read' : 'write' }}
{{ entry.result ? ' · done' : ' · running…' }}
</div>
</div>
</div>
}
@empty {
<p style="color: #8b8fa3; font-size: 0.8rem;">Ask the agent to read or write a file.</p>
}
</ng-template>
</cp-chat>
`,
})
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 }] });
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
*/
export const environment = {
production: true,
langGraphApiUrl: 'http://localhost:4311/api',
langGraphApiUrl: 'https://filesystem-2330285f57625bff8654bc026f70a6ae.us.langgraph.app',
streamingAssistantId: 'filesystem',
};
21 changes: 19 additions & 2 deletions cockpit/deep-agents/filesystem/angular/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,24 @@
{
"extends": "../../../../tsconfig.base.json",
"compilerOptions": {
"module": "preserve"
"noPropertyAccessFromIndexSignature": false,
"experimentalDecorators": true,
"module": "preserve",
"emitDeclarationOnly": false,
"composite": false,
"lib": ["es2022", "dom"],
"skipLibCheck": true,
"strict": false
},
"include": ["src/**/*.ts"]
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": false,
"strictInputAccessModifiers": false,
"strictTemplates": false
},
"files": [],
"include": [],
"references": [
{ "path": "./tsconfig.app.json" }
]
}
4 changes: 2 additions & 2 deletions cockpit/deep-agents/filesystem/python/langgraph.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
"graphs": {
"filesystem": "./src/graph.py:graph"
},
"dependencies": ["./pyproject.toml"],
"env": ".env"
"dependencies": ["."],
"python_version": "3.12"
}
Loading
Loading