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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ jobs:
node-version: 22
cache: npm
- run: npm ci
- run: npx nx run-many -t smoke --projects=cockpit-ag-ui-interrupts-python,cockpit-ag-ui-streaming-python,cockpit-deep-agents-planning-python,cockpit-deep-agents-filesystem-python,cockpit-deep-agents-subagents-python,cockpit-deep-agents-memory-python,cockpit-deep-agents-skills-python,cockpit-deep-agents-sandboxes-python,cockpit-langgraph-persistence-python,cockpit-langgraph-durable-execution-python,cockpit-langgraph-streaming-python,cockpit-langgraph-interrupts-python,cockpit-langgraph-memory-python,cockpit-langgraph-subgraphs-python,cockpit-langgraph-time-travel-python,cockpit-langgraph-deployment-runtime-python,cockpit-chat-messages-python,cockpit-render-spec-rendering-python,cockpit-runtimes-microsoft-agent-framework-python,cockpit-runtimes-aws-strands-python --skip-nx-cache
- run: npx nx run-many -t smoke --projects=cockpit-ag-ui-interrupts-python,cockpit-ag-ui-streaming-python,cockpit-deep-agents-planning-python,cockpit-deep-agents-filesystem-python,cockpit-deep-agents-subagents-python,cockpit-deep-agents-memory-python,cockpit-deep-agents-skills-python,cockpit-langgraph-persistence-python,cockpit-langgraph-durable-execution-python,cockpit-langgraph-streaming-python,cockpit-langgraph-interrupts-python,cockpit-langgraph-memory-python,cockpit-langgraph-subgraphs-python,cockpit-langgraph-time-travel-python,cockpit-langgraph-deployment-runtime-python,cockpit-chat-messages-python,cockpit-render-spec-rendering-python,cockpit-runtimes-microsoft-agent-framework-python,cockpit-runtimes-aws-strands-python --skip-nx-cache

cockpit-deploy-smoke:
name: Cockpit — deploy smoke dry-run
Expand Down
1 change: 0 additions & 1 deletion apps/cockpit/e2e/production-smoke.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ const CHAT_CAPABILITIES = [
'deep-agents/subagents',
'deep-agents/memory',
'deep-agents/skills',
'deep-agents/sandboxes',
'chat/tool-calls',
'chat/subagents',
'chat/threads',
Expand Down
7 changes: 0 additions & 7 deletions apps/cockpit/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -156,13 +156,6 @@
"cwd": "."
}
},
"serve-sandboxes": {
"executor": "nx:run-commands",
"options": {
"command": "npx tsx apps/cockpit/scripts/serve-example.ts --capability=da-sandboxes",
"cwd": "."
}
},
"serve-all": {
"executor": "nx:run-commands",
"options": {
Expand Down
1 change: 0 additions & 1 deletion apps/cockpit/scripts/capability-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ export const capabilities: readonly Capability[] = [
{ id: 'da-subagents', product: 'deep-agents', topic: 'subagents', angularProject: 'cockpit-deep-agents-subagents-angular', port: 4312, pythonPort: 5312, pythonDir: 'cockpit/deep-agents/subagents/python', graphName: 'subagents' },
{ id: 'da-memory', product: 'deep-agents', topic: 'memory', angularProject: 'cockpit-deep-agents-memory-angular', port: 4313, pythonPort: 5313, pythonDir: 'cockpit/deep-agents/memory/python', graphName: 'da-memory' },
{ id: 'da-skills', product: 'deep-agents', topic: 'skills', angularProject: 'cockpit-deep-agents-skills-angular', port: 4314, pythonPort: 5314, pythonDir: 'cockpit/deep-agents/skills/python', graphName: 'da-skills' },
{ id: 'da-sandboxes', product: 'deep-agents', topic: 'sandboxes', angularProject: 'cockpit-deep-agents-sandboxes-angular', port: 4315, pythonPort: 5315, pythonDir: 'cockpit/deep-agents/sandboxes/python', graphName: 'da-sandboxes' },
// Render capabilities
{ id: 'r-spec-rendering', product: 'render', topic: 'spec-rendering', angularProject: 'cockpit-render-spec-rendering-angular', port: 4401, pythonPort: 5401, pythonDir: 'cockpit/render/spec-rendering/python', graphName: 'r-spec-rendering' },
{ id: 'r-element-rendering', product: 'render', topic: 'element-rendering', angularProject: 'cockpit-render-element-rendering-angular', port: 4402, pythonPort: 5402, pythonDir: 'cockpit/render/element-rendering/python', graphName: 'r-element-rendering' },
Expand Down
2 changes: 0 additions & 2 deletions apps/cockpit/src/lib/route-resolution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import { deepAgentsPlanningPythonModule } from '../../../../cockpit/deep-agents/
import { deepAgentsFilesystemPythonModule } from '../../../../cockpit/deep-agents/filesystem/python/src/index';
import { deepAgentsSubagentsPythonModule } from '../../../../cockpit/deep-agents/subagents/python/src/index';
import { deepAgentsSkillsPythonModule } from '../../../../cockpit/deep-agents/skills/python/src/index';
import { deepAgentsSandboxesPythonModule } from '../../../../cockpit/deep-agents/sandboxes/python/src/index';
import { renderSpecRenderingPythonModule } from '../../../../cockpit/render/spec-rendering/python/src/index';
import { renderElementRenderingPythonModule } from '../../../../cockpit/render/element-rendering/python/src/index';
import { renderStateManagementPythonModule } from '../../../../cockpit/render/state-management/python/src/index';
Expand Down Expand Up @@ -133,7 +132,6 @@ export const capabilityModules: RegisteredCapabilityModule[] = [
deepAgentsFilesystemPythonModule,
deepAgentsSubagentsPythonModule,
deepAgentsSkillsPythonModule,
deepAgentsSandboxesPythonModule,
renderSpecRenderingPythonModule,
renderElementRenderingPythonModule,
renderStateManagementPythonModule,
Expand Down
3 changes: 0 additions & 3 deletions apps/cockpit/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,6 @@
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"],
"references": [
{
"path": "../../cockpit/deep-agents/sandboxes/python"
},
{
"path": "../../cockpit/deep-agents/skills/python"
},
Expand Down
39 changes: 35 additions & 4 deletions cockpit/deep-agents/filesystem/angular/e2e/da-filesystem.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,39 @@
// SPDX-License-Identifier: MIT
import { test, expect } from '@playwright/test';
import { submitAndWaitForResponse } from '@threadplane-internal/e2e-harness';
import {
clickInterruptActionAndWaitFinal,
sendPromptAndWaitForInterrupt,
} from '@threadplane-internal/e2e-harness';

test('da-filesystem: hello prompt produces assistant turn', async ({ page }) => {
const bubble = await submitAndWaitForResponse(page, 'Hello');
await expect(bubble).toBeVisible();
// Matches the welcome suggestion in filesystem.component.ts and the fixture's
// `userMessage` match in e2e/fixtures/da-filesystem.json.
const PROMPT =
'Work up a runway suitability note for KASE. Save your raw lookups to /notes/kase-data.md, then write the finished note to /reports/kase-runway.md.';

test('da-filesystem: the workspace fills in, the report write pauses, and approving lands it', async ({
page,
}) => {
await sendPromptAndWaitForInterrupt(page, PROMPT);

// `/notes/` is unrestricted, so that write completed and reached the tree as
// real state — this only renders because the agent runs on StateBackend and
// its files stream on `values.files`.
await expect(page.locator('[data-testid="file-row"][data-path="/notes/kase-data.md"]')).toBeVisible();
await expect(page.getByTestId('file-preview')).toContainText(/7,?820|8,?006|elevation/i);

// `/reports/**` carries a FilesystemPermission in interrupt mode, so the
// second write did NOT complete. The tree shows it as a pending ghost row
// read off the interrupt payload, and no such file exists in state yet.
const pendingRow = page.locator('[data-testid="file-row"][data-path="/reports/kase-runway.md"]');
await expect(pendingRow).toHaveAttribute('data-pending', 'true');
await expect(pendingRow).toContainText('awaiting approval');

// Two directories, because the ghost row groups under its own prefix.
await expect(page.locator('[data-testid="file-dir"]')).toHaveCount(2);

// Approving resumes with { decisions: [{ type: 'approve' }] }; the write then
// completes and the row stops being pending.
await clickInterruptActionAndWaitFinal(page, 'Accept');
await expect(pendingRow).not.toHaveAttribute('data-pending', 'true');
await expect(page.locator('[data-testid="file-row"]')).toHaveCount(2);
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,62 @@
"fixtures": [
{
"match": {
"userMessage": "Hello"
"userMessage": "Work up a runway suitability note for KASE",
"hasToolResult": true,
"turnIndex": 1
},
"response": {
"content": "Hello! How can I help you today? I can read or write files in the workspace\u2014tell me what you'd like to do (e.g., \"read file /path/to/file\" or \"write file /path/to/file with content ...\")."
"toolCalls": [
{
"name": "write_file",
"arguments": "{\"file_path\":\"/notes/kase-data.md\",\"content\":\"KASE Field Elevation: 7,820 ft\\nKASE Longest Runway: 8,006 ft\"}"
}
]
}
},
{
"match": {
"userMessage": "Work up a runway suitability note for KASE",
"hasToolResult": true,
"turnIndex": 2
},
"response": {
"toolCalls": [
{
"name": "write_file",
"arguments": "{\"file_path\":\"/reports/kase-runway.md\",\"content\":\"Runway Suitability Note: KASE (Aspen/Pitkin County)\\n\\n- Field elevation is 7,820 ft, which is considered high altitude and will impact aircraft performance (longer takeoff/landing distances required).\\n- The longest runway is 8,006 ft, which is adequate for many business jets and regional aircraft, but performance calculations are critical due to elevation and possible temperature effects.\\n- Operators should verify aircraft-specific requirements and consider density altitude, especially in warm weather or with heavy loads.\"}"
}
]
}
},
{
"match": {
"userMessage": "Work up a runway suitability note for KASE",
"hasToolResult": true,
"turnIndex": 3
},
"response": {
"content": "I saved the raw data lookups for KASE (field elevation and runway length) to /notes/kase-data.md. The finished runway suitability note is in /reports/kase-runway.md."
}
},
{
"match": {
"userMessage": "Work up a runway suitability note for KASE",
"hasToolResult": false,
"turnIndex": 0
},
"response": {
"toolCalls": [
{
"name": "lookup_field_elevation",
"arguments": "{\"airport\": \"KASE\"}"
},
{
"name": "lookup_runway_length",
"arguments": "{\"airport\": \"KASE\"}"
}
]
}
}
]
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
// SPDX-License-Identifier: MIT
//
// Live-model manual check. Not run by CI (playwright matches *.spec.ts only).
// npx nx run cockpit:serve-filesystem
// npx playwright test cockpit/deep-agents/filesystem/angular/e2e/manual
import { expect, test } from '@playwright/test';

test.describe('Deep Agents Filesystem Example', () => {
Expand All @@ -6,16 +11,20 @@ test.describe('Deep Agents Filesystem Example', () => {
await page.waitForSelector('app-filesystem', { state: 'attached' });
});

test('renders the chat interface with file operations sidebar', async ({ page }) => {
test('renders the chat interface with an empty workspace', async ({ page }) => {
await expect(page.locator('chat')).toBeVisible();
await expect(page.locator('textarea[name="messageText"]')).toBeVisible();
await expect(page.locator('text=No file operations yet')).toBeVisible();
await expect(page.locator('text=No files yet')).toBeVisible();
});

test('sends a message and receives a response', async ({ page }) => {
await page.fill('textarea[name="messageText"]', 'Read the file at workspace/hello.txt');
await page.click('button[type="submit"]');
await expect(page.locator('.chat-md').first()).toBeVisible({ timeout: 30000 });
await expect(page.locator('.chat-md').first()).not.toBeEmpty({ timeout: 30000 });
test('writes notes, pauses on the report, and lands it after approval', async ({ page }) => {
await page.getByRole('button', { name: 'Runway note for KASE' }).click();

await expect(page.locator('[data-testid="file-row"]').first()).toBeVisible({ timeout: 120000 });
await expect(page.locator('chat-interrupt-panel')).toBeVisible({ timeout: 120000 });
await expect(page.locator('text=awaiting approval')).toBeVisible();

await page.locator('chat-interrupt-panel').getByRole('button', { name: /^Accept$/ }).click();
await expect(page.locator('[data-testid="file-row"]')).toHaveCount(2, { timeout: 120000 });
});
});
4 changes: 2 additions & 2 deletions cockpit/deep-agents/filesystem/angular/prompts/filesystem.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Deep Agents Filesystem (Angular)

This capability demonstrates a deep agent that reads, writes, and navigates a sandboxed filesystem using the `@threadplane/chat` Angular component library. The `<chat-debug>` component surfaces every filesystem tool call — including path, arguments, and result — so developers can follow the agent's file operations step by step.
This capability renders the agent's workspace, not a log of its file operations. `StateBackend` puts every file the agent writes on the graph state under `files`, keyed by absolute path, so the Angular sidebar is a projection of the live workspace: an edit that rewrites an existing file shows as one changed file rather than two log entries.

Key components used: `<chat-debug>`. Each tool invocation (read_file, write_file, list_dir, etc.) appears as a collapsible trace node, giving full visibility into how the agent interacts with the filesystem without cluttering the end-user chat view.
A `FilesystemPermission` in `interrupt` mode covers `/reports/**`, so a write there pauses the run and `<chat-interrupt-panel>` from `@threadplane/chat` renders the approval. The pending path is read off the interrupt payload and shown in the tree as a ghost row before the file exists. Resuming takes `{ decisions: [{ type: 'approve' }] }`.
2 changes: 0 additions & 2 deletions cockpit/deep-agents/filesystem/angular/src/app/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import { ApplicationConfig } from '@angular/core';
import { provideAgent } from '@threadplane/langgraph';
import { provideChat } from '@threadplane/chat';
import { provideRender } from '@threadplane/render';
import { environment } from '../environments/environment';

export const appConfig: ApplicationConfig = {
Expand All @@ -12,6 +11,5 @@ export const appConfig: ApplicationConfig = {
assistantId: environment.streamingAssistantId,
}),
provideChat({}),
provideRender({}),
],
};
Loading
Loading