@sema-agent/server 7.60.0 → 7.61.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.
|
@@ -352,7 +352,7 @@ export declare class SqlRetentionStore implements ManagedRetentionCapability {
|
|
|
352
352
|
* **可删**的会话 id:本域、过视界、且四条活引用腿**一条都不命中**(保护谓词下推,扣在 LIMIT 之前 ——
|
|
353
353
|
* 理由与 {@link expiryCandidates} 逐字相同:头部被保护的树不许饿死它后面的树)。
|
|
354
354
|
*
|
|
355
|
-
* `
|
|
355
|
+
* `forUpdate = true`(删除事务里的读)追加 `FOR UPDATE`,这一格是**承重**的(codex 交叉复审 R1-[high]):
|
|
356
356
|
* · 会话行是**存在**的行 ⇒ 这把锁与 hold 哨兵不同,它真的锁得住;
|
|
357
357
|
* · 锁住之后,并发的 `touch()`(把会话重新拉进视界)与 session-sync 的 **owner 改写**(importEntries /
|
|
358
358
|
* replaceEntries / staged commit 都会重写 `session_meta.owner`)必须排队等我们提交 —— 否则「按旧候选集
|
|
@@ -291,11 +291,12 @@ export class SqlRetentionStore {
|
|
|
291
291
|
`AND ${this.liveReferenceExists()} LIMIT $3) x`), [...w.params, new Date(input.cutoffMs), RETENTION_SESSION_BATCH]);
|
|
292
292
|
return Number(rows[0]?.n ?? 0);
|
|
293
293
|
}
|
|
294
|
-
async sessionCandidates(exec, input,
|
|
294
|
+
async sessionCandidates(exec, input, forUpdate = false) {
|
|
295
295
|
const w = this.domainWhere("sm.owner", input.domain, 1);
|
|
296
|
+
const suffix = forUpdate ? " FOR UPDATE" : "";
|
|
296
297
|
const { rows } = await exec.query(this.q(`SELECT sm.session_id FROM session_meta sm WHERE ${w.sql} AND sm.updated_at < ? AND NOT ${this.liveReferenceExists()} ` +
|
|
297
|
-
`ORDER BY sm.updated_at ASC LIMIT ?${
|
|
298
|
-
`ORDER BY sm.updated_at ASC LIMIT $3${
|
|
298
|
+
`ORDER BY sm.updated_at ASC LIMIT ?${suffix}`, `SELECT sm.session_id FROM session_meta sm WHERE ${w.sql} AND sm.updated_at < $2 AND NOT ${this.liveReferenceExists()} ` +
|
|
299
|
+
`ORDER BY sm.updated_at ASC LIMIT $3${suffix}`), [...w.params, new Date(input.cutoffMs), RETENTION_SESSION_BATCH]);
|
|
299
300
|
return rows.map((r) => String(r.session_id));
|
|
300
301
|
}
|
|
301
302
|
async countOrphanToolResultCandidates(exec, input) {
|
|
@@ -161,12 +161,14 @@ export interface SqlEngineFacts {
|
|
|
161
161
|
export interface SqlDriver extends SqlExec {
|
|
162
162
|
readonly dialect: SqlDialect;
|
|
163
163
|
/**
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
* `null`
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
164
|
+
* 本池上**最近一次连接初始化的结果**(状态按池存,所以共享一条池的二十多只店 driver 报同一份事实)。
|
|
165
|
+
* 三种读数,含义各不相同:
|
|
166
|
+
* · `null`(还没借出过连接)—— 没有可报的东西,**不猜**;
|
|
167
|
+
* · 事实 —— 最近一次初始化**回读复核**过的值。每条池连接首次借出都重跑一次初始化(记忆化按连接身份),
|
|
168
|
+
* 所以池在致命错误后重连时,保证是在新连接上**重新建立**的,不是继承来的;
|
|
169
|
+
* · `null`(借出过、但最近一次初始化被**拒**)—— S-137 ④:保证此刻建立不起来,读面就报缺席。
|
|
170
|
+
* 此前这里是「一次成功即长期有效」的快照:连接被驱逐、新连接初始化被拒之后,诊断面仍在陈述一条
|
|
171
|
+
* 已经不成立的结构保证。降级由 `establishSession()` 一处完成,并留 loud warn。
|
|
170
172
|
*/
|
|
171
173
|
readonly facts: SqlEngineFacts | null;
|
|
172
174
|
connect(): Promise<SqlTxConn>;
|
|
@@ -37,6 +37,24 @@ function refuseSession(what) {
|
|
|
37
37
|
`are established per pooled connection at initialization: session REPEATABLE READ on the MySQL protocol, plus TiDB's pessimistic transaction mode. ` +
|
|
38
38
|
`Neither is optional: every "SELECT … FOR UPDATE" decision point in this service is a CURRENT read only under them.`);
|
|
39
39
|
}
|
|
40
|
+
async function establishSession(session, pooled, c, init) {
|
|
41
|
+
if (session.initialized.has(pooled))
|
|
42
|
+
return;
|
|
43
|
+
try {
|
|
44
|
+
session.facts = await init(c);
|
|
45
|
+
session.initialized.add(pooled);
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
const stale = session.facts;
|
|
49
|
+
session.facts = null;
|
|
50
|
+
session.initialized = new WeakSet();
|
|
51
|
+
if (stale !== null)
|
|
52
|
+
console.warn(`[sql-driver] connection initialization REFUSED — SQL engine facts invalidated (was: ${stale ? `${stale.engine} ${stale.version} ${stale.isolation} txnMode=${stale.txnMode ?? "-"}` : "<absent>"}). GET /v1/diagnostics/wiring -> sqlEngine and GET /v1/capabilities -> sql now report ABSENT, which is the honest ` +
|
|
53
|
+
`reading: this pool cannot currently establish the read semantics every "SELECT … FOR UPDATE" in this repo assumes. Cause: ${err.message}`);
|
|
54
|
+
c.release();
|
|
55
|
+
throw err;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
40
58
|
async function initMySqlSession(c) {
|
|
41
59
|
await c.query("SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ");
|
|
42
60
|
let txnMode = await mysqlVar(c, "SELECT @@tidb_txn_mode AS v");
|
|
@@ -72,17 +90,7 @@ export function mysqlDriver(pool) {
|
|
|
72
90
|
query: (sql, params) => mysqlQuery(pool, sql, params),
|
|
73
91
|
async connect() {
|
|
74
92
|
const c = await pool.getConnection();
|
|
75
|
-
|
|
76
|
-
try {
|
|
77
|
-
if (!session.initialized.has(pooled)) {
|
|
78
|
-
session.facts = await initMySqlSession(c);
|
|
79
|
-
session.initialized.add(pooled);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
catch (err) {
|
|
83
|
-
c.release();
|
|
84
|
-
throw err;
|
|
85
|
-
}
|
|
93
|
+
await establishSession(session, c.connection ?? c, c, initMySqlSession);
|
|
86
94
|
return {
|
|
87
95
|
query: (sql, params) => mysqlQuery(c, sql, params),
|
|
88
96
|
begin: () => c.beginTransaction(),
|
|
@@ -122,16 +130,7 @@ export function pgDriver(pool) {
|
|
|
122
130
|
query: (sql, params) => pgQuery(pool, sql, params),
|
|
123
131
|
async connect() {
|
|
124
132
|
const c = await pool.connect();
|
|
125
|
-
|
|
126
|
-
if (!session.initialized.has(c)) {
|
|
127
|
-
session.facts = await initPgSession(c);
|
|
128
|
-
session.initialized.add(c);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
catch (err) {
|
|
132
|
-
c.release();
|
|
133
|
-
throw err;
|
|
134
|
-
}
|
|
133
|
+
await establishSession(session, c, c, initPgSession);
|
|
135
134
|
return {
|
|
136
135
|
query: (sql, params) => pgQuery(c, sql, params),
|
|
137
136
|
begin: async () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/server",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.61.0",
|
|
4
4
|
"description": "Sema Server — the server/API implementation layer for Sema, wiring core, registry, model providers, and cloud agent execution. Built on @sema-agent/core.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "BUSL-1.1",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"build:binary:run-local:darwin-arm64": "bun build --compile --target=bun-darwin-arm64 src/run-local.ts --outfile dist/run-local-darwin-arm64"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@sema-agent/core": "7.5.
|
|
57
|
+
"@sema-agent/core": "7.5.1",
|
|
58
58
|
"@sema-agent/settings-schema": "1.6.0",
|
|
59
59
|
"e2b": "^2.28.0",
|
|
60
60
|
"libsodium-wrappers": "^0.8.4",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"sharp": "^0.35.3"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
|
-
"@sema-agent/sdk": "8.
|
|
72
|
+
"@sema-agent/sdk": "8.3.0",
|
|
73
73
|
"@types/libsodium-wrappers": "^0.7.14",
|
|
74
74
|
"@types/node": "22.10.2",
|
|
75
75
|
"@types/pg": "^8.20.0",
|