Skip to content

Developer guide

Thanks for helping improve Xiajiao. The backend is ~9.5k lines (excluding the largest UI files), organized for approachable contributions.

Environment

Requirements

  • Node.js >= 22.0.0
  • Git
  • Editor of your choice (VS Code, Cursor, Vim, …)

Run locally

bash
git clone https://github.com/moziio/xiajiao.git
cd xiajiao
npm install
npm start

Restart Node after server changes; refresh the browser for public/ edits (no bundler).

Tests

bash
npm test

node:test with ~53 unit tests, usually a few seconds.

Code style

JavaScript

  • No HTTP frameworknode:http only
  • No transpiler — native ESM
  • No frontend bundler — Vanilla JS in public/
  • Prefer const over let; avoid var
  • async/await instead of callback pyramids

Comments

  • Only where intent is non-obvious
  • Explain why, not what the next line does
  • JSDoc on exported helpers:
javascript
/**
 * Search memories for dedupe / retrieval using cosine similarity.
 * Similarity >= DEDUP_THRESHOLD counts as duplicate.
 * @param {string} agentId
 * @param {string} text
 * @param {number} [topK=10]
 * @returns {Promise<Array<{content: string, type: string, similarity: number}>>}
 */
async function searchMemory(agentId, text, topK = 10) {
  // ...
}

Dependencies

Do not add packages unless all are true:

  1. Standard library cannot do it
  2. A faithful implementation would exceed ~200 lines
  3. The feature is core, not a nice-to-have
  4. The dependency is maintained and reputable

Call out any new dependency explicitly in the PR description.

Project map

server/
├── index.js
├── storage.js
├── ws.js
├── routes/
│   └── settings.js            # Settings + HTTP tool management API
├── services/
│   ├── llm.js
│   ├── tool-registry.js       # Centralized tool registration + per-agent ACL
│   ├── http-tool-engine.js    # HTTP custom tool engine (zero-code API bridge)
│   ├── mcp-manager.js         # MCP server connections + tool discovery
│   ├── channel-engine.js      # External channel (Feishu, etc.) management
│   ├── memory.js
│   ├── rag.js
│   ├── tools/                 # Built-in tool modules (auto-scanned)
│   └── ...
└── test/

data/
├── custom-tools/              # User-defined JS tool modules (auto-scanned)
└── http-tools.json            # HTTP custom tool definitions

Which file to change?

GoalLocation
New REST surfaceserver/routes/ + route registration in server/router.js
New built-in tool (JS)Drop .js into server/services/tools/ — auto-registered on startup
New user tool (JS)Drop .js into data/custom-tools/ — auto-registered on startup
New HTTP tool (zero-code)Settings UI → HTTP Tools, or edit data/http-tools.json
Tool registry / ACLserver/services/tool-registry.js
MCP integrationserver/services/mcp-manager.js
LLM pipeline tweaksserver/services/llm.js
Memory / RAGserver/services/memory.js, rag.js
UIpublic/app.js, public/styles.css
Search enginesserver/services/search-engines.js
Schema migrationsserver/storage.js

Testing

All tests

bash
npm test

Single file

bash
node --test server/test/memory.test.js

Example test

javascript
import { describe, it } from 'node:test';
import assert from 'node:assert';

describe('Memory', () => {
  it('should write and search memory', async () => {
    await memory.write('agent-1', 'semantic', 'user prefers Python');
    const results = await memory.search('agent-1', 'programming language');
    assert.ok(results.length > 0);
    assert.ok(results[0].content.includes('Python'));
  });
});

Coverage (approximate)

AreaTestsFocus
storage~15CRUD
memory~10write/search/dedupe
rag~8chunk/index/search
llm~5API + tools
tools~8handlers
misc~7helpers/config

Pull requests

1. Fork & clone

bash
git clone https://github.com/YOUR_USER/xiajiao.git
cd xiajiao
git remote add upstream https://github.com/moziio/xiajiao.git

2. Branch

bash
git checkout -b feature/my-feature

Naming: feature/*, fix/*, docs/*, refactor/*.

3. Develop & test

bash
npm test

4. Commit

bash
git add .
git commit -m "feat: describe the change"

Follow Conventional Commits (feat, fix, docs, refactor, test, chore).

5. Push & open PR

bash
git push origin feature/my-feature

Describe motivation, scope, and how you verified the change.

Good first issues

LevelAreaExamples
EasyDocsFixes, clarifications, translations
EasyUICSS tweaks, responsive fixes
EasyToolsHTTP custom tool definitions (zero-code)
MediumSearchNew engine adapter
MediumToolsJS tool module in data/custom-tools/
MediumTestsMore cases
HardFeaturesWorkflow engine, deeper MCP work

Tutorial: add a search engine (Brave)

1. Adapter

javascript
async function braveSearch(query) {
  const url = `https://api.search.brave.com/res/v1/web/search?q=${encodeURIComponent(query)}`;
  const res = await fetch(url, {
    headers: { 'X-Subscription-Token': process.env.BRAVE_API_KEY }
  });
  const data = await res.json();
  return data.web.results.map(r => ({
    title: r.title,
    url: r.url,
    snippet: r.description
  }));
}

2. Register

javascript
const engines = {
  google: googleSearch,
  bing: bingSearch,
  brave: braveSearch,
};

3. Tests

javascript
describe('Brave Search', () => {
  it('should return search results', async () => {
    const results = await braveSearch('Node.js');
    assert.ok(results.length > 0);
    assert.ok(results[0].title);
    assert.ok(results[0].url);
  });
});

4. PR

bash
git checkout -b feature/brave-search
git add server/services/search-engines.js server/test/search.test.js
git commit -m "feat: add Brave search adapter"
git push origin feature/brave-search

Roughly one service file + tests (~30 lines).

Debugging

LLM traffic

Temporary logging in server/services/llm.js:

javascript
console.log('LLM request:', JSON.stringify(messages, null, 2));
console.log('LLM chunk:', chunk);

SQLite

bash
sqlite3 data/xiajiao.db ".tables"
sqlite3 data/xiajiao.db "SELECT * FROM messages ORDER BY created_at DESC LIMIT 5;"
sqlite3 data/xiajiao.db "SELECT * FROM settings;"

Memory DB

bash
sqlite3 data/workspace-{agentId}/memory.db \
  "SELECT type, content, created_at FROM memories ORDER BY created_at DESC LIMIT 10;"

VS Code launch config

json
{
  "type": "node",
  "request": "launch",
  "name": "Debug Xiajiao",
  "program": "${workspaceFolder}/server/index.js",
  "runtimeVersion": "22",
  "env": { "OWNER_KEY": "admin" }
}