新增: 待发箱/唤醒原语/预算随窗口 + 治理门禁与场景护栏

- 待发箱地基: V51 ai_outbox 表 + 手写 OutboxRepo(claim CAS/崩溃残留复位/瞬时失败保留) + outbox.rs 调度器(KV 默认关/Notify 唤醒/会话忙跳过不丢)
- Wake 异步唤醒原语: 三条件(delay/file-appear/process-exit)+纯判定+注入走正典 spawn_agentic_session,默认关
- 上下文预算随模型窗口: model_context.rs 三级优先(池显式>预设表>回退 128K);上界取 1M 而非 128K——max_tokens 仅裁剪预算,卡 128K 等于不治根因
- 文件租约补纯函数与单测(未另造双轨:LEASE 早随 cb260ab 落地) / 跨会话召回补纯函数单测(主体早已落地)
- 子代理: 完整卡已消化 + 子会话压缩层 + P3 批量
- 隧道/中继: 指令离线不丢 + Hello proto 版本 + 心跳与 runbook
- df-mcp: 写工具后落 KV 变更标记(供 GUI 轮询兜底,补独立 stdio 进程无感知)
- 迁移链顶点改单一真值常量 LATEST_SCHEMA_VERSION(修 2 处因 V51 失效的断言)
- 治理: i18n 特殊字符提交门禁(含反引号覆盖 + 10 自测) / 根目录卫生门禁 / 看板归档器 / 单一 lockfile(bun) / swap-install 落 temp/ 且兼容无 Get-FileHash
- 场景护栏: 流式正文可见性 e2e(playwright 免真机事件回放) + 列表续写/外置句柄 node 测试
This commit is contained in:
lxy
2026-10-10 08:09:17 +08:00
parent 9c68f98d89
commit b37279f3a2
32 changed files with 4175 additions and 152 deletions
+40
View File
@@ -75,6 +75,17 @@ fn validate_device_id(id: &str) -> bool {
!id.is_empty() && id.len() <= 128 && id.chars().all(|c| !c.is_control())
}
/// 当前 Hello 握手协议版本。
///
/// 协商规则:旧客户端 Hello 缺 `proto` 字段 → serde default 视作 v1(向后兼容);
/// 高于本端支持版本时记录告警,为后续破坏性变更预留通知位(当前仅日志,不拒绝握手)。
pub const HELLO_PROTO_VERSION: u32 = 1;
/// `proto` 缺省值:旧客户端不带该字段时按 v1 处理(向后兼容)。
fn default_proto() -> u32 {
HELLO_PROTO_VERSION
}
/// 客户端首消息:身份宣告(简单协议)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Hello {
@@ -84,6 +95,9 @@ pub struct Hello {
pub device_id: String,
/// 配对 token
pub token: String,
/// 协议版本(缺省按 v1,兼容旧客户端)
#[serde(default = "default_proto")]
pub proto: u32,
}
/// Hello.kind 的传输表示(serde 字符串,与 ClientKind 解耦避免 rename 歧义)
@@ -298,6 +312,16 @@ async fn handle_connection(socket: WebSocket, state: RelayState, expected: Clien
return;
}
// 版本协商:proto 缺省按 v1(旧客户端不带字段,向后兼容)。
// 高于本端支持版本时告警 —— 为后续破坏性变更预留的通知位(当前仅日志,不拒绝握手)。
if hello.proto > HELLO_PROTO_VERSION {
tracing::warn!(
client_proto = hello.proto,
server_proto = HELLO_PROTO_VERSION,
"客户端协议版本高于本端,可能存在不兼容(预留通知位)"
);
}
let conn_id = next_conn_id();
let kind: ClientKind = hello.kind.into();
let device_id = hello.device_id.clone();
@@ -305,6 +329,7 @@ async fn handle_connection(socket: WebSocket, state: RelayState, expected: Clien
conn_id = conn_id.0,
?kind,
device_id = %device_id,
proto = hello.proto,
"连接握手通过,进入收发循环"
);
@@ -545,4 +570,19 @@ mod tests {
fn max_frame_is_one_mib() {
assert_eq!(MAX_FRAME_BYTES, 1024 * 1024);
}
#[test]
fn hello_proto_defaults_to_v1_when_absent() {
// 旧客户端不发 proto → 反序列化按 v1,保证向后兼容
let json = r#"{"kind":"device","device_id":"dev-1","token":"t"}"#;
let h: Hello = serde_json::from_str(json).expect("缺 proto 应可解析");
assert_eq!(h.proto, HELLO_PROTO_VERSION);
}
#[test]
fn hello_proto_parsed_when_present() {
let json = r#"{"kind":"device","device_id":"dev-1","token":"t","proto":2}"#;
let h: Hello = serde_json::from_str(json).unwrap();
assert_eq!(h.proto, 2);
}
}