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
+28 -14
View File
@@ -1,6 +1,16 @@
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { getGlobalToolsDir, getUserToolsDir, getGlobalSkillsDir, getUserSkillsDir, getGlobalTasksDir, getUserTasksDir, getGlobalResourcesDir, getHomeDir, DATA_PATH } from '@@/data-path';
import {
getGlobalToolsDir,
getUserToolsDir,
getGlobalSkillsDir,
getUserSkillsDir,
getGlobalTasksDir,
getUserTasksDir,
getGlobalResourcesDir,
getHomeDir,
DATA_PATH,
} from '@@/data-path';
type HookEntry = { type: string; command: string };
type HookRule = { matcher?: Record<string, unknown>; hooks: HookEntry[] };
@@ -53,9 +63,15 @@ export function generateContainerContext(email: string): string {
const tasks = dedup([...scanDir(getGlobalTasksDir(), 'TASK.md'), ...scanDir(getUserTasksDir(email), 'TASK.md')]);
const resources = scanDir(getGlobalResourcesDir(), 'RESOURCE.md');
const content = `# Officer — Container Environment
const globalToolsDir = getGlobalToolsDir();
const userToolsDir = getUserToolsDir(email);
const globalSkillsDir = getGlobalSkillsDir();
const userSkillsDir = getUserSkillsDir(email);
const userDataDir = join(DATA_PATH, email);
This is a sandboxed development container managed by the Officer platform.
const content = `# Officer — User Environment
This is an isolated Linux user environment managed by the Officer platform.
## Directory Layout
@@ -64,14 +80,14 @@ This is a sandboxed development container managed by the Officer platform.
| \`~\` | User home directory (read-write) |
| \`~/Projects/\` | User projects |
| \`~/Downloads/\` | Downloaded files |
| \`/officer/tools/\` | Global tools (read-only) |
| \`/officer/user/tools/\` | User tools (read-only) |
| \`/officer/skills/\` | Reference skills (read-only) |
| \`/officer/data/\` | User data (emails.db, attachments, etc.) |
| \`${globalToolsDir}/\` | Global tools |
| \`${userToolsDir}/\` | User tools |
| \`${globalSkillsDir}/\` | Reference skills |
| \`${userDataDir}/\` | User data (emails.db, attachments, etc.) |
## Available Tools
Tools are callable capabilities used by the Officer AI agent (Pi). Each tool has a \`TOOL.md\` with documentation and an \`index.ts\` that exports an \`execute()\` function. Read individual tool docs at \`/officer/tools/<name>/TOOL.md\` or \`/officer/user/tools/<name>/TOOL.md\`.
Tools are callable capabilities used by the Officer AI agent (Pi). Each tool has a \`TOOL.md\` with documentation and an \`index.ts\` that exports an \`execute()\` function. Read individual tool docs at \`${globalToolsDir}/<name>/TOOL.md\` or \`${userToolsDir}/<name>/TOOL.md\`.
${formatList(tools)}
## Available Skills
@@ -91,7 +107,7 @@ Resources are external service integrations (TTS, STT, OCR, etc.) configured in
${formatList(resources)}
## Creating New Tools
Create a directory in \`/officer/user/tools/<tool-name>/\` with two files:
Create a directory in \`${userToolsDir}/<tool-name>/\` with two files:
**TOOL.md** — Frontmatter metadata + markdown documentation:
\`\`\`yaml
@@ -118,8 +134,6 @@ export async function execute(_toolCallId: string, params: Record<string, unknow
}
\`\`\`
Full guide: \`/officer/tools/TOOLS.md\`
## Environment Variables
| Variable | Description |
@@ -167,8 +181,8 @@ export function generateClaudeSettings(email: string, username?: string): string
// Pre-seed trust and skip-permissions in .claude.json so interactive Claude Code skips all prompts
if (username) {
const containerHome = `/home/${username}`;
const claudeJsonPath = join(getHomeDir(email), '.claude.json');
const homeDir = getHomeDir(email);
const claudeJsonPath = join(homeDir, '.claude.json');
let claudeJson: Record<string, unknown> = {};
try {
claudeJson = JSON.parse(readFileSync(claudeJsonPath, 'utf-8')) as Record<string, unknown>;
@@ -176,7 +190,7 @@ export function generateClaudeSettings(email: string, username?: string): string
// no existing config
}
const projects = (claudeJson.projects ?? {}) as Record<string, Record<string, unknown>>;
const projectKey = containerHome;
const projectKey = homeDir;
if (!projects[projectKey]) projects[projectKey] = {};
projects[projectKey]!.hasTrustDialogAccepted = true;
claudeJson.projects = projects;