101 lines
3.6 KiB
Markdown
101 lines
3.6 KiB
Markdown
# System Patterns
|
|
|
|
## Type Definition Pattern
|
|
Every module that has runtime-validated data uses a dedicated `types.ts` file:
|
|
```
|
|
src/config/types.ts ← Zod schemas + `export type X = z.infer<typeof XSchema>`
|
|
src/bus/types.ts
|
|
src/session/types.ts
|
|
src/cron/types.ts
|
|
src/provider/types.ts
|
|
```
|
|
The implementation files (`loader.ts`, `manager.ts`, etc.) import types from the sibling `types.ts`.
|
|
|
|
## Tool Pattern
|
|
All tools implement the `Tool` interface from `src/agent/tools/base.ts`:
|
|
```ts
|
|
interface Tool {
|
|
name: string
|
|
description: string
|
|
parameters: Record<string, unknown> // JSON Schema object
|
|
execute(args: Record<string, unknown>): Promise<string>
|
|
}
|
|
```
|
|
`ToolRegistry` stores tools by name, exposes `getDefinitions()` (OpenAI function-calling format), and `execute(name, args)`.
|
|
|
|
## Message Bus Pattern
|
|
Inbound and outbound messages are passed through a typed `AsyncQueue<T>`. The queue uses a `Promise`-based dequeue that resolves when an item is available (mirrors Python `asyncio.Queue`).
|
|
|
|
## Provider Pattern
|
|
`LLMProvider` (`src/provider/index.ts`) wraps the Vercel AI SDK `generateText()`. It:
|
|
- Accepts a model string and resolves it to the correct AI SDK provider instance
|
|
- Implements `chatWithRetry()` with 3 attempts on transient errors (429, 5xx, timeout)
|
|
- Repairs malformed tool-call JSON with `jsonrepair`
|
|
- Returns a normalized `LLMResponse` type
|
|
|
|
## Config Pattern
|
|
- Config file: `~/.config/nanobot/config.json` (camelCase JSON)
|
|
- Loaded with `loadConfig()`, validated by Zod, returns inferred `Config` type
|
|
- `NANOBOT_` env vars can override fields (e.g. `NANOBOT_MODEL`)
|
|
|
|
## Mattermost Channel Pattern
|
|
- Inbound: native `WebSocket` connecting to `wss://{server}/api/v4/websocket`, auth via hello message
|
|
- Outbound: `fetch()` to `POST /api/v4/posts`
|
|
- Session key: `mattermost:{channelId}` (or `mattermost:{channelId}:{rootId}` when `replyInThread`)
|
|
|
|
## Session Key Convention
|
|
`{channel}:{chatId}` — e.g. `mattermost:abc123`, `cli:direct`
|
|
|
|
## Logging Pattern
|
|
Use `console.error` / `console.warn` / `console.info` / `console.debug` — no external logger. Color via `picocolors` in CLI output only.
|
|
|
|
## CLI Command Pattern
|
|
Each command is in its own file with a registration function:
|
|
```ts
|
|
// src/cli/agent.ts
|
|
export function agentCommand(program: Command, config: Config, workspace: string): void {
|
|
program.command('agent')
|
|
.description('...')
|
|
.option('-m, --message <text>', 'Single message to process')
|
|
.action(async (opts) => { /* ... */ })
|
|
}
|
|
|
|
// src/cli/commands.ts (bootstrap)
|
|
export function createCli(): Command {
|
|
const program = new Command('nanobot')...
|
|
const config = loadConfig(opts.config);
|
|
const workspace = resolveWorkspacePath(config.agent.workspacePath);
|
|
gatewayCommand(program, config, workspace);
|
|
agentCommand(program, config, workspace);
|
|
return program;
|
|
}
|
|
```
|
|
|
|
## File Layout
|
|
```
|
|
src/
|
|
config/types.ts + loader.ts
|
|
bus/types.ts + queue.ts
|
|
provider/types.ts + index.ts
|
|
session/types.ts + manager.ts
|
|
cron/types.ts + service.ts
|
|
heartbeat/service.ts
|
|
agent/
|
|
memory.ts
|
|
skills.ts
|
|
context.ts
|
|
loop.ts
|
|
subagent.ts
|
|
tools/base.ts + filesystem.ts + shell.ts + web.ts + message.ts + spawn.ts + cron.ts
|
|
channels/
|
|
base.ts + mattermost.ts + manager.ts
|
|
cli/
|
|
types.ts # CommandHandler type
|
|
commands.ts # Bootstrap - loads config, registers commands
|
|
agent.ts # agentCommand() - interactive/single-shot mode
|
|
gateway.ts # gatewayCommand() - full runtime with Mattermost
|
|
index.ts
|
|
templates/ (SOUL.md, AGENTS.md, USER.md, TOOLS.md, HEARTBEAT.md, memory/MEMORY.md)
|
|
skills/ (copied from Python repo)
|
|
```
|