一、sessions_list 工具
1.1 工具概述
功能 :列出所有会话(包括子 agent 会话)
核心特性:
- 支持按类型过滤(main/group/cron/hook/node/other)
- 支持按活跃度过滤(activeMinutes)
- 可包含最后 N 条消息(messageLimit,最大 20)
- 会话可见性检查(visibility guard)
- 沙盒隔离支持
1.2 Schema 定义
位置:第 109015 行
javascript
const SessionsListToolSchema = 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 行
javascript
function createSessionsListTool(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 ? new Set(allowedKindsList) : void 0;
// 4. 解析其他参数
const limit = typeof params.limit === "number" && Number.isFinite(params.limit) ?
Math.max(1, Math.floor(params.limit)) : void 0;
const activeMinutes = typeof params.activeMinutes === "number" && Number.isFinite(params.activeMinutes) ?
Math.max(1, Math.floor(params.activeMinutes)) : void 0;
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 = await gatewayCall({
method: "sessions.list",
params: {
limit,
activeMinutes,
includeGlobal: !restrictToSpawned,
includeUnknown: !restrictToSpawned,
spawnedBy: restrictToSpawned ? effectiveRequesterKey : void 0
}
});
const sessions = Array.isArray(list?.sessions) ? list.sessions : [];
const storePath = typeof list?.path === "string" ? list.path : void 0;
// 6. 创建可见性检查器
const visibilityGuard = await createSessionVisibilityGuard({
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 : void 0,
alias,
mainKey
});
// 类型过滤
if (allowedKinds && !allowedKinds.has(kind)) continue;
// 解析显示键
const displayKey = resolveDisplaySessionKey({
key,
alias,
mainKey
});
// 8. 解析渠道和交付上下文
const entryChannel = typeof entry.channel === "string" ? entry.channel : void 0;
const deliveryContext = entry.deliveryContext && typeof entry.deliveryContext === "object" ?
entry.deliveryContext : void 0;
const deliveryChannel = typeof deliveryContext?.channel === "string" ?
deliveryContext.channel : void 0;
const deliveryTo = typeof deliveryContext?.to === "string" ? deliveryContext.to : void 0;
const deliveryAccountId = typeof deliveryContext?.accountId === "string" ?
deliveryContext.accountId : void 0;
const lastChannel = deliveryChannel ?? (typeof entry.lastChannel === "string" ? entry.lastChannel : void 0);
const lastAccountId = deliveryAccountId ?? (typeof entry.lastAccountId === "string" ? entry.lastAccountId : void 0);
const derivedChannel = deriveChannel({
key,
kind,
channel: entryChannel,
lastChannel
});
// 9. 解析会话元数据
const sessionId = typeof entry.sessionId === "string" ? entry.sessionId : void 0;
const sessionFileRaw = entry.sessionFile;
const sessionFile = typeof sessionFileRaw === "string" ? sessionFileRaw : void 0;
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 });
} else if (path.isAbsolute(trimmedStorePath)) {
effectiveStorePath = trimmedStorePath;
}
}
const filePathOpts = resolveSessionFilePathOptions({
agentId,
storePath: effectiveStorePath
});
transcriptPath = resolveSessionFilePath(sessionId, sessionFile ? { sessionFile } : void 0, filePathOpts);
} catch {
transcriptPath = void 0;
}
}
// 10. 构建会话行
const row = {
key: displayKey,
kind,
channel: derivedChannel,
label: typeof entry.label === "string" ? entry.label : void 0,
displayName: typeof entry.displayName === "string" ? entry.displayName : void 0,
deliveryContext: deliveryChannel || deliveryTo || deliveryAccountId ? {
channel: deliveryChannel,
to: deliveryTo,
accountId: deliveryAccountId
} : void 0,
updatedAt: typeof entry.updatedAt === "number" ? entry.updatedAt : void 0,
sessionId,
model: typeof entry.model === "string" ? entry.model : void 0,
contextTokens: typeof entry.contextTokens === "number" ? entry.contextTokens : void 0,
totalTokens: typeof entry.totalTokens === "number" ? entry.totalTokens : void 0,
estimatedCostUsd: typeof entry.estimatedCostUsd === "number" ? entry.estimatedCostUsd : void 0,
status: typeof entry.status === "string" ? entry.status : void 0,
startedAt: typeof entry.startedAt === "number" ? entry.startedAt : void 0,
endedAt: typeof entry.endedAt === "number" ? entry.endedAt : void 0,
runtimeMs: typeof entry.runtimeMs === "number" ? entry.runtimeMs : void 0,
childSessions: Array.isArray(entry.childSessions) ?
entry.childSessions.filter((value) => typeof value === "string")
.map((value) => resolveDisplaySessionKey({ key: value, alias, mainKey })) : void 0,
thinkingLevel: typeof entry.thinkingLevel === "string" ? entry.thinkingLevel : void 0,
verboseLevel: typeof entry.verboseLevel === "string" ? entry.verboseLevel : void 0,
systemSent: typeof entry.systemSent === "boolean" ? entry.systemSent : void 0,
abortedLastRun: typeof entry.abortedLastRun === "boolean" ? entry.abortedLastRun : void 0,
sendPolicy: typeof entry.sendPolicy === "string" ? entry.sendPolicy : void 0,
lastChannel,
lastTo: deliveryTo ?? (typeof entry.lastTo === "string" ? entry.lastTo : void 0),
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;
const worker = async () => {
while (true) {
const next = index;
index += 1;
if (next >= historyTargets.length) return;
const target = historyTargets[next];
const history = await gatewayCall({
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;
}
};
await Promise.all(Array.from({ length: maxConcurrent }, () => worker()));
}
return jsonResult({
count: rows.length,
sessions: rows
});
}
};
}
1.4 会话类型分类
javascript
function classifySessionKind(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 可见性检查
javascript
const visibilityGuard = await createSessionVisibilityGuard({
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 返回结果格式
json
{
"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 行
javascript
const SessionsHistoryToolSchema = Type.Object({
sessionKey: Type.String(),
limit: Type.Optional(Type.Number({ minimum: 1 })),
includeTools: Type.Optional(Type.Boolean())
});
// 硬性限制
const SESSIONS_HISTORY_MAX_BYTES = 80 * 1024; // 80KB
const SESSIONS_HISTORY_TEXT_MAX_CHARS = 4000; // 4000 字符
2.3 文本截断函数
位置:第 108798 行
javascript
function truncateHistoryText(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 行
javascript
function sanitizeHistoryContentBlock(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 : void 0;
const bytes = data ? data.length : void 0;
if ("data" in entry) {
delete entry.data; // 删除 Base64 数据
truncated = true;
}
entry.omitted = true; // 标记为已省略
if (bytes !== void 0) entry.bytes = bytes; // 保留大小信息
}
return { block: entry, truncated, redacted };
}
2.5 消息清理函数
位置:第 108879 行
javascript
function sanitizeHistoryMessage(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;
} else if (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 行
javascript
function enforceSessionsHistoryHardCap(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 行
javascript
function createSessionsHistoryTool(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 = await resolveSessionReference({
sessionKey: sessionKeyParam,
alias,
mainKey,
requesterInternalKey: effectiveRequesterKey,
restrictToSpawned
});
if (!resolvedSession.ok) {
return jsonResult({
status: resolvedSession.status,
error: resolvedSession.error
});
}
// 4. 检查可见性
const visibleSession = await resolveVisibleSessionReference({
resolvedSession,
requesterSessionKey: effectiveRequesterKey,
restrictToSpawned,
visibilitySessionKey: sessionKeyParam
});
if (!visibleSession.ok) {
return jsonResult({
status: visibleSession.status,
error: visibleSession.error
});
}
const resolvedKey = visibleSession.key;
const displayKey = visibleSession.displayKey;
// 5. 创建可见性检查器
const a2aPolicy = createAgentToAgentPolicy(cfg);
const access = (await createSessionVisibilityGuard({
action: "history",
requesterSessionKey: effectiveRequesterKey,
visibility: resolveEffectiveSessionToolsVisibility({
cfg,
sandboxed: opts?.sandboxed === true
}),
a2aPolicy
})).check(resolvedKey);
if (!access.allowed) {
return jsonResult({
status: access.status,
error: access.error
});
}
// 6. 解析参数
const limit = typeof params.limit === "number" && Number.isFinite(params.limit) ?
Math.max(1, Math.floor(params.limit)) : void 0;
const includeTools = Boolean(params.includeTools);
// 7. 获取历史消息
const result = await gatewayCall({
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
});
return jsonResult({
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 返回结果格式
正常:
json
{
"sessionKey": "main",
"messages": [
{
"role": "user",
"content": "Hello",
"timestamp": 1711716000000
},
{
"role": "assistant",
"content": "Hi there!",
"timestamp": 1711716001000
}
],
"hardCapped": false,
"totalBytes": 5000
}
被截断:
json
{
"sessionKey": "main",
"messages": [...],
"hardCapped": true,
"totalBytes": 81920
}
图片消息:
json
{
"role": "user",
"content": [{
"type": "image",
"omitted": true,
"bytes": 1048576
}],
"timestamp": 1711716000000
}
三、关键机制对比
3.1 过滤能力
| 特性 | sessions_list | sessions_history |
|---|---|---|
| 类型过滤 | kinds 数组 | 不支持 |
| 活跃度过滤 | activeMinutes | 不支持 |
| 数量限制 | limit | limit |
| 消息获取 | messageLimit(最大 20) | 全部消息 |
3.2 安全限制
| 限制类型 | sessions_list | sessions_history |
|---|---|---|
| 可见性检查 | visibilityGuard | visibilityGuard |
| 沙盒隔离 | restrictToSpawned | restrictToSpawned |
| 内容脱敏 | 不需要 | redactSensitiveText |
| 硬性限制 | 无 | 80KB |
3.3 并发处理
| 特性 | sessions_list | sessions_history |
|---|---|---|
| 并发获取 | 最多 4 个并发 | 单次获取 |
| 并发目标 | messageLimit > 0 的会话 | 不适用 |
四、使用示例
4.1 sessions_list 工具调用
用户 :列出最近 10 分钟活跃的主会话
大模型返回:
json
{
"tool_call": {
"name": "sessions_list",
"arguments": {
"kinds": ["main"],
"activeMinutes": 10,
"limit": 10
}
}
}
执行结果:
json
{
"count": 1,
"sessions": [{
"key": "main",
"kind": "main",
"channel": "feishu",
"updatedAt": 1711716000000,
"status": "idle"
}]
}
4.2 sessions_history 工具调用
用户 :获取主会话的最后 20 条消息
大模型返回:
json
{
"tool_call": {
"name": "sessions_history",
"arguments": {
"sessionKey": "main",
"limit": 20
}
}
}
执行结果:
json
{
"sessionKey": "main",
"messages": [
{ "role": "user", "content": "Hello" },
{ "role": "assistant", "content": "Hi there!" }
],
"hardCapped": false,
"totalBytes": 5000
}