feat(skill): add skill-authoring builtin + retarget node-inspect-debugger to Electron/Vite

This commit is contained in:
matevip 2026-05-20 08:09:34 +08:00
parent daeef4e3c2
commit a828de1306
2 changed files with 204 additions and 43 deletions

View File

@ -1,14 +1,15 @@
---
name: node-inspect-debugger
description: Debug Node.js via --inspect + Chrome DevTools Protocol CLI.
version: 1.0.0
version: 1.1.0
tags:
- debugging
- nodejs
- node-inspect
- cdp
- breakpoints
- ui-tui
- electron
- vite
author: ported
---
# Node.js Inspect Debugger
@ -24,15 +25,17 @@ Two tools, pick one:
**Prefer `node inspect` first.** It's always available and the REPL is fast.
In this repo the Node.js surfaces are the front-end packages — `mateclaw-ui`, `mateclaw-webchat`, and the Electron desktop app `mateclaw-desktop`. The Spring Boot backend is a JVM process and is not a target for this skill.
## When to Use
- A Node test fails and you need to see intermediate state
- ui-tui crashes or behaves wrong and you want to inspect React/Ink state pre-render
- tui_gateway child processes (`_SlashWorker`, PTY bridge workers) misbehave
- A Node-based build or packaging step (a Vite build, an `electron-builder` hook, a `scripts/` helper) fails and you need to see intermediate state
- The Electron desktop **main process** (`mateclaw-desktop`) crashes, hangs on startup, or mishandles the bundled Java backend child process
- A Vite dev server or a build plugin behaves wrong and `console.log` can't reach the value
- You need to inspect a value in a closure that `console.log` can't reach without patching
- Perf: attach to a running process to capture a CPU profile or heap snapshot
**Don't use for:** things `console.log` solves in under a minute. Breakpoint-driven debugging is heavier; use it when the payoff is real.
**Don't use for:** things `console.log` solves in under a minute. Breakpoint-driven debugging is heavier; use it when the payoff is real. The Electron **renderer** is a Chromium page, not a Node target — debug it with the window's built-in DevTools, not `node inspect`.
## Quick Reference: `node inspect` REPL
@ -72,7 +75,7 @@ The `debug>` prompt accepts:
## Attaching to a Running Process
When the process is already running (e.g. a long-lived dev server or the TUI gateway):
When the process is already running (e.g. a Vite dev server, or the Electron main process):
```bash
# 1. Send SIGUSR1 to enable the inspector on an existing process
@ -152,7 +155,7 @@ const CDP = require('chrome-remote-interface');
// Set a breakpoint by URL regex + line
await Debugger.setBreakpointByUrl({
urlRegex: '.*app\\.tsx$',
urlRegex: '.*dist-electron/main/index\\.js$',
lineNumber: 119, // 0-indexed
columnNumber: 0,
});
@ -167,74 +170,67 @@ Run it:
node /tmp/cdp-debug.js
```
the agent-specific note: `chrome-remote-interface` is NOT in `ui-tui/package.json`. Install it to a throwaway location if you don't want to dirty the project:
`chrome-remote-interface` is not a dependency of any package in this repo. Install it to a throwaway location so you don't dirty a project's `package.json`:
```bash
mkdir -p /tmp/cdp-tools && cd /tmp/cdp-tools && npm i chrome-remote-interface
NODE_PATH=/tmp/cdp-tools/node_modules node /tmp/cdp-debug.js
```
## Debugging the agent ui-tui
## Debugging the Electron Desktop App
The TUI is built Ink + tsx. Two common scenarios:
`mateclaw-desktop` is an Electron app. The **main process** is a Node process — `electron/main/index.ts`, compiled by Vite to `dist-electron/main/index.js` (the `main` field in `package.json`). It spawns the Java backend as a child process. The **renderer** is a Chromium `BrowserWindow` — debug that with the window's DevTools, not this skill.
### Debugging a single Ink component under dev
### Launch the main process paused
`ui-tui/package.json` has `npm run dev` (tsx --watch). Add `--inspect-brk` by running tsx directly:
Electron forwards `--inspect` / `--inspect-brk` to its main process. Build the Electron output first so there is a `dist-electron/` to run:
```bash
cd /path/to/your/project/ui-tui
npm run build # produce dist/ once so transpile isn't needed on first load
node --inspect-brk dist/entry.js
cd mateclaw-desktop
npm run build # produces dist/ and dist-electron/
npx electron --inspect-brk=9229 . # Electron starts, paused on the main process first line
# In another terminal:
node inspect -p <node pid>
node inspect ws://127.0.0.1:9229/<uuid>
```
Then inside `debug>`:
```
sb('dist/app.js', 220) # or wherever the suspect render is
sb('dist-electron/main/index.js', 220) # e.g. the suspect line in window/backend setup
cont
```
When it pauses, `repl` → inspect `props`, state refs, `useInput` handler values, etc.
When it pauses, `repl` → inspect `mainWindow`, `javaProcess`, `BACKEND_PORT`, the updater state, etc.
### Debugging a running `the agent --tui`
### Attach to an already-running desktop app
The TUI spawns Node from the Python CLI. Easiest path:
The Electron main process is the one launched without a `--type=` flag (renderer/GPU/utility processes carry `--type=`):
```bash
# 1. Launch TUI
the agent --tui &
TUI_PID=$(pgrep -f 'ui-tui/dist/entry' | head -1)
# Find the main process PID (the entry without --type=)
ps aux | grep -i 'mateclaw-desktop' | grep -v -- '--type='
# 2. Enable inspector on that Node PID
kill -SIGUSR1 "$TUI_PID"
# Enable the inspector on it
kill -SIGUSR1 <main-pid>
# 3. Find the WS URL
# Find the WS URL and attach
curl -s http://127.0.0.1:9229/json/list | jq -r '.[0].webSocketDebuggerUrl'
# 4. Attach
node inspect ws://127.0.0.1:9229/<uuid>
```
Interacting with the TUI (typing in its window) continues to advance execution; your debugger can pause it on a breakpoint at any `sb(...)`.
The Java backend that the main process spawns is a JVM, not a Node target — it will not appear in `/json/list`. To debug that, use the JVM's own remote-debug flags, not this skill.
### Debugging `_SlashWorker` / PTY child processes
## Debugging a Vite Dev Server
Those are Python, not Node — use the `python-debugpy` skill for them. Only Node portions (Ink UI, tui_gateway client, tsx-run tests under `ui-tui/`) use this skill.
## Running Vitest Tests Under the Debugger
`mateclaw-ui`, `mateclaw-webchat`, and `mateclaw-desktop` all run `vite` for `dev`. To step through Vite config or a build plugin, run Vite's binary under the inspector instead of the `pnpm dev` wrapper:
```bash
cd /path/to/your/project/ui-tui
# Run a single test file paused on entry
node --inspect-brk ./node_modules/vitest/vitest.mjs run --no-file-parallelism src/app/foo.test.tsx
cd mateclaw-ui
node --inspect-brk ./node_modules/vite/bin/vite.js
# In another terminal: node inspect -p <pid>, then sb('vite.config.ts', N), cont
```
In another terminal: `node inspect -p <pid>`, then `sb('src/app/foo.tsx', 42)`, `cont`.
Use `--no-file-parallelism` (vitest) or `--runInBand` (jest) so only one worker exists — debugging a pool is painful.
This pauses inside the Node process that loads `vite.config.ts` and runs plugin hooks. The browser-side Vue code it serves is not reachable here — that runs in the browser and is debugged with browser DevTools.
## Heap Snapshots & CPU Profiles (Non-interactive)
@ -261,7 +257,7 @@ require('fs').writeFileSync('/tmp/heap.heapsnapshot', chunks.join(''));
## Common Pitfalls
1. **Wrong line numbers in TS source.** Breakpoints hit the emitted JS, not the `.ts`. Either (a) break in the built `dist/*.js`, or (b) enable sourcemaps (`node --enable-source-maps`) and use `sb('src/app.tsx', N)` — but only with CDP clients that follow sourcemaps. `node inspect` CLI does not.
1. **Wrong line numbers in TS source.** Breakpoints hit the emitted JS, not the `.ts`. Either (a) break in the built file (`dist-electron/main/index.js`), or (b) enable sourcemaps (`node --enable-source-maps`) and use `sb('electron/main/index.ts', N)` — but only with CDP clients that follow sourcemaps. The `node inspect` CLI does not.
2. **`--inspect` vs `--inspect-brk`.** `--inspect` starts the inspector but doesn't pause; your script races past your first breakpoint if you attach too late. Use `--inspect-brk` when you need to set breakpoints before any code runs.
@ -270,11 +266,11 @@ require('fs').writeFileSync('/tmp/heap.heapsnapshot', chunks.join(''));
curl -s http://127.0.0.1:9229/json/list # lists all inspectable targets on the host
```
4. **Child processes.** `--inspect` on a parent does NOT inspect its children. Use `NODE_OPTIONS='--inspect-brk' node parent.js` to propagate to every child; be aware they all need unique ports (Node auto-increments when `NODE_OPTIONS='--inspect'` is inherited).
4. **Child processes.** `--inspect` on a parent does NOT inspect its children. Electron itself is multi-process, and the desktop main process additionally spawns the Java backend. Use `NODE_OPTIONS='--inspect-brk' node parent.js` to propagate to every Node child; be aware they all need unique ports (Node auto-increments when `NODE_OPTIONS='--inspect'` is inherited).
5. **Background kills.** If you `Ctrl+C` out of `node inspect` while the target is paused, the target stays paused. Either `cont` first, or `kill` the target explicitly.
6. **Running `node inspect` through an agent terminal.** It's a PTY-friendly REPL. In the agent, launch it with `terminal(pty=true)` or `background=true` + `process(action='submit', data='...')`. Non-PTY foreground mode will work for one-shot commands but not for interactive stepping.
6. **Running `node inspect` through the agent's shell tool.** The `execute_shell_command` tool is one-shot and non-interactive — it cannot drive the interactive `debug>` REPL. For interactive stepping, run `node inspect` in a real terminal yourself. For agent-driven debugging, prefer the scripted CDP driver above: it is fully non-interactive and runs fine as a single `execute_shell_command` call.
7. **Security.** `--inspect=0.0.0.0:9229` exposes arbitrary code execution. Always bind to `127.0.0.1` (the default) unless you have an isolated network.

View File

@ -0,0 +1,165 @@
---
name: skill-authoring
description: 'Author SKILL.md skills: frontmatter, validator limits, structure.'
version: 1.0.0
tags:
- skills
- authoring
- skill-md
- conventions
- meta
author: ported
---
# Authoring MateClaw Skills
## Overview
A skill is a `SKILL.md` file — YAML frontmatter plus a markdown body of reusable instructions. There are two places a SKILL.md can live, and they have different creation paths:
1. **Builtin (in-repo):** `mateclaw-server/src/main/resources/skills/<name>/SKILL.md` — committed, shipped inside the server JAR. On every startup `BuiltinSkillSeedService` scans `classpath*:skills/*/SKILL.md`, parses each frontmatter, and upserts a row into `mate_skill` keyed by `name`. The SKILL.md is the single source of truth — no SQL seed entry is required.
2. **Custom (runtime):** created by an agent or user through the `skill_manage` tool. Stored as a `mate_skill` row with `skill_type=custom` and exported to the workspace at `~/.mateclaw/skills/<name>/`. Not committed; lives per-installation.
This skill covers both. Note that `skill_manage` does NOT write into the in-repo `skills/` tree — builtin skills are authored by writing the file directly and restarting.
## When to Use
- You're adding a reusable workflow that should ship with MateClaw → builtin.
- You're editing an existing builtin skill under `mateclaw-server/src/main/resources/skills/`.
- An agent finished a complex task and wants to persist the approach → custom, via `skill_manage`.
- You're reviewing a SKILL.md for correct frontmatter and structure.
**Don't use for:** recording a one-off tip discovered while *using* a skill (that belongs in `record_lesson` / a per-skill LESSONS.md) or cross-skill memory notes (`remember`). This skill is about writing the skill document itself.
## Required Frontmatter
The frontmatter is parsed by `SkillFrontmatterParser`: a regex (`^---\s*\n(.*?)\n---\s*\n(.*)$`) splits the fenced block, then SnakeYAML loads it as a mapping. Hard requirements:
- Starts with `---` as the **first bytes** — no leading blank line, no BOM.
- A closing `---` line follows, then the body. The body must be non-empty.
- The block between the fences parses as a YAML mapping.
- `name` is present — it is the upsert key. `BuiltinSkillSeedService` skips any SKILL.md with no `name`.
- `description` is present — a single line.
If the frontmatter regex fails to match, the parser treats the whole file as body with an empty `name`, and a builtin skill is silently skipped at seed time. A loadable skill ALWAYS has well-formed frontmatter.
## Size & Naming Limits
- **Skill content:** ≤ 100,000 chars (`MAX_CONTENT_CHARS`, ~25k tokens) — enforced by `skill_manage` for custom skills. Builtin skills aren't hard-checked but should obey the same ceiling.
- **Name:** must match `^[a-z0-9][a-z0-9._-]{0,63}$` — lowercase letters and digits plus `-` `_` `.`, starting with a letter or digit, ≤ 64 chars. `skill_manage` lowercases the name before validating.
- **Description:** keep it to one line. Peer skills run 40-70 chars — a tight trigger phrase, not a paragraph.
- **Peer skills** in `resources/skills/` sit at 6-15k chars. Aim for that range; past ~20k, split detail into `references/*.md`.
## Peer-Matched Frontmatter
Every shipped skill follows this shape:
```yaml
---
name: my-skill-name
description: 'One line: what it does and when it fires.'
version: 1.0.0
tags:
- short
- descriptive
- tags
author: ported
---
```
Fields `BuiltinSkillSeedService` projects onto the `mate_skill` row:
| Field | Effect | Default if absent |
|---|---|---|
| `name` | upsert key, skill identity | — (required) |
| `description` | shown in skill lists | empty |
| `version` | `mate_skill.version` | `1.0.0` |
| `icon` | emoji, or a `/skill-assets/...` path | `🛠️` |
| `author` | attribution | `MateClaw` |
| `tags` | YAML list or CSV string | skill name |
| `nameZh` / `nameEn` | bilingual display names | none |
| `optional: true` | seeds the skill **disabled** — user opts in from the Skills page | `false` (enabled) |
| `dependencies.tools` | required tool ids → `config_json.requiredTools` | none |
| `platforms` | e.g. `[linux, macos, windows]` | none |
`version` / `author` / `tags` are not validator-enforced, but every peer carries them — omitting makes the skill look half-finished. Use `optional: true` for heavyweight skills (paid CLI dependencies, external OAuth, niche integrations) so they ship dark and the user activates them deliberately.
## Skill Structure
Shipped skills follow roughly:
```
# <Title>
## Overview — one or two paragraphs: what and why.
## When to Use — bulleted triggers, plus a "Don't use for:" counter-trigger.
## <Topic sections> — quick-reference tables, exact commands, concrete recipes
(mvn test, paths under mateclaw-server/, etc.).
## Common Pitfalls — numbered mistakes paired with their fixes.
## Verification Checklist — checkbox list of post-action checks.
```
Not every section is mandatory, but `Overview` + `When to Use` + an actionable body + `Common Pitfalls` is the minimum for the skill to read like a peer.
## Directory Placement
```
mateclaw-server/src/main/resources/skills/<skill-name>/SKILL.md
```
The `skills/` tree is **flat** — no category subdirectories. The seed glob `classpath*:skills/*/SKILL.md` matches exactly one level deep, so a skill nested under a category directory would never be scanned. The directory name SHOULD equal the frontmatter `name`. Supporting files go in `references/` and `scripts/` subdirectories (see below).
## Builtin Workflow (in-repo)
1. **Survey peers:** `ls mateclaw-server/src/main/resources/skills/` and read 2-3 SKILL.md files close to your topic — match tone and structure.
2. **Create** `skills/<name>/SKILL.md` with the file tools.
3. **Validate** that the frontmatter parses — see the checklist below.
4. **Restart the server.** `BuiltinSkillSeedService` seeds the new row only at startup; a running server will not see it. The service also skips re-seeding when no SKILL.md's size/mtime changed, so rebuilding the JAR is what makes a change land.
5. **Commit** the new `skills/<name>/` directory. No SQL seed change is needed — the SKILL.md is the source of truth and obsoletes per-skill `INSERT INTO mate_skill`.
## Custom Workflow (skill_manage)
Agents and users create runtime skills with the `skill_manage` tool — actions `create | edit | patch | delete`:
- `create` — a new skill from full SKILL.md content. Rejects a duplicate name.
- `edit` — a full-content rewrite of a custom skill.
- `patch` — find-and-replace one section (`oldText` → `newText`).
- `delete` — uninstall (logical delete plus workspace archive).
Notes:
- Every write is **security-scanned** (`SkillSecurityService`) before saving — dangerous patterns are rejected with the reason. Builtin SKILL.md files are NOT scanned; they are trusted committed source.
- `edit` / `patch` / `delete` **refuse builtin skills** ("cannot edit builtin skill"). To change a builtin skill, edit the resource file and restart.
- A custom skill is live immediately — the tool re-runs the resolver pipeline — so no restart is needed.
## Supporting Files
Beyond `SKILL.md`, a skill directory may carry:
- `references/*.md` — long-form material the body links to. Use this to keep SKILL.md under ~20k chars.
- `scripts/*` — executable helpers a skill invokes.
- `templates/`, `assets/` — used by some bundled skills (HTML templates, images, etc.).
`SkillFileAccessPolicy` only resolves runtime paths under `references/` and `scripts/`, and rejects `..` traversal or absolute paths — keep runtime-read files in those two directories.
## Common Pitfalls
1. **Leading whitespace before `---`.** The frontmatter regex anchors on `^---`; a blank line or BOM makes the whole file parse as body with an empty `name`, and a builtin skill is silently skipped.
2. **Expecting a running server to see a new builtin skill.** `BuiltinSkillSeedService` seeds only at startup. Restart — or, for a quick iteration, create a custom skill via `skill_manage`, which is live immediately.
3. **Trying to `skill_manage edit` a builtin skill.** It is refused. Builtin skills are committed source — edit the file and restart.
4. **Adding an `INSERT INTO mate_skill` for a new builtin skill.** Unnecessary and discouraged — the SKILL.md is the source of truth and the seed service upserts by `name`.
5. **Generic description.** "Debug things" is weak. A peer description names the *trigger* — "4-phase root cause debugging: understand bugs before fixing." beats "Debug things."
6. **Naming an external project or internal RFC in the skill body.** Describe the function objectively. Shipped content states *what* it does, not where the idea came from — `author: ported` is the neutral attribution for an adapted skill.
7. **Skill content over 100k chars.** `skill_manage` rejects it outright; split detail into `references/`.
8. **Mismatched directory and `name`.** The upsert keys on the frontmatter `name`, but a directory that disagrees confuses everyone reading the tree. Keep them equal.
## Verification Checklist
- [ ] File at `mateclaw-server/src/main/resources/skills/<name>/SKILL.md` (builtin); the directory name equals the frontmatter `name`
- [ ] Frontmatter starts at byte 0 with `---`, closes with a `---` line, and the body is non-empty
- [ ] `name` matches `^[a-z0-9][a-z0-9._-]{0,63}$`; `description` is a single line
- [ ] `version`, `tags`, `author` present (peer-matched shape)
- [ ] Total file ≤ 100,000 chars (aim 6-15k; split into `references/` past ~20k)
- [ ] Structure: `# Title``## Overview``## When to Use` → actionable body → `## Common Pitfalls``## Verification Checklist`
- [ ] No external project names or RFC numbers in the body
- [ ] Builtin: server restarted so `BuiltinSkillSeedService` seeds the row; the new `skills/<name>/` directory is committed
- [ ] Custom: created via `skill_manage`, security scan reported PASSED