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 16bffd58d..49a4c818a 100644 --- a/cockpit/deep-agents/filesystem/angular/src/app/filesystem.component.ts +++ b/cockpit/deep-agents/filesystem/angular/src/app/filesystem.component.ts @@ -1,58 +1,31 @@ +// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 import { Component, computed } from '@angular/core'; -import { LegacyChatComponent } from '@cacheplane/chat'; +import { ChatDebugComponent } 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: [LegacyChatComponent], + imports: [ChatDebugComponent], 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โ€ฆ' }} -
+
+ + @if (fileOps().length > 0) { +
- } - @empty { -

Ask the agent to read or write a file.

- } - - + } + + } +
`, }) export class FilesystemComponent { @@ -61,29 +34,18 @@ export class FilesystemComponent { 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 }); + protected readonly fileOps = computed(() => { + const messages = this.stream.messages(); + const ops: { name: string; path: string }[] = []; + for (const msg of messages) { + if ('tool_calls' in msg && Array.isArray((msg as any).tool_calls)) { + for (const tc of (msg as any).tool_calls) { + if (tc.name === 'read_file' || tc.name === 'write_file') { + ops.push({ name: tc.name, path: tc.args?.path ?? '' }); + } } } } - return calls; + return ops; }); - - 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/memory/angular/src/app/memory.component.ts b/cockpit/deep-agents/memory/angular/src/app/memory.component.ts index c6c60fdf9..150ad8a86 100644 --- a/cockpit/deep-agents/memory/angular/src/app/memory.component.ts +++ b/cockpit/deep-agents/memory/angular/src/app/memory.component.ts @@ -1,43 +1,30 @@ +// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 import { Component, computed } from '@angular/core'; -import { LegacyChatComponent } from '@cacheplane/chat'; +import { ChatDebugComponent } 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: [LegacyChatComponent], + imports: [ChatDebugComponent], 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.

- } -
-
+
+ + @if (memoryEntries().length > 0) { + + } +
`, }) export class MemoryComponent { @@ -46,12 +33,10 @@ export class MemoryComponent { assistantId: environment.streamingAssistantId, }); - memoryEntries = computed(() => { - const val = this.stream.value() as { agent_memory?: Record } | undefined; - return Object.entries(val?.agent_memory ?? {}); + protected readonly memoryEntries = computed(() => { + const val = this.stream.value() as Record; + const mem = val?.['agent_memory']; + if (!mem || typeof mem !== 'object') return []; + return Object.entries(mem as Record); }); - - send(text: string): void { - this.stream.submit({ messages: [{ role: 'human', content: text }] }); - } } 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 6394149e9..bd64e74a7 100644 --- a/cockpit/deep-agents/planning/angular/src/app/planning.component.ts +++ b/cockpit/deep-agents/planning/angular/src/app/planning.component.ts @@ -1,51 +1,36 @@ +// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 import { Component, computed } from '@angular/core'; -import { LegacyChatComponent } from '@cacheplane/chat'; +import { ChatDebugComponent } 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: [LegacyChatComponent], + imports: [ChatDebugComponent], template: ` - - -

Task Plan

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

Ask a complex question to see the plan.

- } -
-
+
+ + @if (planSteps().length > 0) { + + } +
`, }) export class PlanningComponent { @@ -54,12 +39,9 @@ export class PlanningComponent { assistantId: environment.streamingAssistantId, }); - planSteps = computed(() => { - const val = this.stream.value() as { plan?: PlanStep[] } | undefined; - return val?.plan ?? []; + protected readonly planSteps = computed(() => { + const val = this.stream.value() as Record; + const plan = val?.['plan']; + return Array.isArray(plan) ? plan as { title: string; status: string }[] : []; }); - - send(text: string): void { - this.stream.submit({ messages: [{ role: 'human', content: text }] }); - } } 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 ab8e8ab75..ad222836f 100644 --- a/cockpit/deep-agents/sandboxes/angular/src/app/sandboxes.component.ts +++ b/cockpit/deep-agents/sandboxes/angular/src/app/sandboxes.component.ts @@ -1,59 +1,43 @@ +// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 import { Component, computed } from '@angular/core'; -import { LegacyChatComponent } from '@cacheplane/chat'; +import { ChatDebugComponent } 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: [LegacyChatComponent], + imports: [ChatDebugComponent], template: ` - - -

Execution Logs

- @for (log of executionLogs(); track $index) { -
-
- - exit {{ log.exitStatus }} - +
+ + @if (execLogs().length > 0) { +
- } - @empty { -

Ask the agent to write and run Python code.

- } - - + } + + } +
`, }) export class SandboxesComponent { @@ -62,35 +46,28 @@ export class SandboxesComponent { 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, - }); + protected readonly execLogs = computed(() => { + const messages = this.stream.messages(); + const logs: { code: string; stdout: string; exitStatus: number }[] = []; + for (const msg of messages) { + if ('tool_calls' in msg && Array.isArray((msg as any).tool_calls)) { + for (const tc of (msg as any).tool_calls) { + if (tc.name === 'run_code') { + const resultIdx = messages.indexOf(msg) + 1; + const resultMsg = messages[resultIdx]; + let stdout = '', exitStatus = 0; + if (resultMsg && typeof resultMsg.content === 'string') { + try { + const parsed = JSON.parse(resultMsg.content); + stdout = parsed.stdout ?? ''; + exitStatus = parsed.exit_status ?? 0; + } catch { /* ignore */ } } + logs.push({ code: tc.args?.code ?? '', stdout, exitStatus }); } } } } return logs; }); - - send(text: string): void { - this.stream.submit({ messages: [{ role: 'human', content: text }] }); - } } 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 718ce244f..5853f5364 100644 --- a/cockpit/deep-agents/skills/angular/src/app/skills.component.ts +++ b/cockpit/deep-agents/skills/angular/src/app/skills.component.ts @@ -1,66 +1,37 @@ +// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 import { Component, computed } from '@angular/core'; -import { LegacyChatComponent } from '@cacheplane/chat'; +import { ChatDebugComponent } from '@cacheplane/chat'; import { streamResource } from '@cacheplane/stream-resource'; import { environment } from '../environments/environment'; -interface SkillInvocation { - skillName: string; - args: string; - result?: string; -} +const SKILL_ICONS: Record = { + calculator: '๐Ÿงฎ', + word_count: '๐Ÿ”ข', + summarize: '๐Ÿ“', +}; -/** - * 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: [LegacyChatComponent], + imports: [ChatDebugComponent], template: ` - - -

Skill Invocations

- @for (inv of skillInvocations(); track $index) { -
-
- - {{ inv.skillName }} - - @if (inv.result) { - done - } @else { - runningโ€ฆ - } -
-
- {{ inv.args }} +
+ + @if (skillInvocations().length > 0) { +
- } - @empty { -

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

- } - - + } + + } +
`, }) export class SkillsComponent { @@ -69,24 +40,18 @@ export class SkillsComponent { 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, - }); + protected readonly skillInvocations = computed(() => { + const messages = this.stream.messages(); + const invocations: { name: string; icon: string }[] = []; + for (const msg of messages) { + if ('tool_calls' in msg && Array.isArray((msg as any).tool_calls)) { + for (const tc of (msg as any).tool_calls) { + if (tc.name === 'calculator' || tc.name === 'word_count' || tc.name === 'summarize') { + invocations.push({ name: tc.name, icon: SKILL_ICONS[tc.name] ?? '๐Ÿ”ง' }); + } } } } return invocations; }); - - send(text: string): void { - this.stream.submit({ messages: [{ role: 'human', content: text }] }); - } } 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 1868c0a6a..ec19c4aa1 100644 --- a/cockpit/deep-agents/subagents/angular/src/app/subagents.component.ts +++ b/cockpit/deep-agents/subagents/angular/src/app/subagents.component.ts @@ -1,56 +1,31 @@ +// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 import { Component, computed } from '@angular/core'; -import { LegacyChatComponent } from '@cacheplane/chat'; +import { ChatDebugComponent } 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: [LegacyChatComponent], + imports: [ChatDebugComponent], template: ` - - -

Subagents

- @for (entry of subagentEntries(); track entry[0]) { -
-
- - {{ entry[1].status() }} - - - {{ entry[0].slice(0, 8) }}โ€ฆ - +
+ + @if (delegations().length > 0) { +
- } - @empty { -

Ask a question to see subagent activity.

- } - - + } + + } +
`, }) export class SubagentsComponent { @@ -59,9 +34,16 @@ export class SubagentsComponent { assistantId: environment.streamingAssistantId, }); - subagentEntries = computed(() => Array.from(this.stream.subagents().entries())); - - send(text: string): void { - this.stream.submit({ messages: [{ role: 'human', content: text }] }); - } + protected readonly delegations = computed(() => { + const messages = this.stream.messages(); + const entries: { name: string }[] = []; + for (const msg of messages) { + if ('tool_calls' in msg && Array.isArray((msg as any).tool_calls)) { + for (const tc of (msg as any).tool_calls) { + entries.push({ name: tc.name }); + } + } + } + return entries; + }); } diff --git a/cockpit/langgraph/persistence/angular/src/app/persistence.component.ts b/cockpit/langgraph/persistence/angular/src/app/persistence.component.ts index aa7f77f42..c83ae851b 100644 --- a/cockpit/langgraph/persistence/angular/src/app/persistence.component.ts +++ b/cockpit/langgraph/persistence/angular/src/app/persistence.component.ts @@ -1,88 +1,43 @@ -import { Component } from '@angular/core'; -import { LegacyChatComponent } from '@cacheplane/chat'; +// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 +import { Component, signal } from '@angular/core'; +import { ChatComponent, type Thread } 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: [LegacyChatComponent], + imports: [ChatComponent], 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 threads = signal([]); + protected readonly activeThreadId = signal(''); + 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); - }, + threadId: null, + onThreadId: (id: string) => this.trackThread(id), }); - threadIds: string[] = []; - currentThreadId = ''; - - /** - * Submit a message to the current thread. - */ - send(text: string): void { - this.stream.submit({ messages: [{ role: 'human', content: text }] }); + private trackThread(id: string): void { + this.activeThreadId.set(id); + if (!this.threads().find(t => t.id === id)) { + this.threads.update(list => [...list, { id }]); + } } - /** - * Switch to an existing thread, loading its full message history. - */ - selectThread(id: string): void { - this.currentThreadId = id; + protected onThreadSelected(id: string): void { + this.activeThreadId.set(id); this.stream.switchThread(id); } - - /** - * Start a new conversation thread. - */ - newThread(): void { - this.currentThreadId = ''; - this.stream.switchThread(null); - } } diff --git a/docs/superpowers/specs/2026-04-05-cockpit-examples-tier1-customization.md b/docs/superpowers/specs/2026-04-05-cockpit-examples-tier1-customization.md new file mode 100644 index 000000000..c296007c4 --- /dev/null +++ b/docs/superpowers/specs/2026-04-05-cockpit-examples-tier1-customization.md @@ -0,0 +1,249 @@ +# Cockpit Examples Tier 1 Customization + +**Date:** 2026-04-05 +**Status:** Draft +**Scope:** Customize 8 cockpit Angular examples using existing @cacheplane/chat components. No new library features needed. + +--- + +## Overview + +Tier 1 covers examples that work with existing `@cacheplane/chat` and `@cacheplane/stream-resource` APIs. Each gets a capability-specific component that goes beyond the generic `` to showcase what makes that capability unique. + +## Tier Breakdown (for reference) + +- **Tier 1 (this spec):** persistence, deployment-runtime, + 6 deep-agents (planning, filesystem, subagents, memory, skills, sandboxes) +- **Tier 2 (next):** memory (LG), subgraphs, interrupts โ€” need custom sidebar content via template overrides +- **Tier 3 (later):** time-travel, durable-execution โ€” need new library features + +## Example Specifications + +### 1. Persistence (`cockpit/langgraph/persistence/angular`) + +**What it demonstrates:** Thread-based conversation persistence. Resume conversations by thread ID. + +**Component:** Uses `` with thread management inputs. + +```typescript +@Component({ + selector: 'app-persistence', + standalone: true, + imports: [ChatComponent], + template: ` + + `, +}) +export class PersistenceComponent { + protected readonly stream = streamResource({ + apiUrl: environment.langGraphApiUrl, + assistantId: environment.streamingAssistantId, + threadId: null, // auto-create + onThreadId: (id) => this.trackThread(id), + }); + + protected readonly threads = signal([]); + protected readonly activeThreadId = signal(''); + + private trackThread(id: string): void { + this.activeThreadId.set(id); + // Add to thread list if new + if (!this.threads().find(t => t.id === id)) { + this.threads.update(list => [...list, { id }]); + } + } + + protected onThreadSelected(id: string): void { + this.activeThreadId.set(id); + this.stream.switchThread(id); + } +} +``` + +**Key signals:** `stream.switchThread()`, `onThreadId` callback, `[threads]` input on ``. + +--- + +### 2. Deployment Runtime (`cockpit/langgraph/deployment-runtime/angular`) + +**What it demonstrates:** Production deployment config โ€” different assistant ID, environment-based URL. + +**Component:** Uses `` with no extras. The differentiation is in `environment.ts` pointing to a deployed endpoint. + +```typescript +@Component({ + selector: 'app-deployment-runtime', + standalone: true, + imports: [ChatComponent], + template: ``, +}) +export class DeploymentRuntimeComponent { + protected readonly stream = streamResource({ + apiUrl: environment.langGraphApiUrl, + assistantId: environment.deploymentRuntimeAssistantId, + }); +} +``` + +Minimal โ€” same as streaming but with deployment-specific environment config. + +--- + +### 3. Planning (`cockpit/deep-agents/planning/angular`) + +**What it demonstrates:** Task decomposition โ€” agent creates a plan with steps, then executes them. + +**Component:** Uses `` plus a computed signal deriving plan steps from `stream.value()`. + +```typescript +@Component({ + selector: 'app-planning', + standalone: true, + imports: [ChatDebugComponent], + template: ` +
+ + @if (planSteps().length > 0) { + + } +
+ `, +}) +export class PlanningComponent { + protected readonly stream = streamResource({ + apiUrl: environment.langGraphApiUrl, + assistantId: environment.streamingAssistantId, + }); + + protected readonly planSteps = computed(() => { + const val = this.stream.value() as Record; + const plan = val?.['plan']; + return Array.isArray(plan) ? plan : []; + }); +} +``` + +--- + +### 4. Filesystem (`cockpit/deep-agents/filesystem/angular`) + +**What it demonstrates:** File read/write tool calls. + +**Component:** `` plus computed signal extracting tool calls from messages. + +```typescript +// Same pattern as planning but derives file operations from stream.messages() +protected readonly fileOps = computed(() => { + const messages = this.stream.messages(); + const ops: { name: string; path: string; status: string }[] = []; + for (const msg of messages) { + if ('tool_calls' in msg && Array.isArray((msg as any).tool_calls)) { + for (const tc of (msg as any).tool_calls) { + if (tc.name === 'read_file' || tc.name === 'write_file') { + ops.push({ name: tc.name, path: tc.args?.path ?? '', status: 'done' }); + } + } + } + } + return ops; +}); +``` + +Sidebar shows file operation log with read/write icons. + +--- + +### 5. Subagents (`cockpit/deep-agents/subagents/angular`) + +**What it demonstrates:** Orchestrator delegating to specialist subagents. + +**Component:** `` plus computed signal from `stream.subagents()` or tool calls. + +```typescript +protected readonly subagentEntries = computed(() => { + const messages = this.stream.messages(); + const entries: { name: string; status: string }[] = []; + for (const msg of messages) { + if ('tool_calls' in msg && Array.isArray((msg as any).tool_calls)) { + for (const tc of (msg as any).tool_calls) { + if (['research_agent', 'analysis_agent', 'summary_agent'].includes(tc.name)) { + entries.push({ name: tc.name, status: 'done' }); + } + } + } + } + return entries; +}); +``` + +Sidebar shows subagent delegation cards. + +--- + +### 6. Memory (`cockpit/deep-agents/memory/angular`) + +**What it demonstrates:** Persistent fact extraction across turns. + +**Component:** `` plus computed signal from `stream.value().agent_memory`. + +```typescript +protected readonly memoryEntries = computed(() => { + const val = this.stream.value() as Record; + const mem = val?.['agent_memory']; + if (!mem || typeof mem !== 'object') return []; + return Object.entries(mem as Record); +}); +``` + +Sidebar shows learned facts as key-value pairs. + +--- + +### 7. Skills (`cockpit/deep-agents/skills/angular`) + +**What it demonstrates:** Multi-skill tool selection (calculator, word_count, summarize). + +**Component:** `` plus computed signal extracting skill invocations from messages. + +Same tool-call extraction pattern as filesystem, filtered for calculator/word_count/summarize. + +--- + +### 8. Sandboxes (`cockpit/deep-agents/sandboxes/angular`) + +**What it demonstrates:** Code execution via `run_code` tool. + +**Component:** `` plus computed signal extracting code execution results. + +Sidebar shows execution logs with exit status badges and stdout output. + +--- + +## Common Pattern + +All 6 deep-agents examples follow the same structure: +1. `` for the main chat + debug panel +2. An optional `