fix(pi): add snap node compatibility diagnostics and documentation

- Added detailed error logging to detect snap node compatibility issues
- When Pi process exits with code 1, log helpful diagnostic info including node path
- Add hint to check for snap node and reinstall via apt/nvm
- Create SNAP_NODE_COMPATIBILITY.md with full troubleshooting guide
- Document root cause: snap node has file descriptor incompatibility with Bun.spawn stdin pipes
- Provide clear installation instructions for NodeSource and nvm alternatives
This commit is contained in:
2026-03-04 01:45:36 +00:00
parent 72d1341cbc
commit ef13f96d36
34 changed files with 2394 additions and 1466 deletions
@@ -0,0 +1,276 @@
# Automation
## Overview
The `/automation` route is the hub for managing AI agent capabilities: skills, tools, tasks, and processes. Each capability is a markdown file (with YAML frontmatter) stored on the filesystem. There is no relational database for automation data.
The page uses a resizable `WorkspaceView` layout with three panels: a sidebar to pick categories, a right panel for browsing/viewing, and an optional bottom-right chat panel for AI-assisted editing.
```
+------------------+------------------------------------------+
| Sidebar (20%) | Right Panel (80%) |
| | - CapabilityList (browse) |
| 8 categories | - CapabilityDetailView (selected item) |
| | - New* form (creating) |
| +------------------------------------------+
| | AutomationEditChat (when editing, 50%) |
+------------------+------------------------------------------+
```
## Route & Entry Point
Defined in `App.tsx`:
```tsx
<Route path="/automation" element={<Dashboard.Automation />} />
<Route path="/new-automation" element={<Dashboard.NewAutomation />} /> // stub, not implemented
```
Entry component: `index.tsx` (`Automation`). Manages the `WorkspaceView` layout and dynamically adds/removes the chat panel based on `selection.editing`.
## Capability Categories
| Category | Kind | Backend Route | Status |
|----------|------|---------------|--------|
| Skills | `Skill` | `GET/POST/DELETE /api/skills` | Working |
| Tools | `Tool` | `GET/POST/DELETE /api/tools` | Working |
| Tasks | `Task` | `GET/POST/DELETE /api/tasks` | Working |
| Processes | `Process` | `GET/POST/DELETE /api/processes` | Working |
| Pipelines | `Pipeline` | None | Placeholder (404) |
| Workflows | `Workflow` | None | Placeholder (404) |
| Crons | `Cron` | None | Placeholder (404) |
| Services | `Service` | None | Placeholder (404) |
Defined as `capabilityItems` in `AutomationSidebar.tsx`.
## State Management
All cross-panel communication uses a single shared channel:
```ts
usePanelChannel<AutomationSelection>('automation:selected-capability', null)
```
`AutomationSelection` (defined in `AutomationRightPanel.tsx`):
```ts
type AutomationSelection = {
kind: string; // 'Skill' | 'Task' | 'Tool' | 'Process' | etc.
endpoint: string; // '/skills' | '/tasks' | etc.
queryKey: string; // React Query cache key
dirName: string; // '' = list view, non-empty = detail view
isNew?: boolean; // just created, AI chat opens in creation mode
editing?: boolean; // chat panel is open
creating?: boolean; // New* form is shown
description?: string; // passed to AI as initial context
} | null;
```
Layout state persisted via `useDashboardState('screens/automation')`.
## Components
### `AutomationSidebar.tsx`
Left nav with the 8 category buttons. Clicking sets selection to `{ kind, endpoint, queryKey, dirName: '' }` (list view).
### `AutomationRightPanel.tsx`
Routes to one of three views based on selection state:
- `creating === true` -> `New*` form (mapped via `newComponentMap[kind]`)
- `dirName === ''` -> `CapabilityList` (browse items)
- `dirName !== ''` -> `CapabilityDetailView` (detail for specific item)
### `CapabilityList.tsx`
Fetches items from `GET {endpoint}`, renders filterable list. Has `+` button to set `creating: true`.
### `CapabilityDetailView.tsx`
Shows a selected capability's detail (frontmatter + markdown body). Header actions:
- **Back arrow** - returns to list view
- **Run** (tasks only) - parses `inputs:` from frontmatter YAML, opens `TaskRunnerModal`
- **Edit** - toggles `selection.editing` to open/close chat panel
- **Delete** - confirmation dialog, then `DELETE {endpoint}/{dirName}`
Task input parsing (`parseInputs`) supports: `string`, `number`, `boolean`, `select` types.
### `AutomationEditChat.tsx`
Bottom chat panel for AI-assisted editing. Uses `CapabilityChat` from `CapabilityPage.tsx`. Features:
- Delete chat history button (`DELETE {endpoint}/{dirName}/chat`)
- Close button (sets `editing: false`)
- On AI response end, invalidates both individual and list query caches
### `New*.tsx` (8 components)
`NewTask`, `NewSkill`, `NewTool`, `NewProcess`, `NewPipeline`, `NewCron`, `NewService`, `NewWorkflow`. All structurally identical:
1. Name + description form
2. `POST {endpoint}` with `{ name }`
3. On success: transitions to detail view with `isNew: true, editing: true` (opens AI chat in creation mode)
## Shared Components (`CapabilityPage.tsx`)
Located at `../CapabilityPage.tsx`. Exports used by automation:
- **`CapabilityChat`** - AI chat wired to Pi agent via `usePiChat()` + `EmbeddableChat`. Injects `promptFrontmatter` prefix before every message. For `isNew === true`, uses rich creation guides (`buildTaskCreationPrefix`, `buildSkillCreationPrefix`, `buildToolCreationPrefix`).
- **`FrontmatterBlock`** - Collapsible YAML frontmatter display.
- **`CapabilityDetail`** type - `{ dirName, name, description, scope, body, rawFrontmatter, filePath, chatSessionId }`.
- **`CapabilityPage`** - Legacy standalone two-panel page (used by `/skills`, `/tasks`, `/processes` routes, not by `/automation`).
## Backend
All four working routers (`skills`, `tools`, `tasks`, `processes`) follow the exact same pattern. Registered on the protected router in `hono.ts`.
### API Endpoints (per capability type)
| Method | Path | Description |
|--------|------|-------------|
| `GET` | `/{type}` | List all items (native + global + user merged) |
| `GET` | `/{type}/:name` | Get detail: frontmatter, body, rawYaml, filePath, chatSessionId |
| `POST` | `/{type}` | Create new item (dir + stub `.md`) |
| `DELETE` | `/{type}/:name` | Delete item directory |
| `GET` | `/{type}/:name/chat` | Get saved chat messages |
| `PUT` | `/{type}/:name/chat` | Save chat messages + session meta |
| `DELETE` | `/{type}/:name/chat` | Delete chat directory |
Route files:
- `src/servers/api/skills/skills.ts`
- `src/servers/api/tools/tools.ts`
- `src/servers/api/tasks/tasks.ts` (also parses `trigger:` block from frontmatter)
- `src/servers/api/processes/processes.ts`
### Task Logs
| Method | Path | Description |
|--------|------|-------------|
| `GET` | `/task-logs` | List all log metadata (newest first) |
| `GET` | `/task-logs/:filename` | Get full log with messages |
Route file: `src/servers/api/task-logs/task-logs.ts`
## Storage Model
Everything is stored on the filesystem. No database tables.
```
DATA_PATH/
skills/ <- global scope
{skill-name}/
SKILL.md <- frontmatter + markdown body
chat/
meta.json <- { id: sessionId }
messages.json <- ChatMessage[]
tasks/ <- global scope
{task-name}/TASK.md
tools/ <- global scope
{tool-name}/TOOL.md
processes/ <- global scope
{process-name}/PROCESS.md
{user-email}/ <- user scope
skills/
tasks/
tools/
processes/
logs/tasks/ <- task execution logs
{timestamp}-{dirName}.json
SEED_PATH/ <- native scope (read-only, shipped with app)
skills/ (sharp, whisper-cpp, google-mail-api, ffmpeg, mutagen, fizzy-cli, mlxaudio)
tasks/ (convert-to-mp3, sync-gmail-inbox, tiktok-trends, transcribe-audio-file, ...)
tools/ (apify, browser, email-db, ffmpeg, gmail, ocr, web-fetch, web-search, ...)
```
Path helpers: `src/servers/data-path.ts` (`getNativeSkillsDir`, `getGlobalSkillsDir`, `getUserSkillsDir`, etc.)
### Scope & Permissions
Three tiers with override resolution: **user > global > native**.
- `native`: seed directory, read-only, shipped with the app
- `global`: shared data directory, writable by Super Admin
- `user`: per-user directory, writable by the owning user
Regular users (`Member`) can only create/delete `user`-scoped items. `Super Admin` can also create/delete `global` items.
## Markdown File Formats
### TASK.md
```yaml
---
name: Task Name
description: What the task does.
version: 1
author: pastilhas
tags: [tag1, tag2]
skills: [skill-name] # optional, skills the agent can consult
tools: [tool_name] # optional, tools the agent can call
trigger: # optional, when omitted: only runnable from Automation page
- type: file
extensions: [mp3, flac]
- type: directory
inputs: # optional, parameters the user fills in before running
- name: param_name
type: string # string | number | boolean | select
required: true
default: some value
---
(Agent instructions in markdown)
```
### SKILL.md
```yaml
---
name: skill-name
description: When to use this skill.
---
(Knowledge base / reference documentation in markdown)
```
### TOOL.md
```yaml
---
name: tool_name
label: Tool Display Name
description: What the tool does.
language: typescript # typescript | bash | python
inputs:
param_name:
type: string # string | number | boolean | enum | object
description: What this parameter is for.
optional: true
sensitive: true
---
(Usage notes, output format, examples in markdown)
```
### PROCESS.md
Same structure as SKILL.md (name + description frontmatter, markdown body).
## Task Execution Flow
1. User clicks "Run" on a task in `CapabilityDetailView`
2. `parseInputs()` extracts `inputs:` from YAML frontmatter
3. If inputs exist: shows input form dialog; if none: runs immediately
4. Opens `TaskRunnerModal` with prompt: `"Read the task instructions at {filePath} and execute them"` (+ input values if any)
5. Modal connects to Pi agent WebSocket via `usePiChat()`
6. Agent reads the `TASK.md`, resolves skill/tool references, executes in the user's sandboxed container
7. `task-logger.ts` (`createTaskLog` -> `appendToLog` -> `finalizeLog`) writes execution log to `DATA_PATH/{email}/logs/tasks/`
## Task Trigger Integration
Tasks with `trigger:` in their frontmatter appear in the file browser context menu:
- `type: file` + `extensions: [mp3]` -> right-click on `.mp3` files shows this task
- `type: directory` -> right-click on directories shows this task
Implemented via `useTasks` hook in the `officerdev` workspace, which provides `getMatchingTasks(fileName, entryType)`.
## Seed Sync (Startup)
On server startup (`bootstrap.ts`):
- `syncSeedSkills()` and `syncSeedTools()` copy/update native seeds to global based on `version:` in frontmatter
- `syncSeedTasks()` exists in code but is **not called** in bootstrap
## Legacy Routes
Standalone pages still exist using the older `CapabilityPage` component:
- `/skills` -> `CapabilityPage` (two-panel, list + detail/chat)
- `/tasks` -> `CapabilityPage`
- `/processes` -> `CapabilityPage`
These share the same API endpoints but use the single-component layout instead of the workspace view.