dsh-plugin-dev-kb 1.0.9 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/README.en.md +14 -8
- package/README.md +14 -8
- package/docs/banner.svg +25 -0
- package/kb/INDEX.md +8 -2
- package/kb/README.md +4 -4
- package/kb/extra/AGENTS.md +3 -3
- package/kb/extra/cookbook/adding-a-remote-api.md +197 -0
- package/kb/extra/cookbook/adding-a-remote-api.zh.md +197 -0
- package/kb/extra/cookbook/adding-a-session-format-version.md +109 -0
- package/kb/extra/cookbook/adding-a-session-format-version.zh.md +109 -0
- package/kb/extra/cookbook/adding-a-vendored-package.md +1 -1
- package/kb/extra/cookbook/adding-a-vendored-package.zh.md +1 -1
- package/kb/extra/cookbook/responding-to-pr-review-on-a-stack.md +1 -1
- package/kb/extra/cookbook/responding-to-pr-review-on-a-stack.zh.md +1 -1
- package/kb/extra/deepseek-llm-api-wire-extensions.md +19 -15
- package/kb/extra/deepseek-llm-api-wire-extensions.zh.md +19 -15
- package/kb/extra/development.md +13 -3
- package/kb/extra/development.zh.md +13 -3
- package/kb/extra/event-producer-consumer.md +49 -46
- package/kb/extra/event-producer-consumer.zh.md +52 -49
- package/kb/extra/i18n/README.md +2 -1
- package/kb/extra/i18n/README.zh.md +3 -2
- package/kb/extra/i18n/style-samples.md +2 -2
- package/kb/extra/i18n/translation-prompt.md +1 -1
- package/kb/extra/module-graph.md +493 -1002
- package/kb/extra/module-graph.zh.md +495 -1004
- package/kb/extra/rescope.md +1 -1
- package/kb/extra/rescope.zh.md +1 -1
- package/kb/extra/session-format-status.md +47 -0
- package/kb/extra/session-format-status.zh.md +47 -0
- package/kb/extra/subsystems/agent-team.md +3 -2
- package/kb/extra/subsystems/agent-team.zh.md +3 -2
- package/kb/extra/subsystems/attachment.md +157 -4
- package/kb/extra/subsystems/attachment.zh.md +157 -4
- package/kb/extra/subsystems/feedback.md +164 -30
- package/kb/extra/subsystems/feedback.zh.md +164 -30
- package/kb/extra/testing.md +9 -4
- package/kb/extra/testing.zh.md +9 -4
- package/kb/extra/web-styling.md +5 -0
- package/kb/extra/web-styling.zh.md +5 -0
- package/kb/meta/search-index.json +223 -151
- package/kb/meta/site-pages.txt +187 -181
- package/kb/meta/source.json +5 -5
- package/kb/meta/topics.md +5 -5
- package/kb/site/develop/basic/publish.md +1 -1
- package/kb/site/develop/cordis-tutorial/07-into-the-harness.md +3 -2
- package/kb/site/develop/practice/llm-adapter.md +4 -3
- package/kb/site/en/develop/basic/publish.md +1 -1
- package/kb/site/en/develop/cordis-tutorial/07-into-the-harness.md +3 -2
- package/kb/site/en/develop/practice/llm-adapter.md +4 -3
- package/kb/site/en/guide/network-proxy.md +87 -0
- package/kb/site/en/guide/providers.md +70 -17
- package/kb/site/en/guide/python-sdk.md +19 -3
- package/kb/site/en/guide/schedule.md +2 -0
- package/kb/site/en/reference/agent-lifecycle.md +17 -8
- package/kb/site/en/reference/api-gateway.md +2 -2
- package/kb/site/en/reference/capability-seams.md +24 -10
- package/kb/site/en/reference/config-catalog.md +322 -264
- package/kb/site/en/reference/cookbook/adding-a-package.md +1 -2
- package/kb/site/en/reference/cookbook/adding-a-settings-card.md +10 -8
- package/kb/site/en/reference/cookbook/adding-a-tool.md +1 -1
- package/kb/site/en/reference/cookbook/extension-cookbook.md +16 -13
- package/kb/site/en/reference/index.md +26 -9
- package/kb/site/en/reference/persistence-catalog.md +189 -95
- package/kb/site/en/reference/subsystems/approval.md +1 -1
- package/kb/site/en/reference/subsystems/client-modules.md +9 -0
- package/kb/site/en/reference/subsystems/client-resources.md +94 -0
- package/kb/site/en/reference/subsystems/code-runtime.md +7 -3
- package/kb/site/en/reference/subsystems/commands.md +25 -16
- package/kb/site/en/reference/subsystems/compaction.md +10 -10
- package/kb/site/en/reference/subsystems/conversation.md +11 -4
- package/kb/site/en/reference/subsystems/core.md +173 -29
- package/kb/site/en/reference/subsystems/credentials.md +5 -4
- package/kb/site/en/reference/subsystems/filesystem.md +15 -0
- package/kb/site/en/reference/subsystems/goal.md +38 -2
- package/kb/site/en/reference/subsystems/index.md +3 -1
- package/kb/site/en/reference/subsystems/invariants.md +1 -1
- package/kb/site/en/reference/subsystems/llm-streaming.md +43 -12
- package/kb/site/en/reference/subsystems/lsp.md +1 -1
- package/kb/site/en/reference/subsystems/permission-presets.md +5 -5
- package/kb/site/en/reference/subsystems/persistence.md +211 -199
- package/kb/site/en/reference/subsystems/plan.md +3 -3
- package/kb/site/en/reference/subsystems/sandbox.md +2 -0
- package/kb/site/en/reference/subsystems/schedule.md +9 -3
- package/kb/site/en/reference/subsystems/scope.md +1 -1
- package/kb/site/en/reference/subsystems/session-projection.md +50 -24
- package/kb/site/en/reference/subsystems/session-query.md +19 -13
- package/kb/site/en/reference/subsystems/session-reference.md +29 -1
- package/kb/site/en/reference/subsystems/session-telemetry.md +27 -10
- package/kb/site/en/reference/subsystems/session-title.md +6 -6
- package/kb/site/en/reference/subsystems/session.md +208 -103
- package/kb/site/en/reference/subsystems/settings.md +30 -12
- package/kb/site/en/reference/subsystems/shell.md +9 -6
- package/kb/site/en/reference/subsystems/sidebar-right.md +151 -0
- package/kb/site/en/reference/subsystems/skills.md +1 -1
- package/kb/site/en/reference/subsystems/slots.md +41 -32
- package/kb/site/en/reference/subsystems/spill.md +13 -6
- package/kb/site/en/reference/subsystems/storage.md +28 -5
- package/kb/site/en/reference/subsystems/subagent.md +58 -105
- package/kb/site/en/reference/subsystems/subprocess.md +25 -26
- package/kb/site/en/reference/subsystems/system-prompt.md +18 -3
- package/kb/site/en/reference/subsystems/token-meter.md +4 -3
- package/kb/site/en/reference/subsystems/tools.md +5 -5
- package/kb/site/en/reference/subsystems/typert.md +18 -18
- package/kb/site/en/reference/subsystems/web-client.md +1 -1
- package/kb/site/en/reference/subsystems/workspace.md +88 -9
- package/kb/site/en/reference/tool-catalog.md +56 -66
- package/kb/site/en/reference/tool-execution-pipeline.md +2 -2
- package/kb/site/guide/network-proxy.md +87 -0
- package/kb/site/guide/providers.md +70 -17
- package/kb/site/guide/python-sdk.md +19 -3
- package/kb/site/guide/schedule.md +2 -0
- package/kb/site/reference/agent-lifecycle.md +17 -8
- package/kb/site/reference/api-gateway.md +2 -2
- package/kb/site/reference/capability-seams.md +24 -10
- package/kb/site/reference/config-catalog.md +321 -263
- package/kb/site/reference/cookbook/adding-a-package.md +1 -2
- package/kb/site/reference/cookbook/adding-a-settings-card.md +10 -8
- package/kb/site/reference/cookbook/adding-a-tool.md +1 -1
- package/kb/site/reference/cookbook/extension-cookbook.md +16 -13
- package/kb/site/reference/index.md +26 -9
- package/kb/site/reference/persistence-catalog.md +190 -96
- package/kb/site/reference/subsystems/approval.md +1 -1
- package/kb/site/reference/subsystems/client-modules.md +9 -0
- package/kb/site/reference/subsystems/client-resources.md +94 -0
- package/kb/site/reference/subsystems/code-runtime.md +7 -3
- package/kb/site/reference/subsystems/commands.md +25 -16
- package/kb/site/reference/subsystems/compaction.md +10 -10
- package/kb/site/reference/subsystems/conversation.md +11 -4
- package/kb/site/reference/subsystems/core.md +173 -29
- package/kb/site/reference/subsystems/credentials.md +5 -4
- package/kb/site/reference/subsystems/filesystem.md +15 -0
- package/kb/site/reference/subsystems/goal.md +38 -2
- package/kb/site/reference/subsystems/index.md +14 -12
- package/kb/site/reference/subsystems/invariants.md +1 -1
- package/kb/site/reference/subsystems/llm-streaming.md +43 -12
- package/kb/site/reference/subsystems/lsp.md +1 -1
- package/kb/site/reference/subsystems/permission-presets.md +5 -5
- package/kb/site/reference/subsystems/persistence.md +210 -198
- package/kb/site/reference/subsystems/plan.md +3 -3
- package/kb/site/reference/subsystems/schedule.md +9 -3
- package/kb/site/reference/subsystems/scope.md +1 -1
- package/kb/site/reference/subsystems/session-projection.md +50 -24
- package/kb/site/reference/subsystems/session-query.md +19 -13
- package/kb/site/reference/subsystems/session-reference.md +29 -1
- package/kb/site/reference/subsystems/session-telemetry.md +27 -10
- package/kb/site/reference/subsystems/session-title.md +6 -6
- package/kb/site/reference/subsystems/session.md +208 -103
- package/kb/site/reference/subsystems/settings.md +30 -12
- package/kb/site/reference/subsystems/shell.md +9 -6
- package/kb/site/reference/subsystems/sidebar-right.md +151 -0
- package/kb/site/reference/subsystems/skills.md +1 -1
- package/kb/site/reference/subsystems/slots.md +41 -32
- package/kb/site/reference/subsystems/spill.md +13 -6
- package/kb/site/reference/subsystems/storage.md +28 -5
- package/kb/site/reference/subsystems/subagent.md +58 -105
- package/kb/site/reference/subsystems/subprocess.md +25 -26
- package/kb/site/reference/subsystems/system-prompt.md +18 -3
- package/kb/site/reference/subsystems/token-meter.md +4 -3
- package/kb/site/reference/subsystems/tools.md +5 -5
- package/kb/site/reference/subsystems/typert.md +18 -18
- package/kb/site/reference/subsystems/web-client.md +1 -1
- package/kb/site/reference/subsystems/workspace.md +88 -9
- package/kb/site/reference/tool-catalog.md +55 -65
- package/kb/site/reference/tool-execution-pipeline.md +2 -2
- package/lib/index.js +25 -44
- package/package.json +3 -2
- package/skills/dsh-plugin-dev-kb.md +4 -4
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# 实操手册:新增一个 Remote API
|
|
2
|
+
|
|
3
|
+
[English](adding-a-remote-api.md) | 中文
|
|
4
|
+
|
|
5
|
+
新增或改动一个 `ctx.remote` 端点按本页五步走:声明方法、声明失败、在包上注册、在 Client 消费、写测试。decorator 语义、lookup 解析、生成管线与 `/api` 路由属于机制,由 [API Gateway 参考](../api-gateway.zh.md)负责;本页给的是每一步的动作与必须遵守的约定。为什么是这套编程面,见 [Typert Remote 方法调用 Agent Note](../../.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.zh.md);为什么失败面是单个 `RemoteError` 加一张码表,见[失败词汇 Agent Note](../../.agents/notes/implemented/architecture/2026-08-28-ctx-remote-failure-vocabulary.zh.md)。
|
|
6
|
+
|
|
7
|
+
## 1. 声明 API
|
|
8
|
+
|
|
9
|
+
owner 是一个 Host 侧 Cordis 服务:继承 `TypertRemoteService` 把 service 键与 wire namespace 一起绑定,再用 `@Remote` 标注对外暴露的方法。业务方法的签名若已符合 wire 约定就直接标注它本身;只有形态需要调整(补 `signal`、换参数顺序、换导出名)才写一个 `remoteExport*` adapter,由它调用不改名的业务方法。lookup 对象(`Agent`、`Session`)只能占顶层参数位,支持协作式取消的方法把 `signal: AbortSignal` 放在最后一位。
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
13
|
+
import type { Agent } from '@deepseek-ai/dsh-agent'
|
|
14
|
+
import { Remote, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol'
|
|
15
|
+
|
|
16
|
+
/** One stored note as a Client reads it. */
|
|
17
|
+
export interface NoteRow {
|
|
18
|
+
readonly noteId: string
|
|
19
|
+
readonly title: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
declare module '@deepseek-ai/cordis' {
|
|
23
|
+
interface Context {
|
|
24
|
+
notesController: NotesController
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class NotesController extends TypertRemoteService {
|
|
29
|
+
constructor(ctx: Context) {
|
|
30
|
+
super(ctx, 'notesController', { namespace: 'notes' })
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param agent - lookup parameter the Gateway resolves from its wire identity.
|
|
35
|
+
* @param signal - carrier cancellation, always the final parameter.
|
|
36
|
+
* @returns the notes this Agent's session owns.
|
|
37
|
+
*/
|
|
38
|
+
@Remote('list')
|
|
39
|
+
async remoteExportList(agent: Agent, signal: AbortSignal): Promise<NoteRow[]> {
|
|
40
|
+
return await this.list(agent, signal)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The in-process API the adapter above delegates to, unchanged by it. */
|
|
44
|
+
async list(agent: Agent, signal: AbortSignal): Promise<NoteRow[]> {
|
|
45
|
+
signal.throwIfAborted()
|
|
46
|
+
return await Promise.resolve([{ noteId: `${agent.id}-1`, title: 'draft' }])
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## 2. 声明失败
|
|
52
|
+
|
|
53
|
+
Remote 失败只有一个类 `RemoteError`:域码经 declaration merging 进 `RemoteErrorDetailsMap`,失败点直接 `throw new RemoteError(code, message, details)`。不要建域异常类家族,也不要写出口映射函数;与本端点无关的异常不预先归类,Gateway 会兜底折成 `gateway/internal`。只有"把任意 provider 异常归为一个域码"这一种场景才写 `catch`,并把原始异常挂在 `cause` 上。
|
|
54
|
+
|
|
55
|
+
码名是 `<域>/<理由>`,声明落点四条:
|
|
56
|
+
|
|
57
|
+
- 只有一个生产者:声明落生产者包,紧挨抛出点。
|
|
58
|
+
- 多个包共同生产:落双方共同依赖的最低层域包(`session/not-found` 在 `core/session`,`workspace/not-found` 在 `dsh-workspace`)。
|
|
59
|
+
- 载体码 `gateway/bad-request`、`gateway/cancelled`、`gateway/internal` 已在 protocol 声明,Gateway 基础设施码已在 gateway 声明——直接用,不要复制。
|
|
60
|
+
- 不上 wire 的本地失败不进码表,用调用方自己的类型表达。
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import { RemoteError } from '@deepseek-ai/dsh-typert-protocol'
|
|
64
|
+
|
|
65
|
+
declare module '@deepseek-ai/dsh-typert-protocol' {
|
|
66
|
+
interface RemoteErrorDetailsMap {
|
|
67
|
+
/** No stored note carries that id. */
|
|
68
|
+
'note/not-found': { readonly noteId: string }
|
|
69
|
+
/** The store refused an otherwise valid write. */
|
|
70
|
+
'note/rejected': { readonly noteId: string }
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
declare const stored: ReadonlyMap<string, string>
|
|
75
|
+
declare function persist(noteId: string, title: string): Promise<void>
|
|
76
|
+
|
|
77
|
+
export async function rename(noteId: string, title: string): Promise<void> {
|
|
78
|
+
if (!stored.has(noteId)) {
|
|
79
|
+
throw new RemoteError('note/not-found', `no note "${noteId}"`, { noteId })
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
await persist(noteId, title)
|
|
83
|
+
} catch (error: unknown) {
|
|
84
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
85
|
+
throw new RemoteError('note/rejected', message, { noteId }, { cause: error })
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## 3. 在包上注册
|
|
91
|
+
|
|
92
|
+
`@Remote` 必须落在一个 Loader entry 插件包里;owner 是抽象 seam 时把控制器放进 `packages/api/` 下的对应包。包清单要补两个生成入口与 protocol 的 peer 依赖,Client 侧则由 `@deepseek-ai/dsh-api-remotes` 的 assembly 挂载该贡献并按需转口类型词汇。两个入口分别指向哪个生成产物、生成管线如何排序,见 [API Gateway 参考](../api-gateway.zh.md)。
|
|
93
|
+
|
|
94
|
+
```json
|
|
95
|
+
{
|
|
96
|
+
"exports": {
|
|
97
|
+
"./typert": { "types": "./lib/typert.host.d.ts", "default": "./lib/typert.host.js" },
|
|
98
|
+
"./remote": { "types": "./lib/typert.remote-client.d.ts", "default": "./lib/typert.remote-client.js" }
|
|
99
|
+
},
|
|
100
|
+
"peerDependencies": { "@deepseek-ai/dsh-typert-protocol": "workspace:^" },
|
|
101
|
+
"devDependencies": { "@deepseek-ai/dsh-typert-protocol": "workspace:^" }
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
改动了签名、码表、namespace 或导出名之后重跑 `pnpm run build:lib`,Client 才拿得到新的声明与 codec;只改实现体不需要重新生成。
|
|
106
|
+
|
|
107
|
+
## 4. 在 Client 消费
|
|
108
|
+
|
|
109
|
+
调用插件在 `inject` 里同时声明 `remote` 与 `remote.<namespace>`,调用点直写 `ctx.remote.<namespace>.<method>(...)`:不要用 `Pick<ClientRemote, …>` 窄化、不要手写方法签名、不要造 wire 中转对象。结果是 `RemoteResult<T>`,就地 `if (!result.ok)` 分支,判 `code` 而不是 `instanceof`——code 分支会自动窄化 `details`。异常流的站点写 `throw result.error`(它是真 Error);接住它的上层用 `isRemoteFailure` 区分 Remote 失败与本地缺陷,本地缺陷继续往上抛。不要写防御性 catch:Remote 调用不 reject,装配错误就该炸。
|
|
110
|
+
|
|
111
|
+
Host 的固定事实读 `ctx.remote.$host`:`home` 与 `isLoopback` 是普通值读取,没有订阅也没有 generation 计数器,`home` 在第一帧 ready 之前是 `undefined`;重连后的刷新走 `ctx.on('connection/reset')` 或各域自己的 remote 事件。调用方 abort 掉一次一元调用时,结果落在错误分支上的 `gateway/cancelled`,而不是抛出。
|
|
112
|
+
|
|
113
|
+
```ts ignore-check
|
|
114
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
115
|
+
import { isRemoteFailure } from '@deepseek-ai/dsh-api-gateway/client'
|
|
116
|
+
import type {} from '@deepseek-ai/dsh-api-remotes/client'
|
|
117
|
+
|
|
118
|
+
export const inject = ['remote', 'remote.notes']
|
|
119
|
+
|
|
120
|
+
declare const ctx: Context
|
|
121
|
+
|
|
122
|
+
/** Store-side read: the error branch is handled where the code is meaningful. */
|
|
123
|
+
export async function noteTitles(): Promise<readonly string[]> {
|
|
124
|
+
const result = await ctx.remote.notes.list()
|
|
125
|
+
if (!result.ok) {
|
|
126
|
+
if (result.error.code === 'note/not-found') return []
|
|
127
|
+
throw result.error
|
|
128
|
+
}
|
|
129
|
+
return result.value.map(row => row.title)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Action-side: a Remote failure becomes copy; a local fault keeps crashing. */
|
|
133
|
+
export async function renderTitles(): Promise<string> {
|
|
134
|
+
try {
|
|
135
|
+
return (await noteTitles()).join(', ')
|
|
136
|
+
} catch (error: unknown) {
|
|
137
|
+
if (!isRemoteFailure(error)) throw error
|
|
138
|
+
return `unavailable (${error.code})`
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Fixed Host facts as plain reads. */
|
|
143
|
+
export function hostLabel(): string {
|
|
144
|
+
const { home, isLoopback } = ctx.remote.$host
|
|
145
|
+
return home ?? (isLoopback ? 'local host' : 'remote host')
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## 5. 测试
|
|
150
|
+
|
|
151
|
+
owner 侧断言抛出的码:捕获后用 `remoteErrorOf` 取出失败,再用 `toMatchObject` 比对 `code` 与需要的 `details` 字段——不要用 `toEqual` 深比对错误对象,也不要断言 `instanceof`。
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
import { remoteErrorOf } from '@deepseek-ai/dsh-typert-protocol'
|
|
155
|
+
import { expect, it } from 'vitest'
|
|
156
|
+
|
|
157
|
+
declare function rename(noteId: string, title: string): Promise<void>
|
|
158
|
+
|
|
159
|
+
it('refuses an unknown note before writing', async () => {
|
|
160
|
+
const failure = await rename('n-404', 'fresh title').catch((error: unknown) => error)
|
|
161
|
+
|
|
162
|
+
expect(remoteErrorOf(failure)).toMatchObject({
|
|
163
|
+
code: 'note/not-found',
|
|
164
|
+
details: { noteId: 'n-404' },
|
|
165
|
+
})
|
|
166
|
+
})
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Client 侧的替身返回真实例:`RemoteError` 与 `TestRemote` 的值 import 一律取自 `@deepseek-ai/dsh-client-test-runtime`,因为从 `api-remotes` facade 值 import 会拉起尚未构建的装配链。`TestRemote.$host` 是普通字段,spec 直接赋值即可。
|
|
170
|
+
|
|
171
|
+
```ts ignore-check
|
|
172
|
+
import { Context } from '@deepseek-ai/cordis'
|
|
173
|
+
import { RemoteError, TestRemote } from '@deepseek-ai/dsh-client-test-runtime'
|
|
174
|
+
import { expect, it } from 'vitest'
|
|
175
|
+
|
|
176
|
+
it('renders the failure code the Host reported', async () => {
|
|
177
|
+
const ctx = new Context()
|
|
178
|
+
const remote = new TestRemote(ctx, {
|
|
179
|
+
notes: {
|
|
180
|
+
list: () => Promise.resolve({
|
|
181
|
+
ok: false as const,
|
|
182
|
+
error: new RemoteError('note/not-found', 'no note "n-404"', { noteId: 'n-404' }),
|
|
183
|
+
}),
|
|
184
|
+
},
|
|
185
|
+
})
|
|
186
|
+
remote.$host = { home: '/home/fixture', isLoopback: true }
|
|
187
|
+
|
|
188
|
+
await expect(ctx.remote.notes.list()).resolves.toMatchObject({ error: { code: 'note/not-found' } })
|
|
189
|
+
})
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## 验证
|
|
193
|
+
|
|
194
|
+
1. `pnpm run build:lib`:签名、码表、namespace 或导出名变过就必须重跑,Client 声明与 codec 由它产出。
|
|
195
|
+
2. `pnpm run typecheck`:Host 与 Client 两个 program 都过一遍,码表的 merge 落点错了会在这里红。
|
|
196
|
+
3. 点名跑两侧 spec:`npx vitest run <owner spec> <client spec>`。
|
|
197
|
+
4. 端点属于产品可见面时补一条录制会话快照,规则见[测试策略](../testing.zh.md)。
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Cookbook: adding a Session log format version
|
|
2
|
+
|
|
3
|
+
English | [中文](adding-a-session-format-version.zh.md)
|
|
4
|
+
|
|
5
|
+
## Summary
|
|
6
|
+
|
|
7
|
+
Use this tutorial to introduce the next structural Session log version without rewriting released data. Read the [version and release-status authority](../session-format-status.md) to identify the checkout writer and the latest released format. Let N denote that verified released format and N+1 the target; substitute numeric values for these placeholders in names and metadata. Start with a working contributor checkout and read the [package checklist](adding-a-package.md), [format library](../../packages/session/session-format/README.md), and [released-format decision](../../.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.md).
|
|
8
|
+
|
|
9
|
+
## Table of Contents
|
|
10
|
+
|
|
11
|
+
- [1. Choose the version and release base](#choose-the-version)
|
|
12
|
+
- [2. Add an identity edge](#add-an-identity-edge)
|
|
13
|
+
- [3. Implement per-artifact stages and validation](#stages-and-validation)
|
|
14
|
+
- [4. Update current-version consumers](#current-version-consumers)
|
|
15
|
+
- [5. Create snapshot successors](#snapshot-successors)
|
|
16
|
+
- [6. Validate the integrated result](#validate)
|
|
17
|
+
- [Dev Note](#dev-note)
|
|
18
|
+
|
|
19
|
+
<a id="choose-the-version"></a>
|
|
20
|
+
## 1. Choose the version and release base
|
|
21
|
+
|
|
22
|
+
Bump the format for a structural change to headers, event envelopes, core event semantics, or surface reconstruction. Ordinary event additions do not require a bump; follow the [versioning rule](../../.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.md). Distinguish the Session format integer from package release versions, SQLite schema versions, projection-unit versions, and protocol-wrapper versions.
|
|
23
|
+
|
|
24
|
+
Use a shared `release/*` integration base for N+1. The base change adds the writer, codec, catalog wiring, identity migration, and verification. Create each independent child branch from that base and target its PR at the release branch, not another independent child’s branch. Each child adds its structural transformation, validators, consumers, and tests to the same adjacent migration package. Do not allocate extra versions just to represent review order. Merge reviewed children into the release branch through PRs, then validate the combined result before release. Honor release-branch force-push and deletion protections; do not force-sync it.
|
|
25
|
+
|
|
26
|
+
Released codecs and migration semantics remain frozen. Do not amend a released edge to implement a new structural feature. Only the N→N+1 edge may incorporate coordinated changes before N+1 ships; after release, further structural changes need the next adjacent edge.
|
|
27
|
+
|
|
28
|
+
Use disposable, isolated Harness homes for unreleased N+1 integration testing. An interim N+1 file already has the target writer version, so a later edit to N→N+1 will not migrate that file again. Re-run from unchanged historical input in a fresh test home; never repair this by rewriting a committed generation or reusing a real user's home.
|
|
29
|
+
|
|
30
|
+
<a id="add-an-identity-edge"></a>
|
|
31
|
+
## 2. Add an identity edge
|
|
32
|
+
|
|
33
|
+
Follow the package checklist to create a library for N→N+1, not a mounted plugin. An identity body conversion is only an initial wiring scaffold. The [V2-to-V3 specification](../../packages/session/session-format-v2-to-v3/README.md#v2-to-v3-specification) is a fixed example of explicit transformations and preservation rules, not an edge to extend or treat as an identity conversion.
|
|
34
|
+
|
|
35
|
+
Declare `dsh.sessionFormatMigration` with numeric `from: N` and `to: N+1`, an export path, and the exported migration, source codec, target codec, target-header validator, and target restorer. Reuse the source codec exported by the preceding edge package and depend on that package; do not copy or redefine a released codec. Export the target codec and validators from the new package. Add the edge as a direct dependency of the catalog and add the workspace’s TypeScript paths and project references.
|
|
36
|
+
|
|
37
|
+
Set `SESSION_FORMAT_VERSION` in [core Session types](../../packages/core/session/src/types.ts) to N+1 alongside the new edge declarations, then generate the catalog. The command below generates only the declared chain; it does not implement a new version:
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
pnpm run gen-session-format-catalog
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The [generator](../../scripts/gen-session-format-catalog.ts) requires exactly one adjacent package for every step from zero to the writer version, matching directory/package names, matching adjacent codec exports, and declared dependencies. It rejects gaps, duplicate or extra edges, unknown metadata members, and a catalog that does not share Session through peer plus development dependencies. Fix the declarations rather than hand-editing `generated.ts`. The catalog is build-static; plugin mounting must not determine historical readability.
|
|
44
|
+
|
|
45
|
+
<a id="stages-and-validation"></a>
|
|
46
|
+
## 3. Implement per-artifact stages and validation
|
|
47
|
+
|
|
48
|
+
Use the [Stage interfaces](../../packages/session/session-format/src/types.ts), not a whole-artifact array-to-array migrator. An immutable `SessionFormatMigration` declaration supplies `migrateHeader`, `validateTargetHeader`, and `createStage`. Every call to `createStage` creates independent state for one source artifact. Keep counters, pending events, and reference maps there; never share a mutable stage across Sessions.
|
|
49
|
+
|
|
50
|
+
Implement `transformEvent(event, context)`, `transformRun(run, context)`, and `finish(context)`. Emit synchronously through `context.emitEvent` or `context.emitRun`; a call can produce zero, one, or many outputs. Let a stage consume codec-owned compact runs directly, or iterate `run.expand()` without materializing an intermediate array. The caller owns scheduling, and the chain finishes upstream stages before downstream stages.
|
|
51
|
+
|
|
52
|
+
Treat the inherited cut as a logical event count, not a physical row count. Expose `headerInheritedEventCount` only when it is known before EOF; `finish` returns the exact target cut. A preceding cardinality-changing edge can make that count unavailable at construction. Derive it from validated seed markers when required, and test seeded multi-hop restoration from each supported historical generation through N+1, not just direct N input. Never substitute zero for an unknown cut.
|
|
53
|
+
|
|
54
|
+
Define the new edge's event admission and transformation rules explicitly. The [V2-to-V3 source audit](../../packages/session/session-format-v2-to-v3/README.md#source-audit) and [alpha V0→V1 rule](../../.agents/notes/implemented/architecture/2026-08-31-alpha-historical-unknown-event-refusal.md) own the policies of those released edges, not the new edge. Do not generalize either to every edge. A change to structure or event positions requires classifying source events, payload members, and references, and explicitly deciding whether opaque data can remain valid. [Equal-version retention](../../.agents/notes/implemented/architecture/2026-08-30-retain-ignorable-external-session-events.md) alone does not prove a structural transformation safe. Validate target semantics and give each newly accepted case a rejecting counterexample; never widen older edges to hide an unsupported transformation.
|
|
55
|
+
|
|
56
|
+
Prove strict restoration through `sessionFormatCatalog.createRestore(header, { recovery: 'strict', validation: 'current' })`, feeding rows in order and calling `finish()`. This exercises physical decoding, the complete chain, and installed current Session validation. Production's recoverable/transformed policy is not a replacement for strict fixture and publication verification. Preserve documented historical validation exceptions rather than claiming stricter source validation than the edge actually performs.
|
|
57
|
+
|
|
58
|
+
<a id="current-version-consumers"></a>
|
|
59
|
+
## 4. Update current-version consumers
|
|
60
|
+
|
|
61
|
+
Trace each current-version consumer, including Session creation/restoration, JSONL filename selection and publication, the catalog's current encoder/restorer, projection-cache generation identity, replay and snapshot normalization, and TypeScript/Python SDK recordings. Use the writer constant where a value means current; keep literal historical versions in released codecs and historical fixtures. Update current documentation and generated references through their owners.
|
|
62
|
+
|
|
63
|
+
Do not bump unrelated versions automatically. A request wrapper's `sessionFormatVersion` identifies its embedded Session generation; its outer schema version has its own meaning. Projection-unit state versions likewise do not replace the cache's Session-generation identity.
|
|
64
|
+
|
|
65
|
+
Verify both read and write paths. Header-only listing must not read bodies or publish. Historical read open may return the migrated in-memory artifact without writing; write open must verify and publish only the final current successor before append. The source path, bytes, and inode stay unchanged. A newer or invalid selected generation must not cause fallback to a predecessor. The [preparation decision](../../.agents/notes/implemented/architecture/2026-09-05-read-only-session-migration-preparation.md) owns publication timing.
|
|
66
|
+
|
|
67
|
+
<a id="snapshot-successors"></a>
|
|
68
|
+
## 5. Create snapshot successors
|
|
69
|
+
|
|
70
|
+
Read [snapshot ownership](../../snapshots/AGENTS.md) and the [snapshot library](../../packages/test-support/session-snapshot/README.md). Select the owning scenario, not an adapter that only references it. After implementing N+1, keep each historical file and generate its successor using the target version’s canonical parent and child filenames. Never rename a predecessor to the target filename or change only its header.
|
|
71
|
+
|
|
72
|
+
For unchanged replay input, use keyless refresh on the owner, then replay without write-back. These SDK commands use `text-turn` and the checkout's writer version. Implement and wire N+1 before using them to generate that version, and select the actual affected owner for a feature:
|
|
73
|
+
|
|
74
|
+
```sh
|
|
75
|
+
pnpm run test:snapshot:refresh snapshots/sdk/sdk.snapshot.ts -t text-turn
|
|
76
|
+
pnpm run test:snapshot snapshots/sdk/sdk.snapshot.ts -t text-turn
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Review the new generation, request sidecars, and protocol output together. Verify every predecessor remains byte-identical and that parent/child roles remain contiguous. Selection uses the numerically highest generation, so update shared references to the owner's selected parent. Do not use the packed-layout migrator as a version upgrader. If the model transcript must change, the scenario owner uses live recording under the [testing policy](../testing.md), with its required provider key.
|
|
80
|
+
|
|
81
|
+
Keep deliberate historical cases explicit through `snapshot.yml`'s `sessionFormat.version` and supported `coverage` names; record and refresh leave their Session fixtures untouched. Update the [corpus policy](../../scripts/session-snapshot-corpus-policy.ts) for the current generation while retaining focused direct-edge, multi-hop, packed-row, retry/failure, and shipped-profile coverage. Check the corpus and both SDK projections; do not mass-refresh unrelated scenarios merely to silence a validation failure.
|
|
82
|
+
|
|
83
|
+
<a id="validate"></a>
|
|
84
|
+
## 6. Validate the integrated result
|
|
85
|
+
|
|
86
|
+
Run from the repository root. These commands check catalog declarations, Stage composition, the released V2→V3 edge, and generation selection. They are a baseline; add focused coverage for the new edge:
|
|
87
|
+
|
|
88
|
+
```sh
|
|
89
|
+
pnpm run verify-session-format-catalog
|
|
90
|
+
pnpm exec vitest run scripts/gen-session-format-catalog.spec.ts packages/session/session-format/tests packages/session/session-format-v2-to-v3/tests packages/session/session-format-catalog/tests
|
|
91
|
+
pnpm run test:snapshot scripts/session-snapshot-corpus.corpus.ts
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
After implementing the new edge, add its actual test path to the focused Vitest run. Add the changed JSONL, replay, projection, and SDK tests selected by the actual diff, plus the built publication-Worker smoke when that path changes. Require successful strict migration, identity preservation for the skeleton, malformed and unknown-required-event refusal, deterministic repeated restores, independent concurrent stage state, seeded multi-hop cuts, unchanged predecessors, and no fallback. Report exact commands and failures, not an inferred full-suite result.
|
|
95
|
+
|
|
96
|
+
Update the [owning Agent Note](../../.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.md) rather than adding a redundant decision record. Keep the [release record](../session-format-status.md#updating-the-record) unchanged until publication; after publication, update it with verified release evidence. Audit related active notes for supersession; retain independent rationale and leave archived notes frozen. Update bilingual prose together, re-record each changed pair with the repository tool, then run documentation checks:
|
|
97
|
+
|
|
98
|
+
```sh
|
|
99
|
+
pnpm run verify-translation-pairing --write docs/cookbook/adding-a-session-format-version.md
|
|
100
|
+
pnpm run test:docs
|
|
101
|
+
pnpm run doc-sync
|
|
102
|
+
pnpm run lint
|
|
103
|
+
git diff --check
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
<a id="dev-note"></a>
|
|
107
|
+
## Dev Note
|
|
108
|
+
|
|
109
|
+
None.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# 实操手册:添加 Session 日志格式版本
|
|
2
|
+
|
|
3
|
+
[English](adding-a-session-format-version.md) | 中文
|
|
4
|
+
|
|
5
|
+
## 概述
|
|
6
|
+
|
|
7
|
+
本教程介绍如何添加下一个结构性 Session 日志版本,同时不改写已发布数据。阅读[版本与发布状态真源](../session-format-status.zh.md),确定工作区写入器与最新已发布格式。令 N 表示经核实的已发布格式,N+1 表示目标版本;名称与元数据中的这些占位符须替换为数字。开始前,请准备可用的贡献者工作区,并阅读[包检查清单](adding-a-package.zh.md)、[格式库](../../packages/session/session-format/README.zh.md)和[已发布格式决策](../../.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.zh.md)。
|
|
8
|
+
|
|
9
|
+
## 目录
|
|
10
|
+
|
|
11
|
+
- [1. 选择版本与发布基线](#choose-the-version)
|
|
12
|
+
- [2. 添加恒等迁移边](#add-an-identity-edge)
|
|
13
|
+
- [3. 实现每份产物独占的 Stage 与校验](#stages-and-validation)
|
|
14
|
+
- [4. 更新当前版本消费方](#current-version-consumers)
|
|
15
|
+
- [5. 创建快照后继代际](#snapshot-successors)
|
|
16
|
+
- [6. 验证集成结果](#validate)
|
|
17
|
+
- [开发备注](#dev-note)
|
|
18
|
+
|
|
19
|
+
<a id="choose-the-version"></a>
|
|
20
|
+
## 1. 选择版本与发布基线
|
|
21
|
+
|
|
22
|
+
当 header、事件信封、核心事件语义或表面重建发生结构性变更时,提升格式版本。普通事件新增不需要提升版本;遵循[版本规则](../../.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.zh.md)。区分 Session 格式整数与包发布版本、SQLite schema 版本、投影单元版本及协议包装层版本。
|
|
23
|
+
|
|
24
|
+
为 N+1 使用共享的 `release/*` 集成基线。基线变更添加写入器、codec、catalog 接线、恒等迁移与验证。从该基线创建各个独立子分支,并将其 PR(Pull Request)的目标设为发布分支,而非另一个独立子分支。每个子分支在同一个相邻迁移包内添加自身的结构变换、校验器、消费方和测试。不要只为表示评审顺序而分配额外版本。通过 PR 将评审后的子分支合入发布分支,并在发布前验证组合结果。遵守发布分支的强制推送与删除保护;不要强制同步该分支。
|
|
25
|
+
|
|
26
|
+
已发布 codec 和迁移语义保持冻结。不要通过修改已发布迁移边来实现新的结构性功能。只有 N→N+1 迁移边可在 N+1 发布前纳入协同变更;发布后,进一步的结构性变更需要下一条相邻迁移边。
|
|
27
|
+
|
|
28
|
+
未发布 N+1 的集成测试应使用可丢弃、相互隔离的 Harness home。中间版本产生的 N+1 文件已标为目标写入器版本,因此后续对 N→N+1 的修改不会再次迁移该文件。请在全新测试 home 中从未变更的历史输入重新运行;绝不通过改写已提交代际或复用真实用户 home 来修复这个问题。
|
|
29
|
+
|
|
30
|
+
<a id="add-an-identity-edge"></a>
|
|
31
|
+
## 2. 添加恒等迁移边
|
|
32
|
+
|
|
33
|
+
按照包检查清单为 N→N+1 创建库,而非挂载插件。恒等正文转换仅是最初的接线骨架。[V2 到 V3 规范](../../packages/session/session-format-v2-to-v3/README.zh.md#v2-to-v3-specification)是明确转换与保留规则的固定示例,而不是可继续扩展或视为恒等转换的迁移边。
|
|
34
|
+
|
|
35
|
+
在 manifest(元数据清单)中声明 `dsh.sessionFormatMigration`,包含数值 `from: N` 和 `to: N+1`、导出路径,以及导出的迁移、源 codec、目标 codec、目标 header 校验器和目标恢复器。复用前一条迁移边所属包导出的源 codec,并依赖该包;不要复制或重新定义已发布 codec。从新包导出目标 codec 和校验器。将迁移边加入 catalog 的直接依赖,并添加工作区的 TypeScript 路径与项目引用。
|
|
36
|
+
|
|
37
|
+
在添加新迁移边声明的同时,将[核心 Session 类型](../../packages/core/session/src/types.ts)中的 `SESSION_FORMAT_VERSION` 设为 N+1,然后生成 catalog。下面的命令只生成已声明的迁移链;它不会实现新版本:
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
pnpm run gen-session-format-catalog
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
[生成器](../../scripts/gen-session-format-catalog.ts)要求从零到写入器版本的每一步恰好有一个相邻迁移包,目录与包名匹配、相邻 codec 导出匹配,并声明所需依赖。它拒绝缺口、重复或多余的迁移边、未知元数据成员,以及未通过对等依赖(peer dependency)加开发依赖共享 Session 的 catalog。请修复声明,而非手改 `generated.ts`。Catalog 在构建时静态确定;插件挂载不得决定历史数据是否可读。
|
|
44
|
+
|
|
45
|
+
<a id="stages-and-validation"></a>
|
|
46
|
+
## 3. 实现每份产物独占的 Stage 与校验
|
|
47
|
+
|
|
48
|
+
使用 [Stage 接口](../../packages/session/session-format/src/types.ts),不要使用整份产物的数组到数组迁移器。不可变的 `SessionFormatMigration` 声明提供 `migrateHeader`、`validateTargetHeader` 和 `createStage`。每次调用 `createStage` 都为一份源产物创建独立状态。计数器、待处理事件和引用映射归该状态所有;不同 Session 之间绝不共享可变 Stage。
|
|
49
|
+
|
|
50
|
+
实现 `transformEvent(event, context)`、`transformRun(run, context)` 和 `finish(context)`。通过 `context.emitEvent` 或 `context.emitRun` 同步输出;一次调用可以产生零个、一个或多个输出。让 Stage 直接消费 codec 所有的紧凑 run,或者迭代 `run.expand()`,而不物化中间数组。调用方负责调度,迁移链先结束上游 Stage,再结束下游 Stage。
|
|
51
|
+
|
|
52
|
+
继承截点是逻辑事件数量,不是物理行数。只有在 EOF 前已知时才公开 `headerInheritedEventCount`;`finish` 返回精确的目标截点。前一条改变事件数量的迁移边可能使该数量在构造时不可知。必要时从已校验的种子标记推导它,并测试从每个受支持历史代际到 N+1 的有种子多跳恢复,而非仅测试直接 N 输入。绝不以零替代未知截点。
|
|
53
|
+
|
|
54
|
+
显式定义新迁移边的事件准入与变换规则。[V2 到 V3 源审计](../../packages/session/session-format-v2-to-v3/README.zh.md#source-audit)和 [Alpha V0→V1 规则](../../.agents/notes/implemented/architecture/2026-08-31-alpha-historical-unknown-event-refusal.zh.md)分别负责对应已发布迁移边的策略,而非新迁移边的策略。不要将任一策略推广到所有迁移边。结构或事件位置变化时,必须分类源事件、载荷成员与引用,并显式判断不透明数据能否保持有效。[同版本保留](../../.agents/notes/implemented/architecture/2026-08-30-retain-ignorable-external-session-events.zh.md)本身不能证明结构变换安全。校验目标语义,并为每个新增可接受案例提供一个被拒绝的反例;绝不放宽旧迁移边来掩盖不受支持的转换。
|
|
55
|
+
|
|
56
|
+
通过 `sessionFormatCatalog.createRestore(header, { recovery: 'strict', validation: 'current' })` 验证严格恢复,按顺序传入各行并调用 `finish()`。这会执行物理解码、完整迁移链与已安装当前 Session 校验。生产环境的 recoverable/transformed 策略不能替代 fixture(测试前置数据)和发布验证所需的严格校验。保留已记录的历史校验例外,不要宣称源校验比迁移边实际执行的更严格。
|
|
57
|
+
|
|
58
|
+
<a id="current-version-consumers"></a>
|
|
59
|
+
## 4. 更新当前版本消费方
|
|
60
|
+
|
|
61
|
+
追踪每个当前版本消费方,包括 Session 创建与恢复、JSONL 文件名选择与发布、catalog 的当前编码器与恢复器、投影缓存的代际身份、回放与快照归一化,以及 TypeScript/Python SDK 录制。当值表示当前版本时使用写入器常量;在已发布 codec 和历史 fixture 中保留字面历史版本。通过各自所有者更新当前文档与生成参考。
|
|
62
|
+
|
|
63
|
+
不要自动提升无关版本。请求包装层的 `sessionFormatVersion` 标识嵌入的 Session 代际;外层 schema 版本有自己的含义。投影单元状态版本同样不能替代缓存的 Session 代际身份。
|
|
64
|
+
|
|
65
|
+
验证读取与写入两条路径。仅 header 的列表操作不得读取正文或发布。历史读取打开可以直接返回迁移后的内存产物而不写入;写入打开必须先校验并发布唯一的最终当前后继代际,再允许追加。源路径、字节与 inode 保持不变。所选代际高于当前版本或无效时,不得回退到前代。[准备阶段决策](../../.agents/notes/implemented/architecture/2026-09-05-read-only-session-migration-preparation.zh.md)负责发布时序。
|
|
66
|
+
|
|
67
|
+
<a id="snapshot-successors"></a>
|
|
68
|
+
## 5. 创建快照后继代际
|
|
69
|
+
|
|
70
|
+
阅读[快照所有权](../../snapshots/AGENTS.md)和[快照库](../../packages/test-support/session-snapshot/README.zh.md)。选择拥有数据的场景,而非仅引用它的适配器。实现 N+1 后,保留每份历史文件,并按目标版本的规范父子文件名生成后继文件。绝不将前代重命名为目标文件名,或仅修改其 header。
|
|
71
|
+
|
|
72
|
+
如果回放输入不变,在所有者上执行无密钥 refresh,再执行不写回的 replay。以下 SDK 命令使用 `text-turn` 和工作区的写入器版本。先实现并接入 N+1,才能用它们生成该版本;功能变更应选择实际受影响的所有者:
|
|
73
|
+
|
|
74
|
+
```sh
|
|
75
|
+
pnpm run test:snapshot:refresh snapshots/sdk/sdk.snapshot.ts -t text-turn
|
|
76
|
+
pnpm run test:snapshot snapshots/sdk/sdk.snapshot.ts -t text-turn
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
一起审查新代际、请求伴随文件与协议输出。验证每个前代的字节保持相同,且父子角色连续。选择规则采用数值最高的代际,因此应将共享引用更新为所有者选中的父代际。不要把 packed 布局迁移器当作版本升级器。如果模型 transcript(文本记录)必须变化,由场景所有者按照[测试策略](../testing.zh.md)使用所需提供方密钥进行实时录制。
|
|
80
|
+
|
|
81
|
+
通过 `snapshot.yml` 的 `sessionFormat.version` 与受支持的 `coverage` 名称显式保留历史案例;record 和 refresh 不改动这些 Session fixture。更新[语料策略](../../scripts/session-snapshot-corpus-policy.ts)以采用当前代际,同时保留聚焦的直接迁移边、多跳、packed row、重试/失败及交付 profile 覆盖。检查语料和两个 SDK 投影;不要仅为消除校验失败而批量 refresh 无关场景。
|
|
82
|
+
|
|
83
|
+
<a id="validate"></a>
|
|
84
|
+
## 6. 验证集成结果
|
|
85
|
+
|
|
86
|
+
从仓库根目录运行。以下命令检查 catalog 声明、Stage 组合、已发布的 V2→V3 迁移边与代际选择。它们是基线检查;需为新迁移边添加聚焦覆盖:
|
|
87
|
+
|
|
88
|
+
```sh
|
|
89
|
+
pnpm run verify-session-format-catalog
|
|
90
|
+
pnpm exec vitest run scripts/gen-session-format-catalog.spec.ts packages/session/session-format/tests packages/session/session-format-v2-to-v3/tests packages/session/session-format-catalog/tests
|
|
91
|
+
pnpm run test:snapshot scripts/session-snapshot-corpus.corpus.ts
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
实现新迁移边后,将其实际测试路径加入聚焦的 Vitest 命令。根据实际 diff 添加受影响的 JSONL、回放、投影与 SDK 测试;发布 Worker 路径变化时还需构建产物冒烟测试。要求严格迁移成功、骨架保持恒等、拒绝格式错误与未知必需事件、重复恢复确定、并发 Stage 状态独立、有种子的多跳截点正确、前代不变且无回退。报告确切命令与失败,不要推断整个测试套件的结果。
|
|
95
|
+
|
|
96
|
+
更新[所属 Agent Note](../../.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.zh.md),而非添加重复决策记录。发布前保持[发布记录](../session-format-status.zh.md#updating-the-record)不变;发布后,使用已核实的发布证据更新它。审计相关活跃记录的取代关系;保留独立理由,并保持归档记录冻结。一起更新双语正文,通过仓库工具重新记录每个变更的配对,然后运行文档检查:
|
|
97
|
+
|
|
98
|
+
```sh
|
|
99
|
+
pnpm run verify-translation-pairing --write docs/cookbook/adding-a-session-format-version.md
|
|
100
|
+
pnpm run test:docs
|
|
101
|
+
pnpm run doc-sync
|
|
102
|
+
pnpm run lint
|
|
103
|
+
git diff --check
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
<a id="dev-note"></a>
|
|
107
|
+
## 开发备注
|
|
108
|
+
|
|
109
|
+
无。
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
English | [中文](adding-a-vendored-package.zh.md)
|
|
4
4
|
|
|
5
|
-
When the harness needs another upstream Cordis package (e.g. `@cordisjs/plugin-http`), it is **vendored** as pinned source under `vendor/`, not added as an npm dependency
|
|
5
|
+
When the harness needs another upstream Cordis package (e.g. `@cordisjs/plugin-http`), it is **vendored** as pinned source under `vendor/`, not added as an npm dependency. [vendor/README.md](../../vendor/README.md) states why and covers *updating* an already-vendored package; this guide is the file-by-file checklist for adding a **new** one. (Verified against the existing vendored set; if it drifts, fix it here.)
|
|
6
6
|
|
|
7
7
|
## 1. Copy the source in
|
|
8
8
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[English](adding-a-vendored-package.md) | 中文
|
|
4
4
|
|
|
5
|
-
当 harness 需要引入另一个上游 Cordis 包(如 `@cordisjs/plugin-http`)时,应将其作为固定版本的源码 **vendor** 到 `vendor/` 下,而非作为 NPM
|
|
5
|
+
当 harness 需要引入另一个上游 Cordis 包(如 `@cordisjs/plugin-http`)时,应将其作为固定版本的源码 **vendor** 到 `vendor/` 下,而非作为 NPM 依赖添加。[vendor/README.md](../../vendor/README.md) 说明其原因并介绍如何*更新*已有的 vendored 包;本指南是添加**新** vendored 包的逐文件清单。(已对照现有 vendored 集合验证;如有偏差,请在此修正。)
|
|
6
6
|
|
|
7
7
|
## 1. 复制源码
|
|
8
8
|
|
|
@@ -17,7 +17,7 @@ Review comments may target several PRs in a dependent stack (`A ← B ← C …`
|
|
|
17
17
|
1. Triage every comment on the merits before acting: verify the claim against the code — a reviewer flagging the right symptom can still misdiagnose the cause.
|
|
18
18
|
2. Map each accepted finding to its originating PR and fix it there.
|
|
19
19
|
3. Propagate the fixed layer through every affected child in order:
|
|
20
|
-
- **Merge-forward:** merge the fixed parent branch into its child, validate the child, and continue upward. Preserve each in-progress checkpoint
|
|
20
|
+
- **Merge-forward:** merge the fixed parent branch into its child, validate the child, and continue upward. Preserve each in-progress checkpoint.
|
|
21
21
|
- **Native cascading rebase:** use `gh stack rebase`, validate the rewritten layers, then publish with `gh stack push`; or use `gh stack sync`, which may publish first and therefore requires immediate post-sync validation under [dsh-pre-push-checks](../../.agents/skills/dsh-pre-push-checks/SKILL.md).
|
|
22
22
|
4. Treat delegated fixes as trust-but-verify: a sub-agent's report describes intent, not necessarily what landed. Re-run the gates yourself on the actual tree, and for a regression guard, prove it FAILS on the unfixed code (introduce the regression, watch red, revert) — a guard that passes both ways guards nothing. A sub-agent that reframes a problem as already handled is a signal to dig in personally.
|
|
23
23
|
5. Reply in the review thread (`gh api repos/{owner}/{repo}/pulls/{pr}/comments/{id}/replies`), not as a top-level comment, stating the fix and the current commit or head that carries it.
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
1. 在行动之前先就事论事地审视每条评论:对照代码验证其论断——评审者指出了正确的症状,但仍可能误诊原因。
|
|
18
18
|
2. 将每个被接受的发现映射到引入该问题的 PR,并在那里修复。
|
|
19
19
|
3. 将修复后的层按顺序传播到每个受影响的子 PR:
|
|
20
|
-
- **Merge-forward:**
|
|
20
|
+
- **Merge-forward:** 将修复后的父分支合并到其子分支,验证子分支,然后继续沿堆叠向上传播。保留每个正在处理的检查点。
|
|
21
21
|
- **原生级联 rebase:** 使用 `gh stack rebase`,验证所有已改写的层,然后通过 `gh stack push` 发布;也可以使用 `gh stack sync`,该命令可能先发布,因此必须按照 [dsh-pre-push-checks](../../.agents/skills/dsh-pre-push-checks/SKILL.md) 在同步后立即验证。
|
|
22
22
|
4. 委派的修复需要信任但验证:subagent 的报告描述的是意图,不一定是实际落地的内容。请亲自在实际代码树上重新运行门禁;对于回归守卫,要证明它在未修复的代码上**失败**(引入回归、观察变红、再还原)——两种情况都通过的守卫什么也守不住。subagent 将问题重新定性为「已处理」时,这是一个需要亲自深入的信号。
|
|
23
23
|
5. 在评审线程中回复(`gh api repos/{owner}/{repo}/pulls/{pr}/comments/{id}/replies`),而非发顶层评论;说明修复内容及当前承载修复的 commit 或 head。
|
|
@@ -73,16 +73,18 @@ An enabled inventory with no qualifying entries sends `packages: []`; disabling
|
|
|
73
73
|
|
|
74
74
|
## `dsh_session_log`
|
|
75
75
|
|
|
76
|
-
[`@deepseek-ai/dsh-session-log-deepseek`](../packages/session/session-log-deepseek/README.md) contributes one contiguous suffix of the canonical Session log. The field is disabled by default. When enabled, it applies to a request with a live Session and at least one event; a direct request, a stale Session id, or an empty log omits the field.
|
|
76
|
+
[`@deepseek-ai/dsh-session-log-deepseek`](../packages/session/session-log-deepseek/README.md) contributes one contiguous suffix of the canonical Session log. The field is disabled by default. When enabled, it applies to a request with a live Session and at least one event; a direct request, a stale Session id, or an empty log omits the field. The examples below use logical Session format 2 only to illustrate the wire fields; they do not identify the [current writer format](session-format-status.md).
|
|
77
77
|
|
|
78
78
|
```json
|
|
79
79
|
{
|
|
80
80
|
"dsh_session_log": {
|
|
81
|
-
"version":
|
|
81
|
+
"version": 2,
|
|
82
|
+
"sessionFormatVersion": 2,
|
|
82
83
|
"session": {
|
|
83
|
-
"version":
|
|
84
|
+
"version": 2,
|
|
84
85
|
"id": "session-id",
|
|
85
|
-
"createdAt": 1780000000000
|
|
86
|
+
"createdAt": 1780000000000,
|
|
87
|
+
"isSeeded": false
|
|
86
88
|
},
|
|
87
89
|
"afterSeq": -1,
|
|
88
90
|
"throughSeq": 0,
|
|
@@ -102,33 +104,34 @@ An enabled inventory with no qualifying entries sends `packages: []`; disabling
|
|
|
102
104
|
|
|
103
105
|
| Member | Type | Meaning |
|
|
104
106
|
|---|---|---|
|
|
105
|
-
| `version` | `
|
|
106
|
-
| `
|
|
107
|
+
| `version` | `2` | Schema version for `dsh_session_log` |
|
|
108
|
+
| `sessionFormatVersion` | non-negative integer | Session format generation represented by this suffix |
|
|
109
|
+
| `session` | object | Immutable wire projection of the current Session header |
|
|
107
110
|
| `afterSeq` | integer | Greatest sequence recorded as accepted before this request, or `-1` |
|
|
108
111
|
| `throughSeq` | non-negative integer | Greatest sequence represented by this request |
|
|
109
112
|
| `events` | array | Contiguous events from `afterSeq + 1` through `throughSeq` |
|
|
110
113
|
|
|
111
114
|
The first upload uses `afterSeq: -1` and carries the complete current log. Each later upload starts after the greatest accepted watermark for the same Session id. The sender snapshots the event array once per request; appends after that snapshot belong to a later request.
|
|
112
115
|
|
|
113
|
-
### Session header
|
|
116
|
+
### Wire Session header
|
|
114
117
|
|
|
115
|
-
The `session` member
|
|
118
|
+
The `session` member projects `Session.header`, not a complete runtime Session or the header object itself. It copies the current header facts, including the required `isSeeded` lineage bit; the exact `Session.inheritedEventCount` is not part of this request field. The outer `dsh_session_log.version` selects this extension schema, while `session.version` selects the logical Session format. Changing the Session header projection requires an extension-schema bump even when the embedded logical format also changes.
|
|
116
119
|
|
|
117
120
|
| Member | Presence | Meaning |
|
|
118
121
|
|---|---|---|
|
|
119
|
-
| `version` | required |
|
|
122
|
+
| `version` | required | Logical Session format version from `Session.header`; see [format status](session-format-status.md) |
|
|
120
123
|
| `id` | required | Exact Session id |
|
|
121
124
|
| `createdAt` | required | Non-negative safe-integer Unix epoch milliseconds |
|
|
122
125
|
| `cwd` | optional | Absolute working directory recorded at Session creation |
|
|
123
126
|
| `parentSession` | optional | Parent Session id for a fork |
|
|
124
|
-
| `
|
|
127
|
+
| `isSeeded` | required | Whether the Session contains a fork-inherited event prefix |
|
|
125
128
|
| `origin` | optional | Literal `subagent` for a subagent child |
|
|
126
129
|
| `delegationDepth` | optional | Non-negative persisted subagent delegation depth |
|
|
127
130
|
| `agentPreset` | optional | Agent preset id used to compose this Session |
|
|
128
131
|
|
|
129
132
|
### Canonical event envelopes
|
|
130
133
|
|
|
131
|
-
Each `events` item is a complete canonical `SessionEvent`, independent of every other request field. An event always carries `type`, `seq`, `time`, and `data`; surface events may additionally carry `sourceEventSeqs` and `surfaceOp`. The sender copies every present member without projection, redaction, or reconstruction.
|
|
134
|
+
Each `events` item is a complete canonical `SessionEvent`, independent of every other request field. An event always carries `type`, `seq`, `time`, and `data`; it may carry `ignorable: true`, and surface events may additionally carry `sourceEventSeqs` and `surfaceOp`. The sender copies every present member without projection, redaction, or reconstruction.
|
|
132
135
|
|
|
133
136
|
### Acceptance watermark and at-least-once delivery
|
|
134
137
|
|
|
@@ -141,19 +144,20 @@ After the endpoint returns HTTP 2xx, the contribution appends this canonical eve
|
|
|
141
144
|
"time": 1780000000002,
|
|
142
145
|
"data": {
|
|
143
146
|
"sessionId": "session-id",
|
|
147
|
+
"sessionFormatVersion": 2,
|
|
144
148
|
"throughSeq": 7
|
|
145
149
|
}
|
|
146
150
|
}
|
|
147
151
|
```
|
|
148
152
|
|
|
149
|
-
`delivery-accepted` means that the configured endpoint returned HTTP 2xx for the containing LLM request. It does not assert SSE completion or remote persistence. The event's `throughSeq` must identify an earlier event,
|
|
153
|
+
`delivery-accepted` means that the configured endpoint returned HTTP 2xx for the containing LLM request. It does not assert SSE completion or remote persistence. The event's `throughSeq` must identify an earlier event, its `sessionId` identifies the Session whose suffix was sent, and `sessionFormatVersion` binds the watermark to that exact logical generation. Absence means historical format v0.
|
|
150
154
|
|
|
151
|
-
The sender folds the greatest matching `throughSeq
|
|
155
|
+
The sender folds the greatest matching `throughSeq` for the current Session id and format generation, so concurrent accepted requests cannot move the cursor backward and a watermark from another generation cannot authorize the current suffix. A resumed process rebuilds the cursor from the durable log. A fork ignores inherited watermarks that name another Session, and therefore sends its own complete inherited prefix before advancing under the child id. The watermark event itself belongs to the next unsent suffix.
|
|
152
156
|
|
|
153
157
|
Transport and non-2xx failures append no watermark. A crash after endpoint acceptance but before local persistence may resend an already accepted range; uncertainty produces duplicates, never a sequence gap. There is no independent upload store, size cap, or truncation path.
|
|
154
158
|
|
|
155
159
|
## Exposure and receiver requirements
|
|
156
160
|
|
|
157
|
-
The request headers expose the Harness application version, one anonymous Harness-home identity, and an optional Session identity. `dsh_plugin_packages` exposes active npm package names and versions. When enabled, `dsh_session_log` may expose the Session working directory, system-prompt snapshots, user and
|
|
161
|
+
The request headers expose the Harness application version, one anonymous Harness-home identity, and an optional Session identity. `dsh_plugin_packages` exposes active npm package names and versions. When enabled, `dsh_session_log` may expose the Session working directory, system-prompt snapshots, user and Assistant content, embedded Assistant streams, failed-attempt output, tool arguments and results, compaction summaries, feedback, and plugin-owned events. Adapter API keys are not Session events and therefore do not enter the field. A gateway selected through `baseURL` receives the same values as the official endpoint.
|
|
158
162
|
|
|
159
|
-
Receivers address extension fields by name, dispatch each field by its own `version`, preserve distinct package versions, and ignore JSON member ordering. A session-log receiver validates the contiguous sequence range before interpreting event types.
|
|
163
|
+
Receivers address extension fields by name, dispatch each field by its own `version`, preserve distinct package versions, and ignore JSON member ordering. A session-log receiver validates the contiguous sequence range before interpreting event types. An unrecognized canonical event without `ignorable: true` prevents lossless reconstruction. The base request remains usable without either the registry or a particular contribution; field absence means that contribution did not apply to that request.
|