npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ylib-syim

v0.0.7

Published

多 IM / 多 Agent 的会话路由与上下文管理(支持 /new)

Readme

@ylib/syim

多 IM(钉钉、飞书等)/ 多 Agent 的会话路由与上下文管理,支持 /new/reset 重开上下文。可作为库接入现有项目,或配合 OpenClaw 使用。

本仓库为独立项目(已从 OpenClaw monorepo 拆出),依赖用 npm 管理。

钉钉桥接(连接远程 yuce-gpt Gateway)

本地调试:

npm install
npx tsx scripts/dingtalk-stdio-bridge.ts

打包成单文件 JS(仍需本机 Node,分发更方便)

npm run build:bridge
node dist/dingtalk-stdio-bridge.cjs

build:bridge 外置上述重型依赖并 默认压缩,产物约 200KB+(未压缩调试用 npm run build:bridge:debug)。运行前项目根 npm install;仅拷贝 cjs 见 scripts/bridge-runtime-package.json。全量单文件:npm run build:bridge:all。体积构成与继续缩小思路见 docs/packaging-二进制与分发.md(文内「主库还能再缩小吗」)。

说明与「真·无 Node 二进制」的可行性见 docs/packaging-二进制与分发.md

一键安装配置 + 后台服务 + im-agent-hub-ctl 管理命令:见 scripts/install/README.md。日常升级代码与依赖可执行 im-agent-hub-ctl update(或 update-help / version 查看说明与当前状态)。若使用 bundle,update 后请再执行 npm run build:bridge

仓库内默认 openclaw.json 可为精简结构:仅包含 channels.dingtalk-connector(钉钉插件 / @dingtalk-real-ai/dingtalk-connector 所需字段)。安装脚本只合并该通道,不会自动写入 bindings 等其它顶级键。

直接使用方式

im-agent-hub 是:在你的代码里创建 Hub、配置 Router、Agents、Adapters,然后把收到的 IM 消息转成 InboundMessage 调用 hub.handleInbound(input),回复会通过对应 adapter 发回 IM。

import {
  createHub,
  createDingTalkAdapter,
  createRuleBasedRouter,
  createInMemorySessionStore,
  createLangChainLikeAgent,
} from "@ylib/syim";

const hub = createHub({
  router: createRuleBasedRouter({ defaultAgentId: "main" }),
  agents: [
    createLangChainLikeAgent({
      id: "main",
      systemPrompt: "你是助手。",
      runnable: async ({ messages }) => {
        const last = messages[messages.length - 1];
        return { content: `回复:${last?.content ?? ""}` };
      },
    }),
  ],
  adapters: [
    createDingTalkAdapter({
      config: { clientId: "钉钉 AppKey", clientSecret: "钉钉 AppSecret" },
    }),
  ],
  sessions: createInMemorySessionStore(),
});

// 当从钉钉收到消息时,把事件转成 InboundMessage 再调用:
await hub.handleInbound({
  provider: "dingtalk",
  peer: { kind: "direct", id: "用户 staffId" },  // 或 kind: "group", id: "群 openConversationId"
  sender: { id: "用户 id", name: "用户名" },
  messageId: "消息 id",
  timestamp: Date.now(),
  text: "用户发的文字",
});

多 Agent 与 Bindings(与主项目一致)

主项目通过 agents.list + bindings 配置多个 agent,并按 channel/accountId/peer 匹配路由。im-agent-hub 提供相同语义:

  • agents:多个 Agent(每个有 idrun,可选 meta 做 per-agent 配置)。
  • bindingsAgentRouteBinding[],每项为 { agentId, match }match 支持:channelaccountId?peer?guildId?(Discord)、teamId?(Slack)、roles?(角色列表)。
  • 匹配顺序(与主项目一致):peer 精确guildId + rolesguildIdteamIdaccountchanneldefaultAgentId

方式一:用 createRouterFromBindings 自己组 Hub

import {
  createHub,
  createRouterFromBindings,
  createDingTalkAdapter,
  createInMemorySessionStore,
  createLangChainLikeAgent,
} from "@ylib/syim";

const agents = [
  createLangChainLikeAgent({ id: "main", runnable: async ({ messages }) => ({ content: "主助手回复" }) }),
  createLangChainLikeAgent({ id: "support", runnable: async ({ messages }) => ({ content: "客服回复" }) }),
];

const hub = createHub({
  router: createRouterFromBindings({
    defaultAgentId: "main",
    bindings: [
      { agentId: "support", match: { channel: "dingtalk", peer: { kind: "group", id: "群openConversationId" } } },
      { agentId: "main", match: { channel: "dingtalk" } },
    ],
  }),
  agents,
  adapters: [createDingTalkAdapter({ config: { clientId: "...", clientSecret: "..." } })],
  sessions: createInMemorySessionStore(),
});

方式二:用 createHubFromConfig 一次传入配置

import { createHubFromConfig, createDingTalkAdapter, createInMemorySessionStore, createLangChainLikeAgent } from "@ylib/syim";

const hub = createHubFromConfig({
  defaultAgentId: "main",
  agents: [
    createLangChainLikeAgent({ id: "main", runnable: async ({ messages }) => ({ content: "主助手" }) }),
    createLangChainLikeAgent({ id: "support", runnable: async ({ messages }) => ({ content: "客服" }) }),
  ],
  adapters: [createDingTalkAdapter({ config: { clientId: "...", clientSecret: "..." } })],
  bindings: [
    { agentId: "support", match: { channel: "dingtalk", peer: { kind: "group", id: "某群ID" } } },
    { agentId: "main", match: { channel: "dingtalk" } },
  ],
  sessions: createInMemorySessionStore(),
});

同一钉钉账号下:该群消息走 support,其他单聊/群走 main

Session 持久化与 HTTP 入口(与主项目能力对齐)

  • Session 持久化createFileSessionStore({ dir? }) 将会话落盘(默认 ~/.openclaw/im-agent-hub/sessions),pm2 重启后会话可恢复。pnpm run dev 时设置 PERSIST_SESSIONS=1SESSION_DIR=/path 即可启用。
  • HTTP 入口createInboundServer({ hub, healthPath?, auth? }) / startInboundServer({ hub, port }) 提供 GET /healthPOST /inbound;可选 auth: { type: 'bearer' | 'api-key', token }

媒体、记忆与工具(与主项目能力对齐)

  • 入站附件downloadInboundAttachments(input, { dir?, timeoutMs? })input.attachments 中带 url 的项下载到本地并填回 path,再交 hub.handleInbound
  • 出站附件OutboundMessage.attachments 可带 { path, contentType?, fileName? },钉钉 adapter 会上传后发文件消息。
  • 记忆HubConfig.memory 传入 MemoryStore(如 createInMemoryMemoryStore()),hub 会注入到 agent.run({ memory })
  • 工具HubConfig.toolsRecord<string, (args) => Promise<string>>;agent 返回 toolCalls 时 hub 执行并拼入回复。
  • 白名单HubConfig.allowFrom?: (input: InboundMessage) => boolean,仅当返回 true 时处理消息。

如何测试(单 agent 收所有 IM)

用「一个简单 agent 接收所有 IM 消息」来验证 Hub 与路由是否正常。

1. 启动示例服务

packages/im-agent-hub 下执行:

cd packages/im-agent-hub
pnpm install
# 可选:配置钉钉后回复会真实发到钉钉;不配置则仅 HTTP 测试,回复在控制台打印
DINGTALK_CLIENT_ID=你的AppKey DINGTALK_CLIENT_SECRET=你的AppSecret pnpm run example:single-agent

服务监听 http://localhost:3100(可用 PORT=3000 改端口)。

2. 用 curl 模拟一条 IM 消息

任意一条「入站消息」都会路由到同一个 agent(main),并得到回复 [测试 agent] 收到:xxx

单聊示例(把 YOUR_STAFF_ID 换成你的钉钉 userId):

curl -X POST http://localhost:3100/inbound -H "Content-Type: application/json" -d '{
  "provider": "dingtalk",
  "peer": { "kind": "direct", "id": "YOUR_STAFF_ID" },
  "sender": { "id": "YOUR_STAFF_ID", "name": "测试" },
  "messageId": "test-1",
  "timestamp": 1700000000000,
  "text": "你好"
}'

成功时返回 {"ok":true,"text":"你好","agentId":"main"}。若已配置钉钉,钉钉端会收到 [测试 agent] 收到:你好

3. 测试 /new、/reset

再发一条,内容为 /new/reset

curl -X POST http://localhost:3100/inbound -H "Content-Type: application/json" -d '{"provider":"dingtalk","peer":{"kind":"direct","id":"YOUR_STAFF_ID"},"sender":{"id":"YOUR_STAFF_ID","name":"测试"},"messageId":"test-2","timestamp":1700000000000,"text":"/new"}'

会返回成功,且若配置了钉钉会收到「已开启新的上下文。」。

4. 用真实钉钉收消息

若要让「钉钉里发消息 → 同一 agent 回复」,可再开一个终端跑 Stream 收消息(与上面示例共用同一套 agent 逻辑):

DINGTALK_CLIENT_ID=xxx DINGTALK_CLIENT_SECRET=yyy pnpm run start:dingtalk

钉钉里给机器人发消息,会由 start:dingtalk 里的 Hub 处理并回发(该脚本内也是单 agent,逻辑与 example-single-agent 一致)。

使用 iflow 接口作为 Agent

若后端是 iflow(OpenAI 兼容) 接口,可用内置的 createIflowAgent,让所有 IM 消息对接该 agent。

环境变量

| 变量 | 必填 | 说明 | |------|------|------| | IFLOW_API_KEY | 是 | API Key,请勿写入代码,仅通过环境变量传入 | | IFLOW_BASE_URL | 否 | 默认 http://92.113.117.22:10068/proxy/iflow |

一键跑示例(HTTP + 可选钉钉回发)

cd packages/im-agent-hub
export IFLOW_API_KEY=sk-你的key
# 可选:钉钉回发
export DINGTALK_CLIENT_ID=xxx
export DINGTALK_CLIENT_SECRET=yyy
pnpm run example:iflow-agent

然后 curl -X POST http://localhost:3100/inbound ... 或钉钉内发消息,回复由 iflow 接口生成。

在代码中使用

import { createHubFromConfig, createIflowAgent, createDingTalkAdapter, createInMemorySessionStore } from "@ylib/syim";

const hub = createHubFromConfig({
  defaultAgentId: "main",
  agents: [
    createIflowAgent({
      id: "main",
      systemPrompt: "你是助手。",
      historyLimit: 20,
      // baseUrl / apiKey 不传则从环境变量 IFLOW_BASE_URL、IFLOW_API_KEY 读取
    }),
  ],
  adapters: [createDingTalkAdapter({ config: { clientId: "...", clientSecret: "..." } })],
  bindings: [{ agentId: "main", match: { channel: "dingtalk" } }],
  sessions: createInMemorySessionStore(),
});

本地测试钉钉 IM

不跑完整 OpenClaw 和钉钉 connector 时,可以用包里的开发服务器只测「Hub + 钉钉发送」是否正常。

1. 安装依赖

在 openclaw 仓库根目录执行:

pnpm install

2. 配置钉钉应用

3. 启动开发服务器

packages/im-agent-hub 下执行(需先 pnpm install 以安装 tsx):

cd packages/im-agent-hub
DINGTALK_CLIENT_ID=你的AppKey DINGTALK_CLIENT_SECRET=你的AppSecret pnpm run dev

或先导出环境变量再启动:

export DINGTALK_CLIENT_ID=你的AppKey
export DINGTALK_CLIENT_SECRET=你的AppSecret
pnpm run dev

服务会在 http://localhost:3100 启动(可通过 PORT=3000 pnpm run dev 改端口)。

4. 发送测试消息

  • 单聊peer.id 填你的钉钉 staffId(或 unionId,视应用权限而定)。
  • 群聊peer{ "kind": "group", "id": "群的 openConversationId" }

示例(单聊,把 YOUR_STAFF_ID 换成真实 userId):

curl -X POST http://localhost:3100/inbound \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "dingtalk",
    "peer": { "kind": "direct", "id": "YOUR_STAFF_ID" },
    "sender": { "id": "YOUR_STAFF_ID", "name": "测试" },
    "messageId": "test-1",
    "timestamp": 1700000000000,
    "text": "你好"
  }'

成功时接口返回 {"ok":true,"text":"你好"},钉钉端会收到机器人回复(当前 dev-server 为 echo 风格:「收到:你好」)。

5. 测试 /new、/reset

对同一 peer 再发一条:

curl -X POST http://localhost:3100/inbound \
  -H "Content-Type: application/json" \
  -d '{"provider":"dingtalk","peer":{"kind":"direct","id":"YOUR_STAFF_ID"},"sender":{"id":"YOUR_STAFF_ID","name":"测试"},"messageId":"test-2","timestamp":1700000000000,"text":"/new"}'

钉钉会收到「已开启新的上下文。」,后续消息会使用新的会话上下文。

钉钉完整收发流程(Stream 收消息 + Hub 回发)

不启动 OpenClaw 的情况下,用与官方 dingtalk-openclaw-connector 相同的 Stream 模式收钉钉消息,由 im-agent-hub 处理并回发。

1. 钉钉应用配置

  • 钉钉开放平台 创建企业内部应用,开启机器人能力。
  • 消息接收方式选择 Stream 模式(与官方 connector 一致),并保存 AppKeyAppSecret
  • 为应用开通「机器人发送消息到群聊」「机器人发送单聊消息」等权限。

2. 启动 Stream 收消息服务

packages/im-agent-hub 下执行:

cd packages/im-agent-hub
pnpm install
DINGTALK_CLIENT_ID=你的AppKey DINGTALK_CLIENT_SECRET=你的AppSecret pnpm run start:dingtalk

可选:DEBUG=1 可打开 dingtalk-stream 调试日志。

3. 在钉钉里发消息

  • 单聊:在钉钉里找到你的机器人,发「你好」等文字,机器人会经 Hub 回复(当前为 echo:「收到:你好」)。
  • 群聊:把机器人拉进群,在群里 @机器人 或发消息(视钉钉机器人设置),同样会走 Hub 回发。
  • 发送 /new/reset 可重开会话上下文。

4. 自定义 Agent(如接 LLM)

当前 scripts/dingtalk-stream-server.ts 里使用的是内置 echo 风格 agent。要接真实 LLM,可复制该脚本到自己的项目,或修改脚本中的 createLangChainLikeAgentrunnable,改为调用你的 LangChain / OpenAI 等接口。

Connector 模式(多账号、项目配置)

pnpm run start:connector 使用官方 dingtalk-openclaw-connector,支持多企业钉钉部署。配置读取优先级:

  1. 项目目录 openclaw.json(与 package.json 同目录)
  2. ~/.openclaw/openclaw.json
  3. 环境变量 DINGTALK_CLIENT_ID / DINGTALK_CLIENT_SECRET(单账号)

在项目根目录创建或复制 openclaw.json:至少配置 channels.dingtalk-connectordingtalk-stdio-bridge 与精简 openclaw.json(仅 channels)即可工作;若使用 start:connector 且需多 Agent 路由,再按需增加 bindings 等键。

将 /v1/chat/completions 代理到 yuce-gpt(可选)

若希望 对话能力由 yuce-gpt 提供(复用其 Agent、技能、记忆等),可将 im-agent-hub 的 POST /v1/chat/completions 配置为反向代理到 yuce-gpt:

  • yuce-gpt 端点POST https://<yuce-gpt-host>/api/v1/yucegpt/v1/chat/completions
  • 鉴权:在代理请求头中增加 Authorization: Bearer <OPENCLAW_GATEWAY_API_KEY>(yuce-gpt 侧需配置同名环境变量 OPENCLAW_GATEWAY_API_KEYOPENCLAW_GATEWAY_TENANT_ID / USER_ID / USER_NAME)。
  • 透传:将 X-OpenClaw-Agent-IdX-OpenClaw-Session-Key 及 body 原样转发。

实现方式:在 createInboundServer 中若启用 enableChatCompletions,可改为不调用本地 createChatCompletionsHandler({ hub }),而是将请求 proxy 到上述 yuce-gpt URL(需自行用 fetch/axios 或 nginx 做反向代理)。详见 yuce-gpt 仓库文档:doc/OpenClaw与ACP会话形式对接说明.md

与 OpenClaw / 钉钉 connector 的关系

  • im-agent-hub:提供「会话路由 + 上下文 + 多 adapter 发送」;钉钉发送逻辑复制自官方 connector 的 OpenAPI。
  • 完整收发可选两种方式:
    1. 本包内置pnpm run start:dingtalk 使用 dingtalk-stream 收消息,交给 hub 处理并用本包钉钉 adapter 回发(无需 OpenClaw)。
    2. Connector 模式pnpm run start:connector 加载 dingtalk-openclaw-connector,支持多账号,配置从项目 openclaw.json 读取。