style(server): gofmt 注释规范 + init.sql 补 parent_session_id + 新增 ratelimit 判据
## 主体是 gofmt
`server/` 下 20 个文件的改动里,**142/147 行是注释与缩进** ——
Go 1.19+ 的 gofmt 把块注释里的 `// x` 规范成 `//\tx`。
功能零变化。
## 一处实质:init.sql 补 parent_session_id
`ALTER TABLE sessions ADD COLUMN IF NOT EXISTS parent_session_id
TEXT REFERENCES sessions(session_id)`
远端已在 `db/migrate.go` 的 `sqliteAddColumns` 结构迁移通道里加了同一列;
init.sql 是**全新库**的建库脚本,缺这列会让新库少一个字段。
- `IF NOT EXISTS` ⇒ 与结构迁移通道幂等,两者并存无害
- 默认空 = 根节点;**不据历史数据猜父子**(猜错会让线索拓扑失真,比没有更糟)
## 新增 handler/ratelimit_test.go(162 行)
速率限制的判据。此前这一块没有独立判据。
## 验证(本机实跑,非推断)
go build ./... 通过
go vet ./internal/{handler,repo,db,sse}/ 通过
go test -count=1(handler/repo/db/sse/blob)
handler 41.3s ok
repo 101.9s ok
db 1.1s ok
sse 0.01s ok
blob 0.08s ok
`-count=1` 是刻意的:第一遍跑出来全是 `(cached)`,那种绿灯不算数。
This commit is contained in:
@ -136,4 +136,4 @@ func SessionLockTouch(ctx context.Context, sessionID uuid.UUID) (bool, error) {
|
||||
}
|
||||
n, _ := tag.RowsAffected()
|
||||
return n > 0, nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -219,4 +219,4 @@ func TestSessionLockReadFailureIsNotTreatedAsUnlocked(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
var _ = uuid.New
|
||||
var _ = uuid.New
|
||||
|
||||
@ -15,10 +15,10 @@ import (
|
||||
生产上 `pi ↔ dsh` 一天跑了 **175 封**、正文 612KB,其中 **relay = 0** ——
|
||||
全部是模型**主动**调 send_mail。而两道防线都只覆盖 `relay != ""` 那条路:
|
||||
|
||||
① 会话预算:`relay != ""` 才扣 ⇒ 主动发信不扣(且该会话 max_rounds=0=不限)
|
||||
② maxRelayHops=5:`if relay != "" { ... }` ⇒ 根本不进那个分支
|
||||
③ 插件自动转发守卫:日志明说"本轮不自动转发:来信方 dsh 是 Agent"
|
||||
—— 守卫**工作正常**,但模型自己发的不受它管
|
||||
① 会话预算:`relay != ""` 才扣 ⇒ 主动发信不扣(且该会话 max_rounds=0=不限)
|
||||
② maxRelayHops=5:`if relay != "" { ... }` ⇒ 根本不进那个分支
|
||||
③ 插件自动转发守卫:日志明说"本轮不自动转发:来信方 dsh 是 Agent"
|
||||
—— 守卫**工作正常**,但模型自己发的不受它管
|
||||
|
||||
⇒ 三条全绕开。没有任何一层在数"两个 Agent 在没有人类参与的情况下连续往返了几次"。
|
||||
|
||||
@ -27,8 +27,8 @@ import (
|
||||
从最新一封往前扫,**连续**的"发件方是 Agent 且收件方也是 Agent"的邮件计数,
|
||||
遇到以下任一即停(= 归零):
|
||||
|
||||
· 人类发的信(`from_name` 是用户)
|
||||
· 人类收的信(`to_name` 是用户)
|
||||
· 人类发的信(`from_name` 是用户)
|
||||
· 人类收的信(`to_name` 是用户)
|
||||
|
||||
"连续"与"人类参与即归零"这两点与 maxRelayHops 一致 —— 正常的
|
||||
「Agent 干完活回一封、人类看一眼再派下一轮」不受影响;只掐住
|
||||
|
||||
@ -75,8 +75,10 @@ func TestAgentPingPongCountsConsecutive(t *testing.T) {
|
||||
// ★ 核心语义:**人类插一句话,计数归零**。
|
||||
//
|
||||
// 这一条决定了这个判据会不会误伤正常用法:
|
||||
// · 人类说一句 → Agent 回十封 → 人类再一句 ⇒ 每次都在阈值以下
|
||||
// · 没有人说话 → 一直涨 ⇒ 到阈值拒绝
|
||||
//
|
||||
// · 人类说一句 → Agent 回十封 → 人类再一句 ⇒ 每次都在阈值以下
|
||||
// · 没有人说话 → 一直涨 ⇒ 到阈值拒绝
|
||||
//
|
||||
// 与 maxRelayHops 的"连续"同构 —— 两条防线在这个语义上必须一致,
|
||||
// 否则同一个现象在两处得到不同的结论。
|
||||
func TestAgentPingPongResetsOnHuman(t *testing.T) {
|
||||
@ -145,13 +147,13 @@ func TestAgentPingPongNotResetByAgents(t *testing.T) {
|
||||
被 CountTrailingAgentPingPong 一并计入「Agent 互发」计数。它同样满足
|
||||
「发件方 Agent + 收件方 Agent + 无人类」,于是:
|
||||
|
||||
① 退信把计数顶到 8 ⇒ 闸被**插件自己**触发;
|
||||
② 闸在 send 入口、先于 relay 分支 ⇒ 退信本身也被 403;
|
||||
③ 退信发不出去 ⇒ 发件人只看到「模型未产生回复」(桥的 sendFailureReply
|
||||
同样发不出,真因被吞);
|
||||
④ 被拒的尝试不产生新邮件 ⇒ 计数永不回落 ⇒ **会话永久死锁**:
|
||||
模型回信 → 403 → 退信也 403 → 发件人重发 → 模型再回 → 再 403。
|
||||
生产上该会话 09-29 22:01 至 09-30 10:11 被 403 拒了 7 次。
|
||||
① 退信把计数顶到 8 ⇒ 闸被**插件自己**触发;
|
||||
② 闸在 send 入口、先于 relay 分支 ⇒ 退信本身也被 403;
|
||||
③ 退信发不出去 ⇒ 发件人只看到「模型未产生回复」(桥的 sendFailureReply
|
||||
同样发不出,真因被吞);
|
||||
④ 被拒的尝试不产生新邮件 ⇒ 计数永不回落 ⇒ **会话永久死锁**:
|
||||
模型回信 → 403 → 退信也 403 → 发件人重发 → 模型再回 → 再 403。
|
||||
生产上该会话 09-29 22:01 至 09-30 10:11 被 403 拒了 7 次。
|
||||
|
||||
正确形状:relay 邮件归 CountTrailingRelayHops(阈值 5,更严)管,
|
||||
不挤占「模型主动互发」的额度(阈值 8)。两条闸独立计数、互不侵占。
|
||||
|
||||
@ -42,7 +42,6 @@ type debt struct {
|
||||
* ⇒ 直接删掉副本、让 `debtSummary()` 读权威 JSON(同一个事实不留两份)。
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* `debtMark` 由各条欠账的判据在"确认未清"时调用。
|
||||
*
|
||||
|
||||
@ -271,11 +271,13 @@ func TestPlatformSessionsWithoutSlugAreSkipped(t *testing.T) {
|
||||
// 活库实测:200 样本 × 0.3s 里 25% 的时刻镜像为空、9 个目录轮流出场。
|
||||
//
|
||||
// ★ 这条**不依赖**请求级 scope 字段:每项 `PlatformSession` 自带 `Workspace`,
|
||||
// 所以**今天就能写、现在就是红、修好即绿**。请求级字段只与 (d2)(上报 `[]`)有关。
|
||||
//
|
||||
// 所以**今天就能写、现在就是红、修好即绿**。请求级字段只与 (d2)(上报 `[]`)有关。
|
||||
//
|
||||
// ⚠️ 本条**当前为红**——它断言的正是那个尚未修复的缺陷。
|
||||
// 按本仓惯例,红的判据要么标 `[FAIL]` 形态、要么随修复一起转绿;
|
||||
// 这里用 `t.Errorf` 而不是 `t.Fatalf`,好让"为什么红"直接显示在测试输出里。
|
||||
//
|
||||
// 按本仓惯例,红的判据要么标 `[FAIL]` 形态、要么随修复一起转绿;
|
||||
// 这里用 `t.Errorf` 而不是 `t.Fatalf`,好让"为什么红"直接显示在测试输出里。
|
||||
func TestReplacePlatformSessionsKeepsOtherWorkspaces(t *testing.T) {
|
||||
setupTestDB(t)
|
||||
seedPlatformAgent(t, "opencode")
|
||||
|
||||
Reference in New Issue
Block a user