Merge branch 'worktree/ci-native-windows-20260808' into worktree/ci-native-windows-coverage-20260808

This commit is contained in:
Tianyi Cui
2026-08-09 17:25:26 +08:00
393 changed files with 1000 additions and 917 deletions

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/session/session-persistence/README.md
README.md: 89c7cd5ebaff6f9ce9df9b60a50121dff4ddeeb5
README.zh.md: 6b02f37343da07a80953ecb9d05e5903bd42d95c
README.md: 29ee219a55d0823f03bb512c0fa37b7e249817eb
README.zh.md: c750d2f85fd267a94c36770452f1e5c7e647a2fb

View File

@@ -35,7 +35,7 @@ Each `session/event` copies its event into the session controller. The first pen
Crash repair is cold-only. For a live id, `load(id)` snapshots the authoritative in-memory log, waits for that snapshot to become durable, and returns it only when balanced; an open live turn rejects instead of receiving synthetic interruption closers. For a cold id, inspection reads, validates, freezes, and constructs one unpublished Session; repeated inspection reuses that object graph only while its source revision remains current. `prepare(id)` performs the same check before repair, reserves the exact Session, commits any pending torn-tail/interrupted-turn repair, and returns it for publication. HMR adoption reads through `loadStored`, applies the coordinator's cwd check, and never closes the active turn.
Backend reads normalize the exact supported same-version shapes before current-shape validation. Pre-identity messages receive the deterministic id `legacy-message:<session-id>:<event-seq>`; a tool-result content replacement inherits its target's imported id. A pre-react-loop `turn/start` loses its obsolete trigger, a removed `steering/message` becomes the same identified `user/message`, and an older `turn/end` maps its terminal reason without inventing unavailable cancellation provenance. The coordinator uses the same normalized view for `load`, `inspect`, `readFrom`, ownerless-state claims, and HMR prefix adoption. Storage remains append-only: reads do not rewrite old records, and later appends use the current shape. These are narrow import exceptions from the [pre-identity message](../../../.agents/notes/implemented/bug-fix/2026-07-28-load-pre-identity-session-messages.md) and [pre-react-loop session](../../../.agents/notes/implemented/bug-fix/2026-08-04-load-pre-react-loop-sessions.md) decisions, not a general v0 migration promise.
Backend reads normalize the exact supported same-version shapes before current-shape validation. Pre-identity messages receive the deterministic id `legacy-message:<session-id>:<event-seq>`; a tool-result content replacement inherits its target's imported id. A pre-react-loop `turn/start` loses its obsolete trigger, a removed `steering/message` becomes the same identified `user/message`, and an older `turn/end` maps its terminal reason without inventing a caller that the old record did not name. The coordinator uses the same normalized view for `load`, `inspect`, `readFrom`, ownerless-state claims, and HMR prefix adoption. Storage remains append-only: reads do not rewrite old records, and later appends use the current shape. These are narrow import exceptions from the [pre-identity message](../../../.agents/notes/implemented/bug-fix/2026-07-28-load-pre-identity-session-messages.md) and [pre-react-loop session](../../../.agents/notes/implemented/bug-fix/2026-08-04-load-pre-react-loop-sessions.md) decisions, not a general v0 migration promise.
When a live session emits `session/disposed`, the coordinator waits for its controller, serializes a final drain, then releases state owned by that exact `Session` object. Failed retirement leaves the controller in the live-session map, so backend teardown can retry it. Backend teardown stops event admission first, flushes every remaining controller, awaits per-id operations, and only then closes the storage handle.

View File

@@ -35,7 +35,7 @@
崩溃修复只适用于冷状态。对于实时 id`load(id)` 为权威内存日志制作快照,等待该快照持久,并只在平衡时返回;开放实时轮次会被拒绝,而不会收到合成中断 closer。对于冷 id检查只读取、验证、冻结并构造一次未发布 Session只有来源修订值仍然是当前值时重复检查才会复用该对象图。`prepare(id)` 在修复前执行相同校验,预留该 Session 本身提交任何待处理的撕裂尾部或中断轮次修复并将其返回用于发布。HMR 接管通过 `loadStored` 读取,应用协调器 cwd 检查,并绝不关闭活动轮次。
后端读取会在当前形状验证前,规范化明确受支持的同版本形状。消息标识机制引入前的消息会获得确定性的 id `legacy-message:<session-id>:<event-seq>`;工具结果的内容替换会继承其目标导入后的 id。react-loop 引入前的 `turn/start` 会移除过时的 trigger已移除的 steering中途引导事件 `steering/message` 会转换为同一条带标识的 `user/message`;旧版 `turn/end` 会在不虚构无法获得的取消来源的前提下映射终止原因。协调器对 `load``inspect``readFrom`、无所有者状态的认领和 HMR 前缀接管使用同一份规范化视图。存储仍然仅追加:读取不会重写旧记录,此后追加的事件使用当前形状。这些是[消息标识机制引入前的消息](../../../.agents/notes/implemented/bug-fix/2026-07-28-load-pre-identity-session-messages.md)与 [react-loop 引入前会话](../../../.agents/notes/implemented/bug-fix/2026-08-04-load-pre-react-loop-sessions.md)决策所规定的范围受限的导入例外,并不构成通用的 v0 迁移承诺。
后端读取会在当前形状验证前,规范化明确受支持的同版本形状。消息标识机制引入前的消息会获得确定性的 id `legacy-message:<session-id>:<event-seq>`;工具结果的内容替换会继承其目标导入后的 id。react-loop 引入前的 `turn/start` 会移除过时的 trigger已移除的 steering中途引导事件 `steering/message` 会转换为同一条带标识的 `user/message`;旧版 `turn/end` 会在不虚构旧记录中未命名调用方的前提下映射终止原因。协调器对 `load``inspect``readFrom`、无所有者状态的认领和 HMR 前缀接管使用同一份规范化视图。存储仍然仅追加:读取不会重写旧记录,此后追加的事件使用当前形状。这些是[消息标识机制引入前的消息](../../../.agents/notes/implemented/bug-fix/2026-07-28-load-pre-identity-session-messages.md)与 [react-loop 引入前会话](../../../.agents/notes/implemented/bug-fix/2026-08-04-load-pre-react-loop-sessions.md)决策所规定的范围受限的导入例外,并不构成通用的 v0 迁移承诺。
实时会话发出 `session/disposed` 时,协调器等待其 controller串行化最终 drain然后释放该精确 `Session` 对象拥有的状态。失败退役会将 controller 保留在实时会话 map 中使后端拆卸可重试。后端拆卸先停止事件接纳flush 每个剩余 controller等待每 id 操作,最后才关闭存储句柄。

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/session/session-title-llm/README.md
README.md: 342687b5aa0cd35a70abf8cc66b3fe80342bce0b
README.zh.md: 10496a727baa8a515f831feb425bc3d66ad91b5f
README.md: db03205cabf9f723ee9bf6ad122026d75cb92113
README.zh.md: a214caae84aeee7a3a5c90f55e8a01ea957b823f

View File

@@ -2,7 +2,7 @@
English | [中文](README.zh.md)
Shared implementation policy for model-backed session-title providers. It resolves the auxiliary route, frames exact selected human messages as JSON, records the exact dispatchable request, applies a language-aware title instruction, enforces input and output budgets, composes timeout and caller cancellation, assembles the stream, and returns normalized text with exact source seqs and model provenance.
Shared implementation policy for model-backed session-title providers. It resolves the auxiliary route, frames exact selected human messages as JSON, records the exact dispatchable request, applies a language-aware title instruction, enforces input and output budgets, composes timeout and caller cancellation, assembles the stream, and returns normalized text with exact source seqs plus the provider/model route used to generate it.
This package is a library, not a Cordis plugin. The provider plugins call `registerSessionTitleLlmProvider()` with their cadence and message selector; it validates shared config and delegates each revision to `generateSessionTitleWithLlm()`, so registration, route, prompt, cancellation, and validation behavior cannot drift between them.

View File

@@ -2,7 +2,7 @@
[English](README.md) | 中文
由模型支持的会话标题提供方的共享实现策略。它解析辅助路由,将精确选中的用户消息封装为 JSON记录可分发的确切请求应用语言感知的标题指令强制执行输入和输出预算组合超时与调用方取消组装流并返回带有确切来源 seq 和模型来源信息的规范化文本。
由模型支持的会话标题提供方的共享实现策略。它解析辅助路由,将精确选中的用户消息封装为 JSON记录可分发的确切请求应用语言感知的标题指令强制执行输入和输出预算组合超时与调用方取消组装流并返回规范化文本,同时给出确切来源 seq 以及生成该文本时使用的提供方/模型路由
此包是普通库,不是 Cordis 插件。提供方插件调用 `registerSessionTitleLlmProvider()`,传入各自节奏与消息选择器;该函数验证共享配置,并将每次修订委派给 `generateSessionTitleWithLlm()`,使各插件的注册、路由、提示词、取消与验证行为不会漂移。

View File

@@ -146,7 +146,7 @@ export type SessionTitleLlmMessageSelector = (
* Register one model-backed provider through the shared configuration and call policy.
* @param ctx - context exposing the title and LLM services.
* @param config - untrusted required deployment policy.
* @param id - stable plugin identity recorded in title provenance.
* @param id - stable plugin id recorded with generated titles.
* @param automatic - provider-owned automatic generation cadence.
* @param selectMessages - exact source-message selection for one revision.
*/

View File

@@ -16,7 +16,7 @@ export const inject = ['invariants']
/**
* No runtime invariant: this stateless helper validates and freezes each auxiliary request before
* dispatch; deadline, stream, and provenance relationships are checked synchronously and by tests.
* dispatch; deadline, stream, cited message seqs, and provider/model fields are checked synchronously and by tests.
*/
const install: InvariantInstaller = () => {}

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/session/session-title/README.md
README.md: 13a2a1c298c3abb8c8987b9373edc46a071ce9db
README.zh.md: 075e9e31fc7acbc76f0e3fd7a2642b4d879569d3
README.md: e7004eb2e03757cb28b4b5756a876e1dee4b6229
README.zh.md: 45c1aafdf214f63ae7a071d2558eaefec13ea70f

View File

@@ -29,7 +29,7 @@ All limits are required; the library supplies no defaults.
## Provider contract
A provider supplies a branded stable id, automatic mode (`first-message` or `all-user-messages`), and `generate(request)`. The request carries the live session, all eligible messages through one fixed revision, the current logged main-request route when available, and cancellation. The result identifies a non-empty title, unique ordered source-message seqs from that request, and optional model provenance. The service normalizes and validates the result before it becomes durable.
A provider supplies a branded stable id, automatic mode (`first-message` or `all-user-messages`), and `generate(request)`. The request carries the live session, all eligible messages through one fixed revision, the current logged main-request route when available, and cancellation. The result identifies a non-empty title, unique ordered source-message seqs from that request, and the optional provider/model route used to generate it. The service normalizes and validates the result before it becomes durable.
See the [session-title data structures](../../../docs/subsystems/session-title.md) and [implemented decision](../../../.agents/notes/implemented/feature/2026-07-21-log-backed-session-titles.md).

View File

@@ -29,7 +29,7 @@ Fork 出的会话会原样继承种子中的标题事件。首消息节奏不会
## 提供方约定
提供方会提供带品牌类型的稳定 id、自动模式`first-message``all-user-messages`)和 `generate(request)`。请求携带活跃会话、截至一次固定修订的所有符合条件消息、可用时当前已记录的主请求路由,以及取消信号。结果包含非空标题、该请求中唯一且有序的来源消息 seq以及可选的模型来源信息。服务会在结果持久保存前进行规范化和验证。
提供方会提供带品牌类型的稳定 id、自动模式`first-message``all-user-messages`)和 `generate(request)`。请求携带活跃会话、截至一次固定修订的所有符合条件消息、可用时当前已记录的主请求路由,以及取消信号。结果包含非空标题、该请求中唯一且有序的来源消息 seq以及生成该标题时使用的可选提供方/模型路由。服务会在结果持久保存前进行规范化和验证。
参见[会话标题数据结构](../../../docs/subsystems/session-title.md)与[已实现决策](../../../.agents/notes/implemented/feature/2026-07-21-log-backed-session-titles.md)。

View File

@@ -63,7 +63,7 @@ export interface SessionTitleEventData {
readonly title: string
/** Exact human `user/message` seqs used to derive this title; empty for an explicit user rename. */
readonly messageSeqs: number[]
/** Built-in fallback, registered-provider, or explicit-user provenance. */
/** Whether the built-in fallback, a registered provider, or the user supplied the title. */
readonly source: SessionTitleSource
}
@@ -146,14 +146,14 @@ export interface SessionTitleProviderResult {
/** One optional asynchronous title implementation registered with the service. */
export interface SessionTitleProvider {
/** Stable provider identity recorded in title provenance. */
/** Stable id of the provider recorded with the title. */
readonly id: SessionTitleProviderId
/** When new human prompts start automatic generation. */
readonly automatic: SessionTitleAutomaticMode
/**
* Produce one title revision.
* @param request - message snapshot, current route, session, and cancellation.
* @returns proposed title plus exact input seqs and optional model provenance.
* @returns proposed title plus exact input seqs and the optional provider/model route used to generate it.
*/
generate(request: SessionTitleProviderRequest): Promise<SessionTitleProviderResult>
}
@@ -615,12 +615,12 @@ export class SessionTitleService extends Service {
let model: SessionTitleModelProvenance | undefined
if (modelCandidate !== undefined) {
if (modelCandidate === null || typeof modelCandidate !== 'object') {
throw new Error('session-title provider model provenance requires non-empty provider and model')
throw new Error('session-title provider result model must contain non-empty provider and model strings')
}
const record = modelCandidate as Record<string, unknown>
if (typeof record.provider !== 'string' || record.provider.length === 0
|| typeof record.model !== 'string' || record.model.length === 0) {
throw new Error('session-title provider model provenance requires non-empty provider and model')
throw new Error('session-title provider result model must contain non-empty provider and model strings')
}
model = { provider: record.provider, model: record.model }
}

View File

@@ -16,7 +16,7 @@ export const name = 'session-title-invariant'
export const inject = ['invariants']
/**
* Durable title-provenance invariant: an automatic title always cites at
* Durable title-source invariant: an automatic title always cites at
* least one human `user/message` seq, and an explicit user rename cites none
* — `messageSeqs` is empty iff `source.kind` is `user`. Provider revisions
* are validated by the service before their append; this checks the durable
@@ -32,7 +32,8 @@ const install: InvariantInstaller = Object.assign((ctx: Context, fail: Invariant
if (event.type !== 'session/title') return
const { source, messageSeqs } = event.data
if ((messageSeqs.length === 0) !== (source.kind === 'user')) {
fail(`session/title event ${String(event.seq)} breaks provenance: source "${source.kind}" with ${String(messageSeqs.length)} cited message seq(s)`)
const requirement = source.kind === 'user' ? 'cite no message seqs' : 'cite at least one message seq'
fail(`session/title event ${String(event.seq)} with source "${source.kind}" must ${requirement}; got ${String(messageSeqs.length)}`)
}
}, { global: true })
}, { inject: ['sessions'] })

View File

@@ -1,4 +1,4 @@
// Title-provenance invariant: messageSeqs is empty iff source.kind is 'user'
// Title-source invariant: `messageSeqs` is empty iff `source.kind` is `user`.
// — the durable relationship every appended session/title event must keep.
import { describe, expect, it } from 'vitest'
import { Context } from 'cordis'
@@ -14,7 +14,7 @@ async function setup(): Promise<Context> {
return ctx
}
describe('session-title provenance invariant', () => {
describe('session-title source invariant', () => {
it('accepts cited automatic titles and citation-free user renames', async () => {
const ctx = await setup()
const session = ctx.sessions.create(SessionId('title-invariant-valid'))

View File

@@ -436,12 +436,12 @@ describe('SessionTitleService provider validation and stale scheduling', () => {
{ value: { title: 'valid', messageSeqs: [999] }, error: /unique, ordered seqs/ },
{ value: { title: 'valid', messageSeqs: [first.seq, first.seq] }, error: /unique, ordered seqs/ },
{ value: { title: 'valid', messageSeqs: [second.seq, first.seq] }, error: /unique, ordered seqs/ },
{ value: { title: 'valid', messageSeqs: [first.seq], model: null }, error: /model provenance/ },
{ value: { title: 'valid', messageSeqs: [first.seq], model: 'route' }, error: /model provenance/ },
{ value: { title: 'valid', messageSeqs: [first.seq], model: { provider: 1, model: 'm' } }, error: /model provenance/ },
{ value: { title: 'valid', messageSeqs: [first.seq], model: { provider: '', model: 'm' } }, error: /model provenance/ },
{ value: { title: 'valid', messageSeqs: [first.seq], model: { provider: 'p', model: 1 } }, error: /model provenance/ },
{ value: { title: 'valid', messageSeqs: [first.seq], model: { provider: 'p', model: '' } }, error: /model provenance/ },
{ value: { title: 'valid', messageSeqs: [first.seq], model: null }, error: /provider result model/ },
{ value: { title: 'valid', messageSeqs: [first.seq], model: 'route' }, error: /provider result model/ },
{ value: { title: 'valid', messageSeqs: [first.seq], model: { provider: 1, model: 'm' } }, error: /provider result model/ },
{ value: { title: 'valid', messageSeqs: [first.seq], model: { provider: '', model: 'm' } }, error: /provider result model/ },
{ value: { title: 'valid', messageSeqs: [first.seq], model: { provider: 'p', model: 1 } }, error: /provider result model/ },
{ value: { title: 'valid', messageSeqs: [first.seq], model: { provider: 'p', model: '' } }, error: /provider result model/ },
]
for (const item of cases) {
result = item.value