- 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
11 KiB
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:
<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:
usePanelChannel<AutomationSelection>('automation:selected-capability', null)
AutomationSelection (defined in AutomationRightPanel.tsx):
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 vianewComponentMap[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, opensTaskRunnerModal - Edit - toggles
selection.editingto 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:
- Name + description form
POST {endpoint}with{ name }- 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 viausePiChat()+EmbeddableChat. InjectspromptFrontmatterprefix before every message. ForisNew === true, uses rich creation guides (buildTaskCreationPrefix,buildSkillCreationPrefix,buildToolCreationPrefix).FrontmatterBlock- Collapsible YAML frontmatter display.CapabilityDetailtype -{ dirName, name, description, scope, body, rawFrontmatter, filePath, chatSessionId }.CapabilityPage- Legacy standalone two-panel page (used by/skills,/tasks,/processesroutes, 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.tssrc/servers/api/tools/tools.tssrc/servers/api/tasks/tasks.ts(also parsestrigger: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 appglobal: shared data directory, writable by Super Adminuser: 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
---
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
---
name: skill-name
description: When to use this skill.
---
(Knowledge base / reference documentation in markdown)
TOOL.md
---
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
- User clicks "Run" on a task in
CapabilityDetailView parseInputs()extractsinputs:from YAML frontmatter- If inputs exist: shows input form dialog; if none: runs immediately
- Opens
TaskRunnerModalwith prompt:"Read the task instructions at {filePath} and execute them"(+ input values if any) - Modal connects to Pi agent WebSocket via
usePiChat() - Agent reads the
TASK.md, resolves skill/tool references, executes in the user's sandboxed container task-logger.ts(createTaskLog->appendToLog->finalizeLog) writes execution log toDATA_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.mp3files shows this tasktype: 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()andsyncSeedTools()copy/update native seeds to global based onversion:in frontmattersyncSeedTasks()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.