Session
Session 是 OpenCode 中的核心会话对象,表示一个完整的对话上下文。
概述
Session 代表一次完整的对话交互,包含了从创建到当前状态的所有信息。它支持会话分支(通过 parentID)、压缩归档、分享和回滚等功能。
定义位置
packages/opencode/src/session/index.ts:42-82
属性说明
| 属性名 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | string | 是 | 会话唯一标识符,格式为 sess_xxxxx |
slug | string | 是 | URL友好的唯一标识符 |
projectID | string | 是 | 所属项目ID |
directory | string | 是 | 工作目录路径 |
parentID | string | 否 | 父会话ID(用于会话分支) |
title | string | 是 | 会话标题 |
version | string | 是 | OpenCode版本号 |
summary | object | 否 | 会话摘要信息 |
summary.additions | number | - | 新增代码行数 |
summary.deletions | number | - | 删除代码行数 |
summary.files | number | - | 修改文件数 |
summary.diffs | FileDiff[] | - | 具体差异详情 |
share | object | 否 | 分享信息 |
share.url | string | - | 分享URL |
time | object | 是 | 时间戳信息 |
time.created | number | - | 创建时间(Unix时间戳) |
time.updated | number | - | 最后更新时间(Unix时间戳) |
time.compacting | number | - | 压缩时间(可选) |
time.archived | number | - | 归档时间(可选) |
permission | PermissionRuleset | 否 | 权限规则集合 |
revert | object | 否 | 回滚信息 |
revert.messageID | string | - | 要回滚到的消息ID |
revert.partID | string | - | 要回滚到的Part ID(可选) |
revert.snapshot | string | - | 快照ID(可选) |
revert.diff | string | - | 差异字符串(可选) |
TypeScript 类型定义
export type Session = {
id: string
slug: string
projectID: string
directory: string
parentID?: string
summary?: {
additions: number
deletions: number
files: number
diffs?: Array<FileDiff>
}
share?: {
url: string
}
title: string
version: string
time: {
created: number
updated: number
compacting?: number
archived?: number
}
permission?: PermissionRuleset
revert?: {
messageID: string
partID?: string
snapshot?: string
diff?: string
}
}
事件
Session 支持以下事件:
| 事件名 | 类型 | 说明 |
|---|---|---|
session.created | info: Session | 会话创建 |
session.updated | info: Session | 会话更新 |
session.deleted | info: Session | 会话删除 |
session.diff | sessionID, diff: FileDiff[] | 会话差异更新 |
session.error | sessionID?, error | 会话错误 |
存储键结构
;["session", projectID, sessionID]
典型使用场景
1. 创建新会话
const session = await Session.create({
title: "Fix bug in login",
permission: permissionRuleset,
})
console.log(session.id) // sess_abc123
2. 创建子会话(分支)
const childSession = await Session.create({
parentID: parentSession.id,
title: "Alternative approach",
})
console.log(childSession.parentID) // sess_parent123
3. 更新会话标题
await Session.update(session.id, (draft) => {
draft.title = "Updated title: Fix bug in login"
})
4. 获取会话的子会话
const children = await Session.children(session.id)
console.log(`Found ${children.length} child sessions`)
5. 删除会话(级联删除所有子会话)
await Session.remove(session.id)
// 这将删除会话及其所有消息、parts 和子会话
父子会话关系
OpenCode 支持会话树状结构,通过 parentID 建立父子关系。
parentID 字段
定义位置:packages/opencode/src/session/index.ts:48
parentID?: string // 父会话 ID,根会话为空
作用:
- 建立会话层级关系
- 支持会话分支和嵌套
- 创建子会话时自动设置
会话树结构
获取子会话
方法:packages/opencode/src/session/index.ts:325
export const children = fn(Identifier.schema("session"), async (parentID) => {
const project = Instance.project
const result = [] as Session.Info[]
for (const item of await Storage.list(["session", project.id])) {
const session = await Storage.read<Info>(item)
if (session.parentID !== parentID) continue
result.push(session)
}
return result
})
使用示例:
// 获取根会话的所有子会
const rootSession = await Session.get("sess_root123")
const children = await Session.children(rootSession.id)
console.log(`Found ${children.length} child sessions`)
for (const child of children) {
console.log(`- ${child.title} (ID: ${child.id})`)
}
级联删除
删除会话时会级联删除所有子会话:
实现:packages/opencode/src/session/index.ts:336-41
export const remove = fn(Identifier.schema("session"), async (sessionID) => {
const project = Instance.project
const session = await get(sessionID)
// 递归删除所有子会话
for (const child of await children(sessionID)) {
await remove(child.id)
}
// 删除会话的消息和 parts
for (const msg of await Storage.list(["message", sessionID])) {
for (const part of await Storage.list(["part", msg.at(-1)!])) {
await Storage.remove(part)
}
await Storage.remove(msg)
}
// 删除会话本身
await Storage.remove(["session", project.id, sessionID])
})
示例:
// 删除根会话,会自动删除所有子会话
await Session.remove(rootSession.id)
// 无需手动删除每个子会话
会话标题自动生成
实现:packages/opencode/src/session/index.ts:32-34
function createDefaultTitle(isChild = false) {
return (isChild ? "Child session - " : "New session - ") + new Date().toISOString()
}
使用:packages/opencode/src/session/index.ts:206
title: input.title ?? createDefaultTitle(!!input.parentID)
- 根会话标题:
New session - 2025-01-26T10:30:00.000Z - 子会话标题:
Child session - 2025-01-26T10:30:00.000Z
上下文继承
子会话继承父会话的部分上下文,同时拥有独立的消息历史。
继承的全局上下文
所有会话(父和子)通过 Instance 单例共享以下全局上下文:
Instance 定义:packages/opencode/src/project/instance.ts
export const Instance = {
get directory() {
return context.use().directory // 当前工作目录
},
get worktree() {
return context.use().worktree // 项目根目录
},
get project() {
return context.use().project // 项目信息
},
}
示例值:
Instance.directory:/home/user/project/.opencodeInstance.worktree:/home/user/projectInstance.project.id:proj_abc123
Session 创建时的上下文传递
实现:packages/opencode/src/session/index.ts:199-213
const result: Info = {
id: Identifier.descending("session", input.id),
slug: Slug.create(),
version: Installation.VERSION,
projectID: Instance.project.id, // 👈 继承项目 ID
directory: input.directory ?? Instance.directory, // 👈 继承工作目录
parentID: input.parentID, // 👈 父会话 ID
title: input.title ?? createDefaultTitle(!!input.parentID),
permission: input.permission,
time: {
created: Date.now(),
updated: Date.now(),
},
}
上下文传递对比表
| 上下文类型 | 是否传递给子会话 | 传递方式 | 说明 |
|---|---|---|---|
| parentID | ✅ 是 | 直接传递 | 建立父子关系 |
| title | ✅ 是 | 直接传递(不同) | 子会话有独立标题 |
| permission | ✅ 是 | 直接传递(可能受限) | 子会话可能有权限限制 |
| projectID | ✅ 是 | Instance.project.id | 共享项目 ID |
| directory | ✅ 是 | Instance.directory | 共享工作目录 |
| worktree | ✅ 是 | Instance.worktree | 共享项目根目录 |
| summary | ❌ 否 | 子会话独立 | 每个会话有自己的代码摘要 |
| share | ❌ 否 | 子会话独立 | 分享信息不继承 |
| messages | ❌ 否 | 子会话独立 | 独立的消息历史 |
| time | ✅ 是 | 新创建 | 子会话有独立时间戳 |
消息历史独立性
重要说明:
- 子会话有独立的消息历史
- 子会话不继承父会话的 Messages
- 父 Agent 通过以下方式与子 Agent 通信:
- 事件监听(监听
PartUpdated事件) - Task 工具返回值(子 Agent 完成后返回结果)
- 事件监听(监听
示例:
// 父会话
const parentSession = await Session.create({
title: "Main task",
})
// 父会话有 10 条消息
// 创建子会话
const childSession = await Session.create({
parentID: parentSession.id,
title: "Subtask",
})
// 子会话的消息历史为空(0 条消息)
// 子会话不会自动复制父会话的消息
权限规则继承
权限合并逻辑:packages/opencode/src/agent/agent.ts:76-83
Task 工具创建子会话时的默认权限限制:
permission: [
{
permission: "todowrite",
pattern: "*",
action: "deny", // 👈 防止子 Agent 管理父 Agent 的 todo
},
{
permission: "todoread",
pattern: "*",
action: "deny", // 👈 防止子 Agent 读取父 Agent 的 todo
},
...(hasTaskPermission
? []
: [
{
permission: "task",
pattern: "*",
action: "deny", // 👈 防止无限递归调用
},
]),
]
典型使用场景
场景 1:创建子会话分支
const parentSession = await Session.get("sess_parent123")
const childSession = await Session.create({
parentID: parentSession.id,
title: "Alternative implementation approach",
permission: [
{
permission: "edit",
pattern: "*",
action: "deny", //只读权限
},
],
})
console.log(childSession.parentID) // "sess_parent123"
场景 2:遍历会话树
async function printSessionTree(sessionID: string, depth: number = 0) {
const session = await Session.get(sessionID)
const indent = " ".repeat(depth)
console.log(`${indent}- ${session.title}`)
const children = await Session.children(sessionID)
for (const child of children) {
await printSessionTree(child.id, depth + 1)
}
}
// 从根会话开始打印
const rootSessions = await Storage.list(["session", Instance.project.id])
for (const item of rootSessions) {
const session = await Storage.read<Session.Info>(item)
if (!session.parentID) {
// 只处理根会话
await printSessionTree(session.id)
}
}
场景 3:检查会话是否有子会话
async function hasChildren(sessionID: string): Promise<boolean> {
const children = await Session.children(sessionID)
return children.length > 0
}
const session = await Session.get("sess_abc123")
if (await hasChildren(session.id)) {
console.log("This session has child sessions")
console.log("Deleting it will also delete all children")
}
对象关系
变更历史
| 版本 | 变更内容 | 日期 |
|---|---|---|
| v1 | 初始 Session 架构 | - |
| v1.1 | 添加 revert 字段支持回滚 | - |