跳转至

平台集成指南

Note

目标受众: 正在构建 claude-mem 集成的开发人员(VSCode 扩展、IDE 插件、CLI 工具)。

快速参考

Worker Service 基础

// Resolve the worker port at runtime. The default is per-user (37700 + uid % 100),
// or whatever the user set via CLAUDE_MEM_WORKER_PORT / settings.json. Read it from
// process.env.CLAUDE_MEM_WORKER_PORT, then ~/.claude-mem/settings.json
// (CLAUDE_MEM_WORKER_PORT key), then fall back to the deterministic default.
const WORKER_BASE_URL = `http://127.0.0.1:${workerPort}`;

最常见操作

// Health check
GET /api/health

// Create/get session and queue observation
POST /api/sessions/observations
Body: { claudeSessionId, tool_name, tool_input, tool_response, cwd }

// Queue summary
POST /api/sessions/summarize
Body: { claudeSessionId, last_user_message, last_assistant_message }

// Complete session
POST /api/sessions/complete
Body: { claudeSessionId }

// Search observations
GET /api/search?query=authentication&type=observations&format=index&limit=20

// Get recent context for project
GET /api/context/recent?project=my-project&limit=3

环境变量

CLAUDE_MEM_MODEL=claude-haiku-4-5-20251001  # Default Claude model for observations/summaries
CLAUDE_MEM_CONTEXT_OBSERVATIONS=50          # Observations injected at SessionStart
CLAUDE_MEM_WORKER_PORT=                     # Optional override; default = 37700 + (uid % 100)
CLAUDE_MEM_DATA_DIR=                        # Optional override for the data directory
CLAUDE_MEM_PYTHON_VERSION=3.13              # Python version for chroma-mcp

构建命令(本地开发)

npm run build                 # Compile TypeScript (hooks + worker)
npm run sync-marketplace      # Copy to ~/.claude/plugins
npm run worker:restart        # Restart worker
npm run worker:logs           # View worker logs
npm run worker:status         # Check worker status

支持的 Harness

Worker API 与具体 harness 无关。claude-mem 为以下平台提供官方安装程序和适配器:

Harness 平台标识符 Hook / MCP 配置位置 安装命令
Claude Code claude 原生插件 hooks(~/.claude/...) /plugin marketplace add thedotmack/claude-mem
Codex CLI codex ~/.codex/config.toml npx claude-mem install --ide codex-cli
Cursor cursor ~/.cursor/hooks.json, ~/.cursor/mcp.json npx claude-mem install --ide cursor
Windsurf windsurf ~/.codeium/windsurf/hooks.json npx claude-mem install --ide windsurf
OpenCode opencode ~/.config/opencode/plugins/claude-mem.js, ~/.config/opencode/opencode.json npx claude-mem install --ide opencode
OpenClaw openclaw OpenClaw 网关插件配置 curl -fsSL https://install.cmem.ai/openclaw.sh \| bash
Antigravity CLI antigravity-cli ~/.gemini/settings.json, ~/.gemini/.../mcp_config.json npx claude-mem install --ide antigravity
Kimi Code kimi ~/.kimi-code/config.toml, ~/.kimi-code/mcp.json npx claude-mem kimi install
Hermes Agent hermes ~/.hermes/plugins/claude-mem/, ~/.hermes/config.yaml npx claude-mem install --ide hermes(Hermes 集成)

OpenCode 插件需要 OpenCode 1.3.4 或更高版本,并支持从一个文件同时适配 OpenCode 1 和 OpenCode 2。OpenCode 2 会从其 plugins 目录加载该插件,因此安装程序仅针对 OpenCode 1 在 opencode.json 中添加 plugin 条目。

Worker 架构

请求流程

Platform Hook/Extension
  → HTTP Request to Worker (`${WORKER_BASE_URL}` — per-user, default 37700+uid%100)
    → Route Handler (SessionRoutes/DataRoutes/SearchRoutes/etc.)
      → Domain Service (SessionManager/SearchManager/DatabaseManager)
        → Database (SQLite3 + Chroma vector DB)
          → SSE Broadcast (real-time UI updates)

领域服务

DatabaseManager

SQLite 连接管理与初始化

SessionManager

事件驱动的会话生命周期、消息队列

SearchManager

搜索编排(FTS5 + Chroma)

SSEBroadcaster

用于实时更新的 Server-Sent Events

SDKAgent

用于生成 observations/summaries 的 Claude Agent SDK

PaginationHelper

查询分页工具

SettingsManager

用户设置 CRUD

FormattingService

结果格式化(index 与 full)

TimelineService

统一时间线生成

路由组织

ViewerRoutes
  • 健康检查端点
  • 查看器 UI(React 应用)
  • 用于实时更新的 SSE 流
SessionRoutes
  • 会话生命周期(init、observations、summarize、complete)
  • 隐私检查与标签剥离
  • 自动启动 SDK agent 生成器
DataRoutes
  • 数据检索(observations、summaries、prompts、stats)
  • 分页支持
  • 处理状态
SearchRoutes
  • 所有搜索操作
  • 统一搜索 API
  • 时间线上下文
  • 语义快捷方式
SettingsRoutes
  • 用户设置
  • MCP 开关
  • 依赖健康状态

API 参考

会话生命周期(SessionRoutes)

创建/获取会话 + 排队观察(新 API)

POST /api/sessions/observations
Content-Type: application/json

{
  "claudeSessionId": "abc123",      // Claude session identifier (string)
  "tool_name": "Bash",
  "tool_input": { "command": "ls" },
  "tool_response": { "stdout": "..." },
  "cwd": "/path/to/project"
}
{ "status": "queued" }
// or
{ "status": "skipped", "reason": "private" }

Info

隐私检查: 如果用户 Prompt 完全被 <private> 标签包裹,则跳过。 标签剥离: 在存储前移除 <private> 和 <claude-mem-context> 标签。 自动启动: 确保 SDK agent 生成器正在运行以处理队列。

队列摘要(新 API)

POST /api/sessions/summarize
Content-Type: application/json

{
  "claudeSessionId": "abc123",
  "last_user_message": "User's message",
  "last_assistant_message": "Assistant's response"
}
{ "status": "queued" }
// or
{ "status": "skipped", "reason": "private" }
// or, for a session the worker has no row for
{ "status": "skipped", "reason": "unknown_session" }
// or, when the session's checkout is an excluded project
{ "status": "skipped", "reason": "project_excluded" }

用于与 CLAUDE_MEM_EXCLUDED_PROJECTS 进行比对的 checkout 是可选参数 cwd;如果未提供,则使用会话记录时所在的 checkout。自身无法检查排除项的主机应将其 checkout 作为 cwd 发送。

完成会话(新 API)

POST /api/sessions/complete
Content-Type: application/json

{
  "claudeSessionId": "abc123"
}
{ "success": true }
// or
{ "success": true, "message": "No active session found" }

Warning

效果: 停止 SDK 代理,将会话标记为完成,并广播状态变更。

旧版端点(仍受支持)

POST /sessions/:sessionDbId/init
Body: { userPrompt, promptNumber }
POST /sessions/:sessionDbId/observations
Body: { tool_name, tool_input, tool_response, prompt_number, cwd }
POST /sessions/:sessionDbId/summarize
Body: { last_user_message, last_assistant_message }
POST /sessions/:sessionDbId/complete

Note

新的集成应使用带 claudeSessionId 的 /api/sessions/* 端点。

数据检索(DataRoutes)

获取分页数据

GET /api/observations?offset=0&limit=20&project=my-project
GET /api/summaries?offset=0&limit=20&project=my-project
GET /api/prompts?offset=0&limit=20&project=my-project

```json 响应格式 { "items": [...], "hasMore": boolean, "offset": number, "limit": number }

#### 按 ID 获取 {#get-by-id}

=== "观察"

    ```http
    GET /api/observation/:id
    ```

=== "会话"

    ```http
    GET /api/session/:id
    ```

=== "提示"

    ```http
    GET /api/prompt/:id
    ```

#### 获取数据库统计信息 {#get-database-stats}

```http
GET /api/stats

```json 响应 { "worker": { "version": "7.0.0", "uptime": 12345, "activeSessions": 2, "sseClients": 1, "port": 37742 }, "database": { "path": "~/.claude-mem/claude-mem.db", "size": 1048576, "observations": 500, "sessions": 50, "summaries": 25 } }

#### 获取项目列表 {#get-projects-list}

```http
GET /api/projects

```json 响应 { "projects": ["claude-mem", "other-project", ...] }

#### 获取处理状态 {#get-processing-status}

```http
GET /api/processing-status

```json 响应 { "isProcessing": boolean, "queueDepth": number, "parkedSessions": number }

`parkedSessions` 统计当前处于暂停状态、等待可用并发槽位的会话(例如,被 `CLAUDE_MEM_MAX_CONCURRENT_AGENTS` 阻塞)。它与 `queueDepth` 相互独立;`queueDepth` 统计的是缓冲消息——会话无论是否有缓冲消息,都可能处于暂停状态。

### 搜索操作(SearchRoutes) {#search-operations-searchroutes}

#### 统一搜索 {#unified-search}

```http
GET /api/search?query=authentication&type=observations&format=index&limit=20

query string (query)
搜索查询文本(可选,省略则仅按筛选条件查询)
type string (query) 默认值:all
"observations" | "sessions" | "prompts"
format string (query) 默认值:index
"index" | "full"
limit number (query) 默认值:20
结果数量
project string (query)
按项目名称筛选
obs_type string (query)
按观察类型筛选(discovery、decision、bugfix、feature、refactor)
concepts string (query)
按概念筛选(逗号分隔)
files string (query)
按文件路径筛选(逗号分隔)
dateStart string (query)
ISO 时间戳(筛选起始时间)
dateEnd string (query)
ISO 时间戳(筛选结束时间)

```json 响应 { "observations": [...], "sessions": [...], "prompts": [...] }

!!! info

    **格式选项:**
    - `index`:用于列表展示的最少字段(id、title、preview)
    - `full`:包含所有字段的完整实体

#### 统一时间线 {#unified-timeline}

```http
GET /api/timeline?anchor=123&depth_before=10&depth_after=10&project=my-project

anchor string (query) 必填
锚点(观察 ID、会话的 "S123",或 ISO 时间戳)
depth_before number (query) 默认值:10
锚点之前的记录数
depth_after number (query) 默认值:10
锚点之后的记录数
project string (query)
按项目筛选

```json 响应 [ { "type": "observation", "id": 120, "created_at_epoch": ..., ... }, { "type": "session", "id": 5, "created_at_epoch": ..., ... }, { "type": "observation", "id": 123, "created_at_epoch": ..., ... } ]

#### 按文件路径搜索 {#search-by-file-path}

```http
GET /api/search/by-file?filePath=src/services/worker-service.ts&format=index&limit=10

获取近期上下文

GET /api/context/recent?project=my-project&limit=3

```json 响应 { "summaries": [...], "observations": [...] }

#### 上下文预览(供设置 UI 使用) {#context-preview-for-settings-ui}

```http
GET /api/context/preview?project=my-project

Note

返回带有 ANSI 颜色的纯文本,用于终端显示

上下文注入(供 Hooks 使用)

GET /api/context/inject?project=my-project&colors=true

Note

返回预格式化、可直接用于显示的上下文字符串

设置与配置(SettingsRoutes)

获取/更新用户设置

GET /api/settings
{
  "sidebarOpen": boolean,
  "selectedProject": string | null
}
POST /api/settings
Body: { "sidebarOpen": true, "selectedProject": "my-project" }
{ "success": true }

MCP 服务器状态/切换

GET /api/mcp/status
{ "enabled": boolean }
POST /api/mcp/toggle
Body: { "enabled": true }
{ "success": true, "enabled": boolean }

查看器与实时更新(ViewerRoutes)

健康检查

GET /api/health

```json Response { "status": "ok" }

#### 查看器 UI {#viewer-ui}

```http
GET /

Note

返回 React 应用的 HTML

SSE 流

GET /stream

Info

服务器发送事件流

事件类型: - processing_status: { type, isProcessing, queueDepth } - session_started: { type, sessionDbId, project } - observation_queued: { type, sessionDbId } - summarize_queued: { type } - observation_created: { type, observation } - summary_created: { type, summary } - new_prompt: { type, id, claude_session_id, project, prompt_number, prompt_text, created_at_epoch }

数据模型

活动会话(内存中)

interface ActiveSession {
  sessionDbId: number;                  // Database ID (numeric)
  claudeSessionId: string;              // Claude session identifier (string)
  sdkSessionId: string | null;          // SDK session ID
  project: string;                      // Project name
  userPrompt: string;                   // Current user prompt text
  pendingMessages: PendingMessage[];    // Queue of pending operations
  abortController: AbortController;     // For cancellation
  generatorPromise: Promise<void> | null; // SDK agent promise
  lastPromptNumber: number;             // Last processed prompt number
  startTime: number;                    // Session start timestamp
  cumulativeInputTokens: number;        // Total input tokens
  cumulativeOutputTokens: number;       // Total output tokens
}

interface PendingMessage {
  type: 'observation' | 'summarize';
  tool_name?: string;
  tool_input?: any;
  tool_response?: any;
  prompt_number?: number;
  cwd?: string;
  last_user_message?: string;
  last_assistant_message?: string;
}

数据库实体

interface ObservationRow {
  id: number;
  sdk_session_id: string;
  title: string;
  subtitle?: string;
  summary: string;
  facts: string;           // JSON array of fact strings
  concepts: string;        // JSON array of concept strings
  files_touched: string;   // JSON array of file paths
  obs_type: string;        // discovery, decision, bugfix, feature, refactor
  project: string;
  created_at_epoch: number;
  prompt_number: number;
}
interface SessionSummaryRow {
  id: number;
  sdk_session_id: string;
  summary_text: string;
  facts: string;           // JSON array
  concepts: string;        // JSON array
  files_touched: string;   // JSON array
  project: string;
  created_at_epoch: number;
}
interface UserPromptRow {
  id: number;
  claude_session_id: string;
  sdk_session_id: string;
  project: string;
  prompt_number: number;
  prompt_text: string;
  created_at_epoch: number;
}

搜索结果

interface ObservationSearchResult {
  id: number;
  title: string;
  subtitle?: string;
  summary: string;
  facts: string[];         // Parsed from JSON
  concepts: string[];      // Parsed from JSON
  files_touched: string[]; // Parsed from JSON
  obs_type: string;
  project: string;
  created_at_epoch: number;
  prompt_number: number;
  rank?: number;           // FTS5 rank score
}

interface SessionSummarySearchResult {
  id: number;
  summary_text: string;
  facts: string[];
  concepts: string[];
  files_touched: string[];
  project: string;
  created_at_epoch: number;
  rank?: number;
}

interface UserPromptSearchResult {
  id: number;
  claude_session_id: string;
  project: string;
  prompt_number: number;
  prompt_text: string;
  created_at_epoch: number;
  rank?: number;
}

时间线条目

interface TimelineItem {
  type: 'observation' | 'session' | 'prompt';
  id: number;
  created_at_epoch: number;
  // Entity-specific fields based on type
}

集成模式

将 Claude Code 钩子映射到 Worker API

1. SessionStart 钩子

新 API 不需要此钩子——会话会在首次观察时自动创建。

2. UserPromptSubmit 钩子

无需调用 API——user_prompt 会由该提示中的首次观察捕获。

3. PostToolUse 钩子

async function onPostToolUse(context: HookContext) {
  const { session_id, tool_name, tool_input, tool_result, cwd } = context;

  const response = await fetch(`${WORKER_BASE_URL}/api/sessions/observations`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      claudeSessionId: session_id,
      tool_name,
      tool_input,
      tool_response: tool_result,
      cwd
    })
  });

  const result = await response.json();
  // result.status === 'queued' | 'skipped'
}

4. Summary 钩子

async function onSummary(context: HookContext) {
  const { session_id, last_user_message, last_assistant_message } = context;

  await fetch(`${WORKER_BASE_URL}/api/sessions/summarize`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      claudeSessionId: session_id,
      last_user_message,
      last_assistant_message
    })
  });
}

5. SessionEnd Hook

async function onSessionEnd(context: HookContext) {
  const { session_id } = context;

  await fetch(`${WORKER_BASE_URL}/api/sessions/complete`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      claudeSessionId: session_id
    })
  });
}

VSCode 扩展集成

语言模型工具注册

import * as vscode from 'vscode';

interface SearchTool extends vscode.LanguageModelChatTool {
  invoke(
    options: vscode.LanguageModelToolInvocationOptions<{ query: string }>,
    token: vscode.CancellationToken
  ): vscode.ProviderResult<vscode.LanguageModelToolResult>;
}

const searchTool: SearchTool = {
  invoke: async (options, token) => {
    const { query } = options.input;

    try {
      const response = await fetch(
        `${WORKER_BASE_URL}/api/search?query=${encodeURIComponent(query)}&format=index&limit=10`
      );

      if (!response.ok) {
        throw new Error(`Search failed: ${response.statusText}`);
      }

      const results = await response.json();

      // Format results for language model
      return new vscode.LanguageModelToolResult([
        new vscode.LanguageModelTextPart(JSON.stringify(results, null, 2))
      ]);
    } catch (error) {
      return new vscode.LanguageModelToolResult([
        new vscode.LanguageModelTextPart(`Error: ${error.message}`)
      ]);
    }
  }
};

// Register tool
vscode.lm.registerTool('claude-mem-search', searchTool);

聊天参与者实现

const participant = vscode.chat.createChatParticipant('claude-mem', async (request, context, stream, token) => {
  const claudeSessionId = context.session.id;

  // First message in conversation - no initialization needed
  // Session is auto-created on first observation

  // Process user message
  stream.markdown(`Searching memory for: ${request.prompt}\n\n`);

  const response = await fetch(
    `${WORKER_BASE_URL}/api/search?query=${encodeURIComponent(request.prompt)}&format=index&limit=5`
  );

  const results = await response.json();

  if (results.observations?.length > 0) {
    stream.markdown('**Found observations:**\n');
    for (const obs of results.observations) {
      stream.markdown(`- ${obs.title} (${obs.project})\n`);
    }
  }

  return { metadata: { command: 'search' } };
});

错误处理与弹性

连接失败

async function callWorkerWithFallback<T>(
  endpoint: string,
  options?: RequestInit
): Promise<T | null> {
  try {
    const response = await fetch(`${WORKER_BASE_URL}${endpoint}`, {
      ...options,
      signal: AbortSignal.timeout(5000) // 5s timeout
    });

    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }

    return await response.json();
  } catch (error) {
    console.error(`Worker unavailable (${endpoint}):`, error);
    return null; // Graceful degradation
  }
}

带指数退避的重试逻辑

async function retryWithBackoff<T>(
  fn: () => Promise<T>,
  maxRetries = 3,
  baseDelay = 100
): Promise<T> {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (attempt === maxRetries - 1) throw error;

      const delay = baseDelay * Math.pow(2, attempt);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
  throw new Error('Max retries exceeded');
}

Worker 健康检查

async function isWorkerHealthy(): Promise<boolean> {
  try {
    const response = await fetch(`${WORKER_BASE_URL}/api/health`, {
      signal: AbortSignal.timeout(2000)
    });
    return response.ok;
  } catch {
    return false;
  }
}

隐私标签处理

Info

worker 在存储前会自动移除隐私标签: - <private>content</private> - 用户级隐私控制 - <claude-mem-context>content</claude-mem-context> - 系统级标签(防止递归存储)

隐私检查: 如果整个用户提示词都被 <private> 标签包裹,则会跳过观察结果/摘要。

自定义错误类

class WorkerUnavailableError extends Error {
  constructor() {
    super('Claude-mem worker is not running or unreachable');
    this.name = 'WorkerUnavailableError';
  }
}

class WorkerTimeoutError extends Error {
  constructor(endpoint: string) {
    super(`Worker request timed out: ${endpoint}`);
    this.name = 'WorkerTimeoutError';
  }
}

SSE 流错误处理

function connectToSSE(onEvent: (event: any) => void) {
  const eventSource = new EventSource(`${WORKER_BASE_URL}/stream`);

  eventSource.onmessage = (event) => {
    try {
      const data = JSON.parse(event.data);
      onEvent(data);
    } catch (error) {
      console.error('SSE parse error:', error);
    }
  };

  eventSource.onerror = (error) => {
    console.error('SSE connection error:', error);
    eventSource.close();

    // Reconnect after 5 seconds
    setTimeout(() => connectToSSE(onEvent), 5000);
  };

  return eventSource;
}

开发工作流

vscode-extension/
├── src/
│   ├── extension.ts              # Extension entry point
│   ├── services/
│   │   ├── WorkerClient.ts       # HTTP client for worker
│   │   └── MemoryManager.ts      # High-level memory operations
│   ├── chat/
│   │   └── participant.ts        # Chat participant implementation
│   └── tools/
│       ├── search.ts             # Search language model tool
│       └── context.ts            # Context injection tool
├── package.json
├── tsconfig.json
└── README.md

构建配置(esbuild)

// build.js
const esbuild = require('esbuild');

esbuild.build({
  entryPoints: ['src/extension.ts'],
  bundle: true,
  outfile: 'dist/extension.js',
  external: ['vscode'],
  format: 'cjs',
  platform: 'node',
  target: 'node18',
  sourcemap: true
}).catch(() => process.exit(1));

package.json(VSCode 扩展)

{
  "name": "claude-mem-vscode",
  "displayName": "Claude-Mem",
  "version": "1.0.0",
  "engines": {
    "vscode": "^1.95.0"
  },
  "activationEvents": [
    "onStartupFinished"
  ],
  "main": "./dist/extension.js",
  "contributes": {
    "chatParticipants": [
      {
        "id": "claude-mem",
        "name": "memory",
        "description": "Search your persistent memory"
      }
    ],
    "languageModelTools": [
      {
        "name": "claude-mem-search",
        "displayName": "Search Memory",
        "description": "Search persistent memory for observations, sessions, and prompts"
      }
    ]
  },
  "scripts": {
    "build": "node build.js",
    "watch": "node build.js --watch",
    "package": "vsce package"
  },
  "devDependencies": {
    "@types/vscode": "^1.95.0",
    "esbuild": "^0.19.0",
    "typescript": "^5.3.0"
  }
}

本地测试循环

1. 终端 1:监听构建

npm run watch

2. 终端 2:检查 worker 状态

npm run worker:status
npm run worker:logs

3. 终端 3:手动测试 API

curl http://127.0.0.1:$WORKER_PORT/api/health
curl "http://127.0.0.1:$WORKER_PORT/api/search?query=test&limit=5"

4. VSCode:启动扩展主机

按 F5 启动扩展主机。

调试配置(.vscode/launch.json)

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Run Extension",
      "type": "extensionHost",
      "request": "launch",
      "args": [
        "--extensionDevelopmentPath=${workspaceFolder}"
      ],
      "outFiles": [
        "${workspaceFolder}/dist/**/*.js"
      ],
      "preLaunchTask": "npm: build"
    }
  ]
}

测试策略

单元测试(Worker Client)

import { describe, it, expect } from 'vitest';
import { WorkerClient } from '../src/services/WorkerClient';

describe('WorkerClient', () => {
  it('should check worker health', async () => {
    const client = new WorkerClient();
    const healthy = await client.isHealthy();
    expect(healthy).toBe(true);
  });

  it('should queue observation', async () => {
    const client = new WorkerClient();
    const result = await client.queueObservation({
      claudeSessionId: 'test-123',
      tool_name: 'Bash',
      tool_input: { command: 'ls' },
      tool_response: { stdout: 'file1.txt' },
      cwd: '/tmp'
    });
    expect(result.status).toBe('queued');
  });

  it('should search observations', async () => {
    const client = new WorkerClient();
    const results = await client.search({ query: 'test', limit: 5 });
    expect(results).toHaveProperty('observations');
  });
});

集成测试(启动 Worker)

import { spawn } from 'child_process';
import { describe, it, expect, beforeAll, afterAll } from 'vitest';

describe('Worker Integration', () => {
  let workerProcess: ReturnType<typeof spawn>;

  beforeAll(async () => {
    // Start worker process
    workerProcess = spawn('node', ['dist/worker-service.js'], {
      env: { ...process.env, CLAUDE_MEM_WORKER_PORT: '37778' }
    });

    // Wait for worker to be ready
    await new Promise(resolve => setTimeout(resolve, 2000));
  });

  afterAll(() => {
    workerProcess.kill();
  });

  it('should respond to health check', async () => {
    const response = await fetch('http://localhost:37778/api/health');
    expect(response.ok).toBe(true);
  });
});

手动测试清单

第一阶段:连接与健康
  • [ ] Worker 成功启动(npm run worker:status)
  • [ ] 健康检查端点正常响应(curl http://127.0.0.1:$WORKER_PORT/api/health)
  • [ ] SSE 流成功连接(curl http://127.0.0.1:$WORKER_PORT/stream)
第二阶段:会话生命周期
  • [ ] 观察记录入队会创建会话
  • [ ] 观察记录出现在数据库中
  • [ ] 隐私标签被去除
  • [ ] 私有 Prompt 被跳过
  • [ ] 汇总请求入队会创建摘要
  • [ ] 完成会话会停止处理
第三阶段:搜索与检索
  • [ ] 按查询搜索观察记录
  • [ ] 按查询搜索会话
  • [ ] 按查询搜索 Prompt
  • [ ] 获取项目的近期上下文
  • [ ] 获取观察记录周围的时间线
  • [ ] 语义快捷方式(decisions、changes、how-it-works)
第四阶段:实时更新
  • [ ] SSE 广播处理状态
  • [ ] SSE 广播新的观察记录
  • [ ] SSE 广播新的摘要
  • [ ] SSE 广播新的 Prompt
第五阶段:错误处理
  • [ ] Worker 不可用时优雅降级
  • [ ] 慢请求的超时处理
  • [ ] 暂时性故障的重试逻辑

代码示例

完整的 WorkerClient 实现

export class WorkerClient {
  private baseUrl: string;

  // Resolve the active worker port via env / settings.json, falling back
  // to the deterministic per-user default. See parseWorkerPort() for an
  // example helper; never hardcode a single value.
  constructor(port: number = resolveWorkerPort()) {
    this.baseUrl = `http://127.0.0.1:${port}`;
  }

  async isHealthy(): Promise<boolean> {
    try {
      const response = await fetch(`${this.baseUrl}/api/health`, {
        signal: AbortSignal.timeout(2000)
      });
      return response.ok;
    } catch {
      return false;
    }
  }

  async queueObservation(data: {
    claudeSessionId: string;
    tool_name: string;
    tool_input: any;
    tool_response: any;
    cwd?: string;
  }): Promise<{ status: string; reason?: string }> {
    const response = await fetch(`${this.baseUrl}/api/sessions/observations`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data),
      signal: AbortSignal.timeout(5000)
    });

    if (!response.ok) {
      throw new Error(`Failed to queue observation: ${response.statusText}`);
    }

    return await response.json();
  }

  async queueSummarize(data: {
    claudeSessionId: string;
    last_user_message?: string;
    last_assistant_message?: string;
  }): Promise<{ status: string; reason?: string }> {
    const response = await fetch(`${this.baseUrl}/api/sessions/summarize`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data),
      signal: AbortSignal.timeout(5000)
    });

    if (!response.ok) {
      throw new Error(`Failed to queue summary: ${response.statusText}`);
    }

    return await response.json();
  }

  async completeSession(claudeSessionId: string): Promise<void> {
    const response = await fetch(`${this.baseUrl}/api/sessions/complete`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ claudeSessionId }),
      signal: AbortSignal.timeout(5000)
    });

    if (!response.ok) {
      throw new Error(`Failed to complete session: ${response.statusText}`);
    }
  }

  async search(params: {
    query?: string;
    type?: 'observations' | 'sessions' | 'prompts';
    format?: 'index' | 'full';
    limit?: number;
    project?: string;
  }): Promise<any> {
    const queryString = new URLSearchParams(
      Object.entries(params)
        .filter(([_, v]) => v !== undefined)
        .map(([k, v]) => [k, String(v)])
    ).toString();

    const response = await fetch(
      `${this.baseUrl}/api/search?${queryString}`,
      { signal: AbortSignal.timeout(10000) }
    );

    if (!response.ok) {
      throw new Error(`Search failed: ${response.statusText}`);
    }

    return await response.json();
  }

  connectSSE(onEvent: (event: any) => void): EventSource {
    const eventSource = new EventSource(`${this.baseUrl}/stream`);

    eventSource.onmessage = (event) => {
      try {
        const data = JSON.parse(event.data);
        onEvent(data);
      } catch (error) {
        console.error('SSE parse error:', error);
      }
    };

    eventSource.onerror = (error) => {
      console.error('SSE connection error:', error);
    };

    return eventSource;
  }
}

搜索语言模型工具

import * as vscode from 'vscode';
import { WorkerClient } from './WorkerClient';

export function registerSearchTool(context: vscode.ExtensionContext) {
  const client = new WorkerClient();

  const searchTool = vscode.lm.registerTool('claude-mem-search', {
    description: 'Search persistent memory for observations, sessions, and prompts',
    inputSchema: {
      type: 'object',
      properties: {
        query: {
          type: 'string',
          description: 'Search query text'
        },
        type: {
          type: 'string',
          enum: ['observations', 'sessions', 'prompts'],
          description: 'Type of results to return'
        },
        limit: {
          type: 'number',
          description: 'Maximum number of results',
          default: 10
        }
      },
      required: ['query']
    },
    invoke: async (options, token) => {
      const { query, type, limit = 10 } = options.input;

      try {
        const results = await client.search({
          query,
          type,
          format: 'index',
          limit
        });

        // Format results for language model
        let formatted = '';

        if (results.observations?.length > 0) {
          formatted += '## Observations\n\n';
          for (const obs of results.observations) {
            formatted += `- **${obs.title}** (${obs.project})\n`;
            formatted += `  ${obs.summary}\n`;
            if (obs.concepts?.length > 0) {
              formatted += `  Concepts: ${obs.concepts.join(', ')}\n`;
            }
            formatted += '\n';
          }
        }

        return new vscode.LanguageModelToolResult([
          new vscode.LanguageModelTextPart(formatted)
        ]);
      } catch (error) {
        return new vscode.LanguageModelToolResult([
          new vscode.LanguageModelTextPart(`Error: ${error.message}`)
        ]);
      }
    }
  });

  context.subscriptions.push(searchTool);
}

关键实现说明

Warning

sessionDbId 与 claudeSessionId

重要提示: 新的 API 端点请使用 claudeSessionId(字符串),而不是 sessionDbId(数字)。

  • sessionDbId - 数字数据库 ID(仅限旧版端点)
  • claudeSessionId - 来自 Claude 平台的字符串标识符(新版端点)

Warning

JSON 字符串字段

像 facts、concepts 和 files_touched 这样的字段以 JSON 字符串形式存储,需要解析:

const observation = await client.getObservationById(123);
const facts = JSON.parse(observation.facts); // string[] array
const concepts = JSON.parse(observation.concepts); // string[] array

Warning

时间戳

所有 created_at_epoch 字段的单位均为毫秒,而非秒:

const date = new Date(observation.created_at_epoch); // ✅ Correct
const date = new Date(observation.created_at_epoch * 1000); // ❌ Wrong (already in ms)

Info

异步处理

Worker 会异步处理观察记录/摘要。结果将在排队后 1-2 秒内出现在数据库中。请使用 SSE 事件获取实时通知。

Info

隐私标签

始终将敏感内容包裹在 <private> 标签中以防止存储:

const userMessage = '<private>API key: sk-1234567890</private>';
// This observation will be skipped (entire prompt is private)

其他资源

文档

完整的 claude-mem 文档

GitHub

源代码和问题跟踪器

Worker Service

Worker 架构详情

数据库模式

数据库结构和查询

本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 cl/claude-mem