一、sessions_list 工具
1.1 工具概述
功能:列出所有会话(包括子 agent 会话)核心特性:
• 支持按类型过滤(main/group/cron/hook/node/other) • 支持按活跃度过滤(activeMinutes) • 可包含最后 N 条消息(messageLimit,最大 20) • 会话可见性检查(visibility guard) • 沙盒隔离支持
1.2 Schema 定义
位置:第 109015 行
constSessionsListToolSchema = Type.Object({kinds: Type.Optional(Type.Array(Type.String())),limit: Type.Optional(Type.Number({ minimum: 1 })),activeMinutes: Type.Optional(Type.Number({ minimum: 1 })),messageLimit: Type.Optional(Type.Number({ minimum: 0 }))});1.3 完整执行代码
位置:第 109015 行
functioncreateSessionsListTool(opts) {return {label: "Sessions",name: "sessions_list",description: "List sessions with optional filters and last messages.",parameters: SessionsListToolSchema,execute: async (_toolCallId, args) => {const params = args;const cfg = opts?.config ?? loadConfig();// 1. 解析沙盒会话工具上下文const { mainKey, alias, requesterInternalKey, restrictToSpawned } = resolveSandboxedSessionToolContext({ cfg,agentSessionKey: opts?.agentSessionKey,sandboxed: opts?.sandboxed });const effectiveRequesterKey = requesterInternalKey ?? alias;// 2. 解析会话可见性const visibility = resolveEffectiveSessionToolsVisibility({ cfg,sandboxed: opts?.sandboxed === true });// 3. 解析类型过滤const allowedKindsList = (readStringArrayParam(params, "kinds")?.map((value) => value.trim().toLowerCase() ) ?? []).filter((value) => ["main", "group", "cron", "hook", "node", "other" ].includes(value));const allowedKinds = allowedKindsList.length ? newSet(allowedKindsList) : void0;// 4. 解析其他参数const limit = typeof params.limit === "number" && Number.isFinite(params.limit) ? Math.max(1, Math.floor(params.limit)) : void0;const activeMinutes = typeof params.activeMinutes === "number" && Number.isFinite(params.activeMinutes) ? Math.max(1, Math.floor(params.activeMinutes)) : void0;const messageLimitRaw = typeof params.messageLimit === "number" && Number.isFinite(params.messageLimit) ? Math.max(0, Math.floor(params.messageLimit)) : 0;const messageLimit = Math.min(messageLimitRaw, 20); // 最大 20 条// 5. 调用 Gateway 获取会话列表const gatewayCall = opts?.callGateway ?? callGateway;const list = awaitgatewayCall({method: "sessions.list",params: { limit, activeMinutes,includeGlobal: !restrictToSpawned,includeUnknown: !restrictToSpawned,spawnedBy: restrictToSpawned ? effectiveRequesterKey : void0 } });const sessions = Array.isArray(list?.sessions) ? list.sessions : [];const storePath = typeof list?.path === "string" ? list.path : void0;// 6. 创建可见性检查器const visibilityGuard = awaitcreateSessionVisibilityGuard({action: "list",requesterSessionKey: effectiveRequesterKey, visibility,a2aPolicy: createAgentToAgentPolicy(cfg) });const rows = [];const historyTargets = [];// 7. 处理每个会话for (const entry of sessions) {if (!entry || typeof entry !== "object") continue;const key = typeof entry.key === "string" ? entry.key : "";if (!key) continue;// 可见性检查if (!visibilityGuard.check(key).allowed) continue;if (key === "unknown") continue;if (key === "global" && alias !== "global") continue;// 分类会话类型const kind = classifySessionKind({ key,gatewayKind: typeof entry.kind === "string" ? entry.kind : void0, alias, mainKey });// 类型过滤if (allowedKinds && !allowedKinds.has(kind)) continue;// 解析显示键const displayKey = resolveDisplaySessionKey({ key, alias, mainKey });// 8. 解析渠道和交付上下文const entryChannel = typeof entry.channel === "string" ? entry.channel : void0;const deliveryContext = entry.deliveryContext && typeof entry.deliveryContext === "object" ? entry.deliveryContext : void0;const deliveryChannel = typeof deliveryContext?.channel === "string" ? deliveryContext.channel : void0;const deliveryTo = typeof deliveryContext?.to === "string" ? deliveryContext.to : void0;const deliveryAccountId = typeof deliveryContext?.accountId === "string" ? deliveryContext.accountId : void0;const lastChannel = deliveryChannel ?? (typeof entry.lastChannel === "string" ? entry.lastChannel : void0);const lastAccountId = deliveryAccountId ?? (typeof entry.lastAccountId === "string" ? entry.lastAccountId : void0);const derivedChannel = deriveChannel({ key, kind,channel: entryChannel, lastChannel });// 9. 解析会话元数据const sessionId = typeof entry.sessionId === "string" ? entry.sessionId : void0;const sessionFileRaw = entry.sessionFile;const sessionFile = typeof sessionFileRaw === "string" ? sessionFileRaw : void0;let transcriptPath;if (sessionId) {try {const agentId = resolveAgentIdFromSessionKey(key);const trimmedStorePath = storePath?.trim();let effectiveStorePath;if (trimmedStorePath && trimmedStorePath !== "(multiple)") {if (trimmedStorePath.includes("{agentId}") || trimmedStorePath.startsWith("~")) { effectiveStorePath = resolveStorePath(trimmedStorePath, { agentId }); } elseif (path.isAbsolute(trimmedStorePath)) { effectiveStorePath = trimmedStorePath; } }const filePathOpts = resolveSessionFilePathOptions({ agentId,storePath: effectiveStorePath }); transcriptPath = resolveSessionFilePath(sessionId, sessionFile ? { sessionFile } : void0, filePathOpts); } catch { transcriptPath = void0; } }// 10. 构建会话行const row = {key: displayKey, kind,channel: derivedChannel,label: typeof entry.label === "string" ? entry.label : void0,displayName: typeof entry.displayName === "string" ? entry.displayName : void0,deliveryContext: deliveryChannel || deliveryTo || deliveryAccountId ? {channel: deliveryChannel,to: deliveryTo,accountId: deliveryAccountId } : void0,updatedAt: typeof entry.updatedAt === "number" ? entry.updatedAt : void0, sessionId,model: typeof entry.model === "string" ? entry.model : void0,contextTokens: typeof entry.contextTokens === "number" ? entry.contextTokens : void0,totalTokens: typeof entry.totalTokens === "number" ? entry.totalTokens : void0,estimatedCostUsd: typeof entry.estimatedCostUsd === "number" ? entry.estimatedCostUsd : void0,status: typeof entry.status === "string" ? entry.status : void0,startedAt: typeof entry.startedAt === "number" ? entry.startedAt : void0,endedAt: typeof entry.endedAt === "number" ? entry.endedAt : void0,runtimeMs: typeof entry.runtimeMs === "number" ? entry.runtimeMs : void0,childSessions: Array.isArray(entry.childSessions) ? entry.childSessions.filter((value) =>typeof value === "string") .map((value) =>resolveDisplaySessionKey({ key: value, alias, mainKey })) : void0,thinkingLevel: typeof entry.thinkingLevel === "string" ? entry.thinkingLevel : void0,verboseLevel: typeof entry.verboseLevel === "string" ? entry.verboseLevel : void0,systemSent: typeof entry.systemSent === "boolean" ? entry.systemSent : void0,abortedLastRun: typeof entry.abortedLastRun === "boolean" ? entry.abortedLastRun : void0,sendPolicy: typeof entry.sendPolicy === "string" ? entry.sendPolicy : void0, lastChannel,lastTo: deliveryTo ?? (typeof entry.lastTo === "string" ? entry.lastTo : void0), lastAccountId, transcriptPath };// 11. 如果需要消息,添加到获取队列if (messageLimit > 0) {const resolvedKey = resolveInternalSessionKey({ key, alias, mainKey }); historyTargets.push({ row, resolvedKey }); } rows.push(row); }// 12. 并发获取消息(如果需要)if (messageLimit > 0 && historyTargets.length > 0) {const maxConcurrent = Math.min(4, historyTargets.length);let index = 0;constworker = async () => {while (true) {const next = index; index += 1;if (next >= historyTargets.length) return;const target = historyTargets[next];const history = awaitgatewayCall({method: "chat.history",params: {sessionKey: target.resolvedKey,limit: messageLimit } });const filtered = stripToolMessages(Array.isArray(history?.messages) ? history.messages : [] ); target.row.messages = filtered.length > messageLimit ? filtered.slice(-messageLimit) : filtered; } };awaitPromise.all(Array.from({ length: maxConcurrent }, () =>worker())); }returnjsonResult({count: rows.length,sessions: rows }); } };}1.4 会话类型分类
functionclassifySessionKind(params) {const { key, gatewayKind, alias, mainKey } = params;// 1. Gateway 已分类if (gatewayKind) return gatewayKind;// 2. 根据键分类if (key === "global") return"other";if (key === mainKey) return"main";if (key === alias) return"main";// 3. 根据前缀分类if (key.startsWith("cron:")) return"cron";if (key.startsWith("hook:")) return"hook";if (key.startsWith("node:")) return"node";if (key.startsWith("group:")) return"group";return"other";}1.5 可见性检查
const visibilityGuard = awaitcreateSessionVisibilityGuard({action: "list",requesterSessionKey: effectiveRequesterKey, visibility, // session_tools.visibility 配置a2aPolicy: createAgentToAgentPolicy(cfg) // Agent-to-Agent 策略});// 检查会话是否可见if (!visibilityGuard.check(key).allowed) continue;1.6 执行流程图
sessions_list 工具调用 ↓1. 解析沙盒上下文 ├─ mainKey, alias ├─ requesterInternalKey └─ restrictToSpawned ↓2. 解析会话可见性配置 ↓3. 解析类型过滤(kinds) ↓4. 解析其他参数(limit/activeMinutes/messageLimit) ↓5. 调用 Gateway 获取会话列表 ↓6. 创建可见性检查器 ↓7. 处理每个会话 ├─ 可见性检查 ├─ 类型分类 ├─ 类型过滤 ├─ 解析渠道信息 └─ 解析元数据 ↓8. 构建会话行对象 ↓9. 如果需要消息,添加到获取队列 ↓10. 并发获取消息(最多 4 个并发) ↓11. 返回结果1.7 返回结果格式
{"count":5,"sessions":[{"key":"main","kind":"main","channel":"feishu","label":"Main Session","displayName":"主会话","updatedAt":1711716000000,"sessionId":"abc123","model":"qwen3.5-plus","contextTokens":50000,"totalTokens":100000,"estimatedCostUsd":0.05,"status":"idle","startedAt":1711710000000,"runtimeMs":6000000,"thinkingLevel":"high","messages":[{"role":"user","content":"Hello"},{"role":"assistant","content":"Hi there!"}]}]}二、sessions_history 工具
2.1 工具概述
功能:获取指定会话的历史消息核心特性:
• 敏感内容脱敏(redact sensitive text) • 文本截断(最大 4000 字符) • 图片省略(只保留元数据) • 思考过程签名删除 • 使用/成本信息删除 • 80KB 硬性限制
2.2 Schema 定义
位置:第 108788 行
constSessionsHistoryToolSchema = Type.Object({sessionKey: Type.String(),limit: Type.Optional(Type.Number({ minimum: 1 })),includeTools: Type.Optional(Type.Boolean())});// 硬性限制constSESSIONS_HISTORY_MAX_BYTES = 80 * 1024; // 80KBconstSESSIONS_HISTORY_TEXT_MAX_CHARS = 4000; // 4000 字符2.3 文本截断函数
位置:第 108798 行
functiontruncateHistoryText(text) {// 1. 敏感内容脱敏const sanitized = redactSensitiveText(text);const redacted = sanitized !== text;// 2. 检查是否超过限制if (sanitized.length <= SESSIONS_HISTORY_TEXT_MAX_CHARS) {return {text: sanitized,truncated: false, redacted }; }// 3. 截断并添加标记return {text: `${truncateUtf16Safe(sanitized, SESSIONS_HISTORY_TEXT_MAX_CHARS)}\n…(truncated)…`,truncated: true, redacted };}2.4 内容块清理函数
位置:第 108816 行
functionsanitizeHistoryContentBlock(block) {if (!block || typeof block !== "object") {return { block, truncated: false, redacted: false }; }const entry = { ...block };let truncated = false;let redacted = false;const type = typeof entry.type === "string" ? entry.type : "";// 1. 文本内容if (typeof entry.text === "string") {const res = truncateHistoryText(entry.text); entry.text = res.text; truncated ||= res.truncated; redacted ||= res.redacted; }// 2. 思考过程if (type === "thinking") {if (typeof entry.thinking === "string") {const res = truncateHistoryText(entry.thinking); entry.thinking = res.text; truncated ||= res.truncated; redacted ||= res.redacted; }// 删除思考签名(安全考虑)if ("thinkingSignature"in entry) {delete entry.thinkingSignature; truncated = true; } }// 3. 部分 JSON(工具调用)if (typeof entry.partialJson === "string") {const res = truncateHistoryText(entry.partialJson); entry.partialJson = res.text; truncated ||= res.truncated; redacted ||= res.redacted; }// 4. 图片(省略数据,只保留元数据)if (type === "image") {const data = typeof entry.data === "string" ? entry.data : void0;const bytes = data ? data.length : void0;if ("data"in entry) {delete entry.data; // 删除 Base64 数据 truncated = true; } entry.omitted = true; // 标记为已省略if (bytes !== void0) entry.bytes = bytes; // 保留大小信息 }return { block: entry, truncated, redacted };}2.5 消息清理函数
位置:第 108879 行
functionsanitizeHistoryMessage(message) {if (!message || typeof message !== "object") {return { message, truncated: false, redacted: false }; }const entry = { ...message };let truncated = false;let redacted = false;// 1. 删除敏感元数据if ("details"in entry) {delete entry.details; truncated = true; }if ("usage"in entry) {delete entry.usage; // token 使用量 truncated = true; }if ("cost"in entry) {delete entry.cost; // 成本 truncated = true; }// 2. 处理文本内容if (typeof entry.content === "string") {const res = truncateHistoryText(entry.content); entry.content = res.text; truncated ||= res.truncated; redacted ||= res.redacted; } elseif (Array.isArray(entry.content)) {const updated = entry.content.map((block) =>sanitizeHistoryContentBlock(block)); entry.content = updated.map((item) => item.block); truncated ||= updated.some((item) => item.truncated); redacted ||= updated.some((item) => item.redacted); }// 3. 处理 text 字段if (typeof entry.text === "string") {const res = truncateHistoryText(entry.text); entry.text = res.text; truncated ||= res.truncated; redacted ||= res.redacted; }return { message: entry, truncated, redacted };}2.6 硬性限制函数
位置:第 108921 行
functionenforceSessionsHistoryHardCap(params) {// 1. 检查是否超过限制if (params.bytes <= params.maxBytes) {return {items: params.items,bytes: params.bytes,hardCapped: false }; }// 2. 尝试只保留最后一条消息const last = params.items.at(-1);const lastOnly = last ? [last] : [];const lastBytes = jsonUtf8Bytes(lastOnly);if (lastBytes <= params.maxBytes) {return {items: lastOnly,bytes: lastBytes,hardCapped: true }; }// 3. 如果最后一条也太大,返回占位符const placeholder = [{role: "assistant",content: "[sessions_history omitted: message too large]" }];return {items: placeholder,bytes: jsonUtf8Bytes(placeholder),hardCapped: true };}2.7 完整执行代码
位置:第 108941 行
functioncreateSessionsHistoryTool(opts) {return {label: "Session History",name: "sessions_history",description: "Fetch message history for a session.",parameters: SessionsHistoryToolSchema,execute: async (_toolCallId, args) => {const params = args;const gatewayCall = opts?.callGateway ?? callGateway;// 1. 解析 sessionKey(必填)const sessionKeyParam = readStringParam$1(params, "sessionKey", { required: true });const cfg = opts?.config ?? loadConfig();// 2. 解析沙盒上下文const { mainKey, alias, effectiveRequesterKey, restrictToSpawned } = resolveSandboxedSessionToolContext({ cfg,agentSessionKey: opts?.agentSessionKey,sandboxed: opts?.sandboxed });// 3. 解析会话引用const resolvedSession = awaitresolveSessionReference({sessionKey: sessionKeyParam, alias, mainKey,requesterInternalKey: effectiveRequesterKey, restrictToSpawned });if (!resolvedSession.ok) {returnjsonResult({status: resolvedSession.status,error: resolvedSession.error }); }// 4. 检查可见性const visibleSession = awaitresolveVisibleSessionReference({ resolvedSession,requesterSessionKey: effectiveRequesterKey, restrictToSpawned,visibilitySessionKey: sessionKeyParam });if (!visibleSession.ok) {returnjsonResult({status: visibleSession.status,error: visibleSession.error }); }const resolvedKey = visibleSession.key;const displayKey = visibleSession.displayKey;// 5. 创建可见性检查器const a2aPolicy = createAgentToAgentPolicy(cfg);const access = (awaitcreateSessionVisibilityGuard({action: "history",requesterSessionKey: effectiveRequesterKey,visibility: resolveEffectiveSessionToolsVisibility({ cfg,sandboxed: opts?.sandboxed === true }), a2aPolicy })).check(resolvedKey);if (!access.allowed) {returnjsonResult({status: access.status,error: access.error }); }// 6. 解析参数const limit = typeof params.limit === "number" && Number.isFinite(params.limit) ? Math.max(1, Math.floor(params.limit)) : void0;const includeTools = Boolean(params.includeTools);// 7. 获取历史消息const result = awaitgatewayCall({method: "chat.history",params: {sessionKey: resolvedKey, limit } });const messages = Array.isArray(result?.messages) ? result.messages : [];// 8. 清理消息const sanitized = messages.map((msg) =>sanitizeHistoryMessage(msg));const cleanedMessages = sanitized.map((item) => item.message);// 9. 应用硬性限制const totalBytes = jsonUtf8Bytes(cleanedMessages);const capped = enforceSessionsHistoryHardCap({items: cleanedMessages,bytes: totalBytes,maxBytes: SESSIONS_HISTORY_MAX_BYTES });returnjsonResult({sessionKey: displayKey,messages: capped.items,hardCapped: capped.hardCapped,totalBytes: capped.bytes }); } };}2.8 执行流程图
sessions_history 工具调用 ↓1. 解析 sessionKey(必填) ↓2. 解析沙盒上下文 ↓3. 解析会话引用 ├─ 检查会话是否存在 └─ 解析内部键 ↓4. 检查可见性 ├─ 可见性配置 ├─ A2A 策略 └─ 访问检查 ↓5. 解析参数(limit/includeTools) ↓6. 调用 Gateway 获取历史消息 ↓7. 清理消息 ├─ 删除敏感元数据(usage/cost/details) ├─ 文本截断(4000 字符) ├─ 思考签名删除 └─ 图片数据省略 ↓8. 应用硬性限制(80KB) ├─ 未超限 → 返回全部 ├─ 超限 → 只保留最后一条 └─ 最后一条也超限 → 返回占位符 ↓9. 返回结果2.9 返回结果格式
正常:
{"sessionKey":"main","messages":[{"role":"user","content":"Hello","timestamp":1711716000000},{"role":"assistant","content":"Hi there!","timestamp":1711716001000}],"hardCapped":false,"totalBytes":5000}被截断:
{"sessionKey":"main","messages":[...],"hardCapped":true,"totalBytes":81920}图片消息:
{"role":"user","content":[{"type":"image","omitted":true,"bytes":1048576}],"timestamp":1711716000000}三、关键机制对比
3.1 过滤能力
| 类型过滤 | ||
| 活跃度过滤 | ||
| 数量限制 | ||
| 消息获取 |
3.2 安全限制
| 可见性检查 | ||
| 沙盒隔离 | ||
| 内容脱敏 | ||
| 硬性限制 |
3.3 并发处理
| 并发获取 | ||
| 并发目标 |
四、使用示例
4.1 sessions_list 工具调用
用户:列出最近 10 分钟活跃的主会话
大模型返回:
{"tool_call":{"name":"sessions_list","arguments":{"kinds":["main"],"activeMinutes":10,"limit":10}}}执行结果:
{"count":1,"sessions":[{"key":"main","kind":"main","channel":"feishu","updatedAt":1711716000000,"status":"idle"}]}4.2 sessions_history 工具调用
用户:获取主会话的最后 20 条消息
大模型返回:
{"tool_call":{"name":"sessions_history","arguments":{"sessionKey":"main","limit":20}}}执行结果:
{"sessionKey":"main","messages":[{"role":"user","content":"Hello"},{"role":"assistant","content":"Hi there!"}],"hardCapped":false,"totalBytes":5000}
夜雨聆风