@sema-agent/server 7.139.0 → 7.140.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/MIGRATION.md +181 -0
- package/USAGE.md +19 -13
- package/dist/adoption/runner.js +1 -1
- package/dist/adoption/sql.js +1 -1
- package/dist/approval-card.d.ts +6 -6
- package/dist/bench/s1/live-deps.js +3 -2
- package/dist/bench/s1/row.d.ts +5 -2
- package/dist/bench/s1/row.js +2 -22
- package/dist/boot/adoption.js +1 -1
- package/dist/boot/execution-env.js +12 -10
- package/dist/boot/execution-lane.d.ts +8 -0
- package/dist/boot/execution-lane.js +6 -1
- package/dist/boot/memory-boundary.d.ts +14 -51
- package/dist/boot/memory-boundary.js +10 -20
- package/dist/boot/memory-consolidation.d.ts +15 -11
- package/dist/boot/memory-consolidation.js +23 -12
- package/dist/boot/resolve-spec.d.ts +3 -5
- package/dist/boot/resolve-spec.js +2 -4
- package/dist/boot/runner-deps.d.ts +3 -3
- package/dist/boot/runner-deps.js +1 -0
- package/dist/boot/stage-03-store-backend.d.ts +2 -6
- package/dist/boot/stage-03-store-backend.js +2 -3
- package/dist/boot/stage-04-stores.d.ts +2 -6
- package/dist/boot/stage-04-stores.js +5 -5
- package/dist/boot/stage-05-execution-env.d.ts +2 -6
- package/dist/boot/stage-06-runners.d.ts +2 -6
- package/dist/boot/stage-06-runners.js +2 -2
- package/dist/boot/stage-07-capability-layer.d.ts +2 -6
- package/dist/boot/stage-08-reapers.d.ts +2 -6
- package/dist/boot/stage-09-leader.d.ts +2 -6
- package/dist/boot/stage-10-http-server.d.ts +2 -6
- package/dist/boot/stage-10-http-server.js +5 -4
- package/dist/boot/stores.d.ts +3 -6
- package/dist/boot/stores.js +27 -40
- package/dist/boot/task-list-lane.d.ts +1 -1
- package/dist/boot/task-list-lane.js +1 -1
- package/dist/boot/web-admin.js +35 -9
- package/dist/capabilities/execution-lane.d.ts +2 -2
- package/dist/config-catalog.js +2 -1
- package/dist/config-provider.d.ts +14 -13
- package/dist/config-provider.js +24 -18
- package/dist/config-types.d.ts +11 -2
- package/dist/config.js +2 -1
- package/dist/delegated-session-owner.d.ts +2 -1
- package/dist/delegated-session-owner.js +1 -1
- package/dist/deployment-shape.d.ts +9 -8
- package/dist/deployment-shape.js +4 -4
- package/dist/execution-lane-caps.d.ts +97 -106
- package/dist/execution-lane-caps.js +67 -83
- package/dist/http/memory-compliance-failure-reply.d.ts +3 -1
- package/dist/http/memory-compliance-failure-reply.js +5 -0
- package/dist/http/memory-layer-reply.d.ts +30 -0
- package/dist/http/memory-layer-reply.js +24 -0
- package/dist/http/resume-legs.js +13 -16
- package/dist/http/retired-service-deps.d.ts +1 -1
- package/dist/http/retired-service-deps.js +1 -1
- package/dist/http/routes/adoption.js +1 -1
- package/dist/http/routes/memory-bundle.js +3 -0
- package/dist/http/routes/memory-compliance.js +8 -1
- package/dist/http/routes/memory-consolidation.js +12 -1
- package/dist/http/routes/memory-origin.js +14 -2
- package/dist/http/routes/memory-policy.js +5 -0
- package/dist/http/routes/sessions.js +28 -11
- package/dist/http/routes/tasks.js +4 -2
- package/dist/http/server.d.ts +5 -25
- package/dist/http/server.js +2 -2
- package/dist/identity-source.d.ts +1 -1
- package/dist/identity-source.js +1 -1
- package/dist/lane-home-probe.d.ts +68 -42
- package/dist/lane-home-probe.js +53 -30
- package/dist/leader/wire.js +5 -4
- package/dist/memory-bundle-engine.d.ts +16 -9
- package/dist/memory-bundle-engine.js +29 -6
- package/dist/memory-operator-faces.d.ts +85 -30
- package/dist/memory-operator-faces.js +136 -30
- package/dist/memory-project-layer.d.ts +3 -21
- package/dist/memory-project-layer.js +11 -8
- package/dist/memory-scope-stores.d.ts +73 -0
- package/dist/memory-scope-stores.js +86 -0
- package/dist/memory-scope.d.ts +29 -5
- package/dist/memory-scope.js +16 -4
- package/dist/memory-sync-client.d.ts +20 -7
- package/dist/memory-sync-client.js +31 -13
- package/dist/plugins/adoption-log-sql.js +4 -4
- package/dist/plugins/approval-ask-store-sql.js +14 -14
- package/dist/plugins/approval-nonce-store.js +4 -4
- package/dist/plugins/blob-backend.d.ts +4 -4
- package/dist/plugins/blob-backend.js +7 -7
- package/dist/plugins/breaker-state-sql.js +2 -2
- package/dist/plugins/checkpoint-index-columns.js +2 -2
- package/dist/plugins/checkpoint-store-sql.js +16 -16
- package/dist/plugins/device-store-sql.js +19 -19
- package/dist/plugins/ensure-index.d.ts +1 -2
- package/dist/plugins/execution-placement-sql.d.ts +8 -8
- package/dist/plugins/execution-placement-sql.js +8 -8
- package/dist/plugins/file-history-store-sql.d.ts +2 -2
- package/dist/plugins/file-history-store-sql.js +4 -4
- package/dist/plugins/image-bake-store-sql.js +9 -9
- package/dist/plugins/image-index-sql.js +11 -11
- package/dist/plugins/leader-run-store-sql.js +4 -4
- package/dist/plugins/mailbox-store-sql.d.ts +2 -1
- package/dist/plugins/mailbox-store-sql.js +4 -4
- package/dist/plugins/memory-engine-tidb.js +5 -5
- package/dist/plugins/memory-optout-grant-store-sql.js +1 -1
- package/dist/plugins/memory-sync-store-tidb.js +3 -3
- package/dist/plugins/one-time-migrations.d.ts +10 -9
- package/dist/plugins/one-time-migrations.js +36 -36
- package/dist/plugins/org-delivery-quota-store.js +2 -2
- package/dist/plugins/outcome-ledger-sql.js +2 -2
- package/dist/plugins/permission-rule-store-sql.d.ts +4 -4
- package/dist/plugins/permission-rule-store-sql.js +16 -16
- package/dist/plugins/remote-env-adb.d.ts +7 -24
- package/dist/plugins/remote-env-adb.js +22 -5
- package/dist/plugins/remote-env-device.d.ts +2 -28
- package/dist/plugins/remote-env-device.js +2 -1
- package/dist/plugins/remote-env-e2b.d.ts +18 -26
- package/dist/plugins/remote-env-e2b.js +51 -4
- package/dist/plugins/remote-env-file-error.d.ts +1 -1
- package/dist/plugins/remote-env-file-error.js +8 -2
- package/dist/plugins/remote-env-host.d.ts +19 -35
- package/dist/plugins/remote-env-host.js +26 -3
- package/dist/plugins/remote-env-k8s.d.ts +3 -14
- package/dist/plugins/remote-env-k8s.js +3 -2
- package/dist/plugins/remote-env-local-docker.d.ts +4 -15
- package/dist/plugins/remote-env-local-docker.js +5 -4
- package/dist/plugins/remote-env-ssh.d.ts +4 -24
- package/dist/plugins/remote-env-ssh.js +4 -3
- package/dist/plugins/resume-anchor-store-sql.js +2 -2
- package/dist/plugins/retention-lane-store-sql.d.ts +1 -1
- package/dist/plugins/retention-lane-store-sql.js +2 -2
- package/dist/plugins/retention-store-sql.d.ts +1 -1
- package/dist/plugins/retention-store-sql.js +6 -6
- package/dist/plugins/run-store-sql.js +15 -15
- package/dist/plugins/schema-consistency.d.ts +6 -6
- package/dist/plugins/schema-consistency.js +1 -1
- package/dist/plugins/schema-ensure-legs.d.ts +2 -3
- package/dist/plugins/schema-ensure-legs.js +2 -2
- package/dist/plugins/schema-live-columns.d.ts +1 -1
- package/dist/plugins/session-capture-record-store-sql.js +1 -1
- package/dist/plugins/session-rules-column.d.ts +1 -1
- package/dist/plugins/session-rules-column.js +1 -1
- package/dist/plugins/session-workspace-cell.d.ts +1 -1
- package/dist/plugins/session-workspace-cell.js +1 -1
- package/dist/plugins/shared-memory-store-sql.js +2 -2
- package/dist/plugins/sql-driver.d.ts +10 -5
- package/dist/plugins/sql-driver.js +6 -6
- package/dist/plugins/sql-errors.js +1 -1
- package/dist/plugins/store-backend.d.ts +4 -6
- package/dist/plugins/store-backend.js +6 -6
- package/dist/plugins/task-list-store-sql.js +11 -11
- package/dist/plugins/tidb-pool.js +5 -5
- package/dist/plugins/tidb-session-storage.js +4 -4
- package/dist/plugins/tidb-session-store.js +13 -13
- package/dist/plugins/tool-result-store-sql.d.ts +2 -2
- package/dist/plugins/tool-result-store-sql.js +3 -3
- package/dist/plugins/user-input-ledger-sql.js +2 -2
- package/dist/plugins/workflow-journal-store-sql.js +5 -5
- package/dist/plugins/workflow-run-store-sql.js +19 -19
- package/dist/plugins/write-behind-counter.d.ts +2 -3
- package/dist/plugins/write-behind-counter.js +5 -5
- package/dist/resource-suspend.d.ts +14 -12
- package/dist/resource-suspend.js +1 -1
- package/dist/resource-window.d.ts +4 -3
- package/dist/run-local.js +14 -19
- package/dist/runs.d.ts +8 -4
- package/dist/runs.js +49 -65
- package/dist/session-incarnation.d.ts +1 -1
- package/dist/session-incarnation.js +1 -1
- package/dist/session-policy-wire.d.ts +1 -1
- package/dist/sql-engine-posture.d.ts +1 -1
- package/dist/sql-engine-posture.js +1 -1
- package/dist/{plugins → stores-sql}/background-agent-store-sql.d.ts +2 -2
- package/dist/{plugins → stores-sql}/background-agent-store-sql.js +8 -8
- package/dist/{plugins → stores-sql}/session-policy-store-sql.d.ts +7 -5
- package/dist/{plugins → stores-sql}/session-policy-store-sql.js +12 -12
- package/dist/{plugins → stores-sql}/usage-window-store-sql.d.ts +1 -1
- package/dist/{plugins → stores-sql}/usage-window-store-sql.js +3 -3
- package/dist/task-a2a.js +5 -6
- package/dist/task-mcp.d.ts +20 -7
- package/dist/task-mcp.js +17 -9
- package/dist/terminal.d.ts +27 -0
- package/dist/terminal.js +6 -0
- package/dist/trace/redact.d.ts +3 -1
- package/dist/trace/redact.js +80 -28
- package/dist/trace/self-minted-wire.d.ts +58 -0
- package/dist/trace/self-minted-wire.js +16 -0
- package/dist/web-admin-client/candidate-gate.d.ts +11 -7
- package/dist/web-admin-client/candidate-gate.js +1 -1
- package/dist/web-admin-client/http-client.d.ts +5 -4
- package/dist/web-admin-client/http-client.js +2 -2
- package/dist/web-admin-client/read-warnings.d.ts +7 -17
- package/dist/web-admin-client/read-warnings.js +5 -9
- package/package.json +2 -2
- package/wire-changes/server@7.140.0.json +184 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { resolveMemoryScopeStore } from "@sema-agent/core";
|
|
2
|
+
import { memoryScopeContractOf } from "./memory-scope.js";
|
|
3
|
+
export function createMemoryScopeStores(input) {
|
|
4
|
+
const byPlane = new Map();
|
|
5
|
+
const resolve = (layer, backend) => resolveMemoryScopeStore({
|
|
6
|
+
scope: layer,
|
|
7
|
+
scopeContract: memoryScopeContractOf([layer]),
|
|
8
|
+
backend,
|
|
9
|
+
memoryEngineDir: input.root,
|
|
10
|
+
...(input.onIncident !== undefined ? { onIncident: input.onIncident } : {}),
|
|
11
|
+
});
|
|
12
|
+
return {
|
|
13
|
+
backend: input.backend,
|
|
14
|
+
root: input.root,
|
|
15
|
+
namedLayers: input.namedLayers,
|
|
16
|
+
storeOf(layer) {
|
|
17
|
+
const { plane } = resolve(layer, PLANE_PROBE);
|
|
18
|
+
const known = byPlane.get(plane);
|
|
19
|
+
if (known !== undefined)
|
|
20
|
+
return known;
|
|
21
|
+
const store = resolve(layer, input.backend).backend;
|
|
22
|
+
byPlane.set(plane, store);
|
|
23
|
+
return store;
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const PLANE_PROBE = Object.freeze({});
|
|
28
|
+
export class MemoryLayerRefusal extends Error {
|
|
29
|
+
code = "request.memory_layer";
|
|
30
|
+
constructor(message, cause) {
|
|
31
|
+
super(message, cause !== undefined ? { cause } : undefined);
|
|
32
|
+
this.name = "MemoryLayerRefusal";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const quoted = (layers) => layers.map((l) => JSON.stringify(l)).join(", ");
|
|
36
|
+
function storeOfNamed(stores, layer, whose, seat) {
|
|
37
|
+
try {
|
|
38
|
+
return stores.storeOf(layer);
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
if (err?.code !== "config.memory_scope_key")
|
|
42
|
+
throw err;
|
|
43
|
+
const why = err instanceof Error ? err.message : String(err);
|
|
44
|
+
throw new MemoryLayerRefusal(whose === "request"
|
|
45
|
+
? `the memory layer ${JSON.stringify(layer)} cannot be read: ${why}`
|
|
46
|
+
: `this deployment's configured memory layer ${JSON.stringify(layer)} cannot be read (${why}), so the store a request that names no layer belongs to cannot be told — name the layer in the request (${seat})`, err);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export function memoryStoreForRequest(stores, layers, seat = "`scope`") {
|
|
50
|
+
if (layers.length > 0) {
|
|
51
|
+
const groups = new Map();
|
|
52
|
+
for (const layer of layers) {
|
|
53
|
+
const store = storeOfNamed(stores, layer, "request", seat);
|
|
54
|
+
const group = groups.get(store);
|
|
55
|
+
if (group === undefined)
|
|
56
|
+
groups.set(store, [layer]);
|
|
57
|
+
else
|
|
58
|
+
group.push(layer);
|
|
59
|
+
}
|
|
60
|
+
if (groups.size === 1)
|
|
61
|
+
return [...groups.keys()][0];
|
|
62
|
+
const split = [...groups.values()].map((g) => `[${quoted(g)}]`).join(" / ");
|
|
63
|
+
throw new MemoryLayerRefusal(`the memory layers ${quoted(layers)} live in ${groups.size} different memory stores on this deployment and one request reaches one store — send one request per store: ${split}`);
|
|
64
|
+
}
|
|
65
|
+
const groups = deploymentStoreGroups(stores, seat);
|
|
66
|
+
if (groups.size === 1)
|
|
67
|
+
return stores.backend;
|
|
68
|
+
const elsewhere = [...groups.entries()].filter(([store]) => store !== stores.backend).flatMap(([, g]) => g);
|
|
69
|
+
throw new MemoryLayerRefusal(`this deployment keeps memory layers in ${groups.size} stores — the project layers${groups.get(stores.backend).length > 0 ? ` and ${quoted(groups.get(stores.backend))}` : ""} in the deployment's store, ${quoted(elsewhere)} in another — so a request that names no layer cannot be told which store it means; name the layer in the request (${seat})`);
|
|
70
|
+
}
|
|
71
|
+
export function memoryStoresOfDeployment(stores, seat = "`scope`") {
|
|
72
|
+
return [...deploymentStoreGroups(stores, seat).keys()];
|
|
73
|
+
}
|
|
74
|
+
function deploymentStoreGroups(stores, seat) {
|
|
75
|
+
const groups = new Map([[stores.backend, []]]);
|
|
76
|
+
for (const layer of stores.namedLayers()) {
|
|
77
|
+
const store = storeOfNamed(stores, layer, "deployment", seat);
|
|
78
|
+
const group = groups.get(store);
|
|
79
|
+
if (group === undefined)
|
|
80
|
+
groups.set(store, [layer]);
|
|
81
|
+
else
|
|
82
|
+
group.push(layer);
|
|
83
|
+
}
|
|
84
|
+
return groups;
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=memory-scope-stores.js.map
|
package/dist/memory-scope.d.ts
CHANGED
|
@@ -8,11 +8,35 @@ export declare const V2_SCOPE_PREFIXES: readonly string[];
|
|
|
8
8
|
* 一个 scope 键是否带 v2 typed 前缀。
|
|
9
9
|
*
|
|
10
10
|
* ⚠️ 判据刻意停在**前缀**层,不升级成 core 的 `parseScopeKey`:后者对「带可识别前缀但结构坏」的键
|
|
11
|
-
* 是 fail-loud
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* 是 fail-loud 抛错,而本谓词的两个读者都只是在决定「要不要挂 v2 标({@link memoryScopeContractOf})/
|
|
12
|
+
* 同步层要不要另给(`config.ts`)」——把抛点挪到这里会把 core 的校验时机整体前移(行为面改动,不在本条案的射程内)。
|
|
13
|
+
* 坏键仍由 core 在它自己的校验点 fail-loud。
|
|
14
14
|
*/
|
|
15
15
|
export declare function isV2ScopeKey(key: string): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* 本服务给一组记忆层盖的**作用域契约**(core `TaskSpec.memory.scopeContract`)—— 一个判据、两个读者:
|
|
18
|
+
* · {@link memorySpecForRequest} 铸 spec 时盖在会话上的那一个(读列表 = 全局层 + 登记簿种子);
|
|
19
|
+
* · 同步执行器替一层问「这一层在哪只店」时报给引擎的那一个(`resolveMemoryScopeStore` 的 `scopeContract`,`memory-sync-client.ts`)。
|
|
20
|
+
* 契约决定**类型化键落哪个平面**(v2 下 `user:` / `userproj:` / `org:` 在个人平面,不透明键在项目平面;不声明则一律项目平面),
|
|
21
|
+
* 所以两个读者必须读同一只:会话把一层放在哪只店,同步就同步哪只店。
|
|
22
|
+
*
|
|
23
|
+
* 判据:列表里有类型化键(前缀表 {@link V2_SCOPE_PREFIXES})⇒ `"v2"`;没有 ⇒ `undefined`(不声明;显式 opt-in,不嗅探调用方数据 ——
|
|
24
|
+
* 这些键只来自本服务自己的铸点与运维的登记簿,见 {@link memorySpecForRequest} 的 142-S1.5 注)。
|
|
25
|
+
* 按层问与按会话问答同一只店:平面按**每把键自己的种类**分,契约只挪类型化键,而本判据下带类型化键的会话恒盖 `"v2"`。
|
|
26
|
+
* 🚧 前提是键为规范形:引擎读键前去掉首尾空白,本判据看原串 —— 带首尾空白的类型化键(`" user:alice"`,配置边界今天放行)本判据不认、引擎在 v2 下读得出,
|
|
27
|
+
* 于是同一把键在带类型化种子的会话里落个人平面、在不带种子的会话与同步执行器里落项目平面。这是会话面先有的不一致(不同项目的会话对同一层就答两只店,
|
|
28
|
+
* 同步执行器无从同时对上两者),收口点在配置边界(拒或规范化这一形,后者会挪存量记忆的平面),不在本谓词;未闭环。
|
|
29
|
+
*/
|
|
30
|
+
export declare function memoryScopeContractOf(scopes: readonly string[]): "v2" | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* 本部署**自己点名**的记忆层 —— 配置里写着的每一层(去重、去空串、保序):单用户的全局层(`MEMORY_SCOPE`,{@link memoryScopeFor} 单用户臂的答案)、
|
|
33
|
+
* 同步层(`MEMORY_SYNC_SCOPE`,缺省即全局层)、项目登记簿的种子层(`config.projects[*].defaultScopes`,{@link memorySpecForRequest} 盖进会话的读列表)、
|
|
34
|
+
* 整理层(`MEMORY_CONSOLIDATION_SCOPES`)。项目层(`userrepo:…`)按任务根铸、不在配置里,也就不在这里 —— 它们住部署的店。
|
|
35
|
+
*
|
|
36
|
+
* 唯一读者:记忆作用域店的「不点名层的动词答在哪只店」(`memory-scope-stores.ts` 的 {@link memoryStoreForRequest} 无层那一臂)。
|
|
37
|
+
* 登记簿随中心配置热换(`applyEffective` 在同一只 `config.projects` 对象上原地整表换内容),所以读者每次现调本函数,不在起服时冻一份。
|
|
38
|
+
*/
|
|
39
|
+
export declare function memoryLayersNamedBy(config: Pick<ServiceConfig, "requirePrincipal" | "memoryScope" | "memorySync" | "projects" | "memoryConsolidationDriver">): string[];
|
|
16
40
|
/** Resolve the memory scope for a request —— {@link memoryScopeFor} 的三形(按实现逐臂写;[ref] 合并前主会话点名:
|
|
17
41
|
* 旧版这里写「多租户恒 `undefined` ⇒ 记忆暗」,与下面 DB 后端那一臂正好相反,test [ref] 就是被这句带偏的)。
|
|
18
42
|
*
|
|
@@ -93,8 +117,8 @@ export declare function replicatesProjectLayersOf(coverage: MemoryReplicationCov
|
|
|
93
117
|
* Pure posture probe (memoryEngineBackendFor consumes it; main.ts logs it): `undefined` = no plane split
|
|
94
118
|
* (the lane's file plane IS this worker's fs — `in-process` / `host`), `dark` = split + fail-closed, `forced` =
|
|
95
119
|
* split + operator override.
|
|
96
|
-
*
|
|
97
|
-
* `lane === undefined || lane === "host"
|
|
120
|
+
* The plane question reads the execution environment block (`hostLocalPaths`, sole author `capabilitiesFor`) — it used to be the
|
|
121
|
+
* hand-written synonym `lane === undefined || lane === "host"`; readings are unchanged lane by lane.
|
|
98
122
|
*/
|
|
99
123
|
export declare function memoryEngineRemoteLanePosture(config: ServiceConfig): {
|
|
100
124
|
lane: ExecutionLane;
|
package/dist/memory-scope.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import { FileMemoryEngineBackend, resolveMemoryEngineRoot, deriveControlPlaneDir, formatUserScope, formatProjScope, normalizeMemorySpec } from "@sema-agent/core";
|
|
3
|
-
import {
|
|
3
|
+
import { capabilitiesFor, executionLaneOf } from "./execution-lane-caps.js";
|
|
4
4
|
import { assertMemoryScopeWidth } from "./plugins/memory-key-guards.js";
|
|
5
5
|
import { projectLayerForTaskRoot } from "./project-identity.js";
|
|
6
6
|
import { parseProjectLayerKey, projectLayerOwnerOf } from "./project-layer-key.js";
|
|
@@ -14,6 +14,18 @@ export const V2_SCOPE_PREFIXES = Object.freeze(Object.values(V2_SCOPE_PREFIX_BY_
|
|
|
14
14
|
export function isV2ScopeKey(key) {
|
|
15
15
|
return V2_SCOPE_PREFIXES.some((prefix) => key.startsWith(prefix));
|
|
16
16
|
}
|
|
17
|
+
export function memoryScopeContractOf(scopes) {
|
|
18
|
+
return scopes.some(isV2ScopeKey) ? "v2" : undefined;
|
|
19
|
+
}
|
|
20
|
+
export function memoryLayersNamedBy(config) {
|
|
21
|
+
const named = [
|
|
22
|
+
...(config.requirePrincipal === true ? [] : [config.memoryScope]),
|
|
23
|
+
...(config.memorySync !== undefined ? [config.memorySync.scope] : []),
|
|
24
|
+
...Object.values(config.projects ?? {}).flatMap((p) => p.defaultScopes ?? []),
|
|
25
|
+
...config.memoryConsolidationDriver.scopes,
|
|
26
|
+
];
|
|
27
|
+
return named.filter((s, i) => typeof s === "string" && s.length > 0 && named.indexOf(s) === i);
|
|
28
|
+
}
|
|
17
29
|
export { MEMORY_SCOPE_COLUMN_CHARS, assertMemoryScopeWidth } from "./plugins/memory-key-guards.js";
|
|
18
30
|
export function memoryScopeFor(config, principal, projectId) {
|
|
19
31
|
if (config.requirePrincipal === true) {
|
|
@@ -64,7 +76,7 @@ export function replicatesProjectLayersOf(coverage, owner) {
|
|
|
64
76
|
}
|
|
65
77
|
export function memoryEngineRemoteLanePosture(config) {
|
|
66
78
|
const lane = executionLaneOf(config.remoteExec?.provider);
|
|
67
|
-
if (
|
|
79
|
+
if (capabilitiesFor(config).hostLocalPaths)
|
|
68
80
|
return undefined;
|
|
69
81
|
return { lane, posture: config.memoryEngineRemoteLaneAllowed === true ? "forced" : "dark" };
|
|
70
82
|
}
|
|
@@ -158,14 +170,14 @@ export function memorySpecForRequest(scope, projectKey, memoryWrite, defaultScop
|
|
|
158
170
|
throw new Error(`memory project scope ${JSON.stringify(projectScope)} equals the global memory scope (MEMORY_SCOPE) — the project layer and the global layer would be one directory. ` +
|
|
159
171
|
"Set MEMORY_SCOPE to a name outside the server-minted project-key families (see src/project-layer-key.ts).");
|
|
160
172
|
}
|
|
161
|
-
const isV2 = isV2ScopeKey;
|
|
162
173
|
const extras = (defaultScopes ?? []).filter((s, i, arr) => typeof s === "string" && s.length > 0 && s !== scope && s !== projectScope && arr.indexOf(s) === i);
|
|
163
174
|
const layered = projectKey !== undefined
|
|
164
175
|
? { scopes: projectScope !== undefined ? [projectScope, scope, ...extras] : [scope, ...extras], writeScope: projectScope ?? scope, projectKey }
|
|
165
176
|
: extras.length > 0
|
|
166
177
|
? { scopes: [scope, ...extras], writeScope: scope }
|
|
167
178
|
: { scopes: [scope] };
|
|
168
|
-
const
|
|
179
|
+
const contract = memoryScopeContractOf([scope, ...extras]);
|
|
180
|
+
const v2 = contract !== undefined ? { scopeContract: contract } : {};
|
|
169
181
|
const stamped = {};
|
|
170
182
|
if (originPolicy !== undefined) {
|
|
171
183
|
if (scope.startsWith("org:"))
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type MemorySyncClientResult, type MemorySyncTransport } from "@sema-agent/core";
|
|
2
|
+
import type { ServiceConfig } from "./config-types.js";
|
|
3
|
+
import type { MemoryScopeStores } from "./memory-scope-stores.js";
|
|
2
4
|
import { type OneTimeMigrationWindow } from "./plugins/one-time-migrations.js";
|
|
3
5
|
/** 结构化日志面(src/observability/logger.ts Logger 的结构子集——测试可注入捕获器)。 */
|
|
4
6
|
export interface MemorySyncLog {
|
|
@@ -41,9 +43,9 @@ export declare function createMemorySyncTransport(opts: {
|
|
|
41
43
|
/** The one call shape this transport makes (`fetch` itself fits it). */
|
|
42
44
|
fetchImpl?: (url: string, init: RequestInit) => Promise<Response>;
|
|
43
45
|
}): MemorySyncTransport;
|
|
44
|
-
/**
|
|
46
|
+
/** 一层的游标文件路径:引擎根 `memoryEngineDir`(= `RunnerDeps.memoryEngineDir` 的那一个值)下 `sync-cursors/<scope 摘要>.json`。一层一只,不随对端变、不随店变。
|
|
45
47
|
* 落在子目录里:与更早的根下一层文件名 `sync-cursor-<peer>.json` 的空间不相交。 */
|
|
46
|
-
export declare function memorySyncCursorPath(
|
|
48
|
+
export declare function memorySyncCursorPath(memoryEngineDir: string, scope: string): string;
|
|
47
49
|
/** M31 一次的读数(测试按它断言)。`window: "closed"` ⇒ 一行都没读。 */
|
|
48
50
|
export interface SyncCursorRelocationM31Report {
|
|
49
51
|
readonly window: "open" | "closed";
|
|
@@ -59,7 +61,7 @@ export interface SyncCursorRelocationM31Report {
|
|
|
59
61
|
* 与同一数据根上并跑的另一个进程(server + run-local)同搬:源已被对方搬走(`ENOENT`)⇒ 跳过。残余(如实):先查目标再 `rename` 之间若有写者在目标路径上
|
|
60
62
|
* 落下游标,会被盖掉 —— 写者只有本模块的 runner,而 runner 写任何游标之前先跑完本步。别的读写错 ⇒ 抛(这一轮整轮失败、下一轮再搬)。
|
|
61
63
|
*/
|
|
62
|
-
export declare function relocateSyncCursorsM31(
|
|
64
|
+
export declare function relocateSyncCursorsM31(memoryEngineDir: string, log: MemorySyncLog, opts: {
|
|
63
65
|
readonly today: string;
|
|
64
66
|
readonly window?: Pick<OneTimeMigrationWindow, "removableAfter">;
|
|
65
67
|
}): Promise<SyncCursorRelocationM31Report>;
|
|
@@ -91,13 +93,13 @@ export interface MemorySyncRunner {
|
|
|
91
93
|
export declare const MAX_SYNC_ROUNDS = 32;
|
|
92
94
|
/**
|
|
93
95
|
* 组装同步执行器:同步层 `scope` + 同属主的项目层,逐层一轮(cursor 持久化 + core `syncMemoryScope` + conflicts 上报),串行化/节流。
|
|
94
|
-
*
|
|
96
|
+
* 每一层同步的店由记忆作用域店答(⑥);同步层建时就问(坏的层键 ⇒ 这里抛引擎的 `config.memory_scope_key`,原样)。
|
|
95
97
|
*/
|
|
96
98
|
export declare function createMemorySyncRunner(opts: {
|
|
97
|
-
backend
|
|
99
|
+
/** 记忆作用域店(本进程那一只):每一层的店问它的 `storeOf`;项目层的发现列它的 `backend`(部署的店本身);游标的根 = 它的 `root`。 */
|
|
100
|
+
stores: MemoryScopeStores;
|
|
98
101
|
/** 同步层(`config.memorySync.scope`);它同属主的项目层随它一起同步。 */
|
|
99
102
|
scope: string;
|
|
100
|
-
memoryRoot: string;
|
|
101
103
|
/** 对端 = 它的 `peer`(这一轮的对端只有这一个来源;游标绑的对端由引擎对它判)。 */
|
|
102
104
|
transport: MemorySyncTransport;
|
|
103
105
|
log: MemorySyncLog;
|
|
@@ -106,4 +108,15 @@ export declare function createMemorySyncRunner(opts: {
|
|
|
106
108
|
maxPushEntries?: number;
|
|
107
109
|
maxPullEntries?: number;
|
|
108
110
|
}): MemorySyncRunner;
|
|
111
|
+
/**
|
|
112
|
+
* 两个入口(HTTP 服务 `boot/stores.ts` 与 `run-local.ts`)起同步执行器的**唯一一处**:建 transport(对端身份在这里铸一次)、建执行器、
|
|
113
|
+
* 起服日志一行 `memory_sync_enabled`、fire-and-forget 起服那一轮。两处此前各手抄一份同样的装配(上限两键漏过一次);
|
|
114
|
+
* 店只经入口装配出的那只记忆作用域店取 —— 它的两件事实就是交给 Runner 的那两只,会话把一层放在哪只店由同样的两件事实决定。
|
|
115
|
+
*/
|
|
116
|
+
export declare function startMemorySyncRunner(deps: {
|
|
117
|
+
sync: NonNullable<ServiceConfig["memorySync"]>;
|
|
118
|
+
/** 入口装配出的记忆作用域店(= 本入口的记忆引擎对象)。 */
|
|
119
|
+
stores: MemoryScopeStores;
|
|
120
|
+
log: MemorySyncLog;
|
|
121
|
+
}): MemorySyncRunner;
|
|
109
122
|
//# sourceMappingURL=memory-sync-client.d.ts.map
|
|
@@ -50,8 +50,8 @@ const SYNC_CURSOR_DIR = "sync-cursors";
|
|
|
50
50
|
function scopeDigest(scope) {
|
|
51
51
|
return createHash("sha256").update(scope, "utf8").digest("hex").slice(0, 32);
|
|
52
52
|
}
|
|
53
|
-
export function memorySyncCursorPath(
|
|
54
|
-
return join(
|
|
53
|
+
export function memorySyncCursorPath(memoryEngineDir, scope) {
|
|
54
|
+
return join(memoryEngineDir, SYNC_CURSOR_DIR, `${scopeDigest(scope)}.json`);
|
|
55
55
|
}
|
|
56
56
|
function unusableCursor(scope, path, why) {
|
|
57
57
|
return new Error(`memory sync: layer ${JSON.stringify(scope)} not synced — the cursor file ${path} cannot be read as a sync cursor (${why}); the layer stays unsynced until the operator picks one of two ways out: ` +
|
|
@@ -120,7 +120,7 @@ function scopeOfLegacyCursor(raw) {
|
|
|
120
120
|
const scope = parsed["scope"];
|
|
121
121
|
return typeof scope === "string" && scope.length > 0 ? scope : undefined;
|
|
122
122
|
}
|
|
123
|
-
export async function relocateSyncCursorsM31(
|
|
123
|
+
export async function relocateSyncCursorsM31(memoryEngineDir, log, opts) {
|
|
124
124
|
if (!migrationWindowOpen(opts.window ?? m31Window(), opts.today))
|
|
125
125
|
return { window: "closed", moved: 0, left: 0 };
|
|
126
126
|
let moved = 0;
|
|
@@ -155,7 +155,7 @@ export async function relocateSyncCursorsM31(memoryRoot, log, opts) {
|
|
|
155
155
|
moved += 1;
|
|
156
156
|
log.info("memory_sync_cursor_relocated", { from, to });
|
|
157
157
|
};
|
|
158
|
-
const legacyDir = join(
|
|
158
|
+
const legacyDir = join(memoryEngineDir, SYNC_CURSOR_DIR, LEGACY_PEER_SEGMENT);
|
|
159
159
|
let names = [];
|
|
160
160
|
try {
|
|
161
161
|
names = await readdir(legacyDir);
|
|
@@ -166,7 +166,7 @@ export async function relocateSyncCursorsM31(memoryRoot, log, opts) {
|
|
|
166
166
|
}
|
|
167
167
|
for (const name of [...names].sort()) {
|
|
168
168
|
if (/^[0-9a-f]{32}\.json$/.test(name))
|
|
169
|
-
await move(join(legacyDir, name), join(
|
|
169
|
+
await move(join(legacyDir, name), join(memoryEngineDir, SYNC_CURSOR_DIR, name));
|
|
170
170
|
}
|
|
171
171
|
if (names.length > 0) {
|
|
172
172
|
try {
|
|
@@ -177,7 +177,7 @@ export async function relocateSyncCursorsM31(memoryRoot, log, opts) {
|
|
|
177
177
|
throw err;
|
|
178
178
|
}
|
|
179
179
|
}
|
|
180
|
-
const legacyFile = join(
|
|
180
|
+
const legacyFile = join(memoryEngineDir, LEGACY_ROOT_CURSOR_FILE);
|
|
181
181
|
let raw;
|
|
182
182
|
try {
|
|
183
183
|
raw = await readFile(legacyFile, "utf8");
|
|
@@ -193,7 +193,7 @@ export async function relocateSyncCursorsM31(memoryRoot, log, opts) {
|
|
|
193
193
|
log.warn("memory_sync_cursor_not_relocated", { path: legacyFile, note: "a cursor file written by an earlier version that is not a JSON object naming its scope, so no layer can take it as its baseline; restore it from a backup and restart, or delete it" });
|
|
194
194
|
}
|
|
195
195
|
else {
|
|
196
|
-
await move(legacyFile, memorySyncCursorPath(
|
|
196
|
+
await move(legacyFile, memorySyncCursorPath(memoryEngineDir, scope));
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
199
|
return { window: "open", moved, left };
|
|
@@ -209,8 +209,11 @@ export function createMemorySyncRunner(opts) {
|
|
|
209
209
|
const tracked = new Set();
|
|
210
210
|
let enumerationWarned = false;
|
|
211
211
|
const isCoveredProjectLayer = (scope) => scope !== opts.scope && parseProjectLayerKey(scope) !== undefined && replicationCovers(coverage, scope);
|
|
212
|
+
const storeOf = (scope) => opts.stores.storeOf(scope);
|
|
213
|
+
const memoryEngineDir = opts.stores.root;
|
|
214
|
+
storeOf(opts.scope);
|
|
212
215
|
const layersOfRound = () => {
|
|
213
|
-
const local = enumerateScopes(opts.backend);
|
|
216
|
+
const local = enumerateScopes(opts.stores.backend);
|
|
214
217
|
if (!local.supported && !enumerationWarned) {
|
|
215
218
|
enumerationWarned = true;
|
|
216
219
|
opts.log.warn("memory_sync_layer_enumeration_unsupported", { scope: opts.scope, reason: local.reason, note: "only the sync layer and tracked project layers are synced" });
|
|
@@ -219,7 +222,7 @@ export function createMemorySyncRunner(opts) {
|
|
|
219
222
|
return [opts.scope, ...[...projectLayers].sort()];
|
|
220
223
|
};
|
|
221
224
|
const syncLayer = async (scope, reason) => {
|
|
222
|
-
const cursorPath = memorySyncCursorPath(
|
|
225
|
+
const cursorPath = memorySyncCursorPath(memoryEngineDir, scope);
|
|
223
226
|
const baseline = await loadLayerBaseline(cursorPath, scope);
|
|
224
227
|
let cursor = baseline.kind === "cursor" ? baseline.cursor : undefined;
|
|
225
228
|
let result;
|
|
@@ -227,7 +230,7 @@ export function createMemorySyncRunner(opts) {
|
|
|
227
230
|
for (;;) {
|
|
228
231
|
subRound += 1;
|
|
229
232
|
result = await syncMemoryScope({
|
|
230
|
-
backend:
|
|
233
|
+
backend: storeOf(scope),
|
|
231
234
|
scope,
|
|
232
235
|
transport: opts.transport,
|
|
233
236
|
...(cursor !== undefined ? { cursor } : {}),
|
|
@@ -288,7 +291,7 @@ export function createMemorySyncRunner(opts) {
|
|
|
288
291
|
let relocated = false;
|
|
289
292
|
const round = async (reason) => {
|
|
290
293
|
if (!relocated) {
|
|
291
|
-
await relocateSyncCursorsM31(
|
|
294
|
+
await relocateSyncCursorsM31(memoryEngineDir, opts.log, { today: new Date(now()).toISOString().slice(0, 10) });
|
|
292
295
|
relocated = true;
|
|
293
296
|
}
|
|
294
297
|
const layers = [];
|
|
@@ -299,7 +302,7 @@ export function createMemorySyncRunner(opts) {
|
|
|
299
302
|
}
|
|
300
303
|
catch (err) {
|
|
301
304
|
failures.push({ scope, err });
|
|
302
|
-
opts.log.warn("memory_sync_layer_failed", { reason, scope, cursorPath: memorySyncCursorPath(
|
|
305
|
+
opts.log.warn("memory_sync_layer_failed", { reason, scope, cursorPath: memorySyncCursorPath(memoryEngineDir, scope), err: String(err) });
|
|
303
306
|
}
|
|
304
307
|
}
|
|
305
308
|
if (failures.length > 0) {
|
|
@@ -339,6 +342,21 @@ export function createMemorySyncRunner(opts) {
|
|
|
339
342
|
tracked.add(scope);
|
|
340
343
|
queueRound("track");
|
|
341
344
|
};
|
|
342
|
-
return { syncOnce, trigger, track, exclusive, cursorPathFor: (scope) => memorySyncCursorPath(
|
|
345
|
+
return { syncOnce, trigger, track, exclusive, cursorPathFor: (scope) => memorySyncCursorPath(memoryEngineDir, scope), pending: () => pending };
|
|
346
|
+
}
|
|
347
|
+
export function startMemorySyncRunner(deps) {
|
|
348
|
+
const { sync } = deps;
|
|
349
|
+
const transport = createMemorySyncTransport({ url: sync.url, token: sync.token });
|
|
350
|
+
const runner = createMemorySyncRunner({
|
|
351
|
+
stores: deps.stores,
|
|
352
|
+
scope: sync.scope,
|
|
353
|
+
transport,
|
|
354
|
+
log: deps.log,
|
|
355
|
+
...(sync.maxPushEntries !== undefined ? { maxPushEntries: sync.maxPushEntries } : {}),
|
|
356
|
+
...(sync.maxPullEntries !== undefined ? { maxPullEntries: sync.maxPullEntries } : {}),
|
|
357
|
+
});
|
|
358
|
+
deps.log.info("memory_sync_enabled", buildMemorySyncEnabledFields(transport.peer, sync.scope, runner.cursorPathFor(sync.scope)));
|
|
359
|
+
runner.trigger("boot");
|
|
360
|
+
return runner;
|
|
343
361
|
}
|
|
344
362
|
//# sourceMappingURL=memory-sync-client.js.map
|
|
@@ -86,8 +86,8 @@ export class SqlAdoptionLogStore {
|
|
|
86
86
|
constructor(db) {
|
|
87
87
|
this.db = db;
|
|
88
88
|
}
|
|
89
|
-
q(
|
|
90
|
-
return this.db.dialect === "
|
|
89
|
+
q(mysql, pg) {
|
|
90
|
+
return this.db.dialect === "mysql" ? mysql : pg;
|
|
91
91
|
}
|
|
92
92
|
get reader() {
|
|
93
93
|
return this.db;
|
|
@@ -191,7 +191,7 @@ export class SqlAdoptionLogStore {
|
|
|
191
191
|
try {
|
|
192
192
|
lockName = qualifyAdoptionLockName(await this.resolveLockKeyspace(conn), name);
|
|
193
193
|
for (let i = 0; i < attempts && !held; i += 1) {
|
|
194
|
-
const res = await conn.query(this.q("SELECT GET_LOCK(?, 0) AS got", "SELECT pg_try_advisory_lock($1, $2) AS got"), this.db.dialect === "
|
|
194
|
+
const res = await conn.query(this.q("SELECT GET_LOCK(?, 0) AS got", "SELECT pg_try_advisory_lock($1, $2) AS got"), this.db.dialect === "mysql" ? [lockName] : [PG_ADOPTION_LOCK_CLASS, pgLockObjectKey(lockName)]);
|
|
195
195
|
const got = res.rows[0]?.got;
|
|
196
196
|
held = got === 1 || got === true || got === "1";
|
|
197
197
|
if (!held)
|
|
@@ -204,7 +204,7 @@ export class SqlAdoptionLogStore {
|
|
|
204
204
|
finally {
|
|
205
205
|
if (held) {
|
|
206
206
|
try {
|
|
207
|
-
await conn.query(this.q("SELECT RELEASE_LOCK(?) AS released", "SELECT pg_advisory_unlock($1, $2) AS released"), this.db.dialect === "
|
|
207
|
+
await conn.query(this.q("SELECT RELEASE_LOCK(?) AS released", "SELECT pg_advisory_unlock($1, $2) AS released"), this.db.dialect === "mysql" ? [lockName] : [PG_ADOPTION_LOCK_CLASS, pgLockObjectKey(lockName)]);
|
|
208
208
|
}
|
|
209
209
|
catch (releaseErr) {
|
|
210
210
|
console.warn("[adoption-log] releasing the adoption arc lock failed (it auto-frees when the connection closes):", releaseErr);
|
|
@@ -386,7 +386,7 @@ function patchColumns(patch, json) {
|
|
|
386
386
|
return out;
|
|
387
387
|
}
|
|
388
388
|
function ph(dialect, n) {
|
|
389
|
-
return dialect === "
|
|
389
|
+
return dialect === "mysql" ? "?" : `$${n}`;
|
|
390
390
|
}
|
|
391
391
|
function pendingClaimWhere(dialect, firstParam) {
|
|
392
392
|
return `ask_id = ${ph(dialect, firstParam)} AND batch_id = ${ph(dialect, firstParam + 1)} AND state = 'STREAM_PENDING'`;
|
|
@@ -394,15 +394,15 @@ function pendingClaimWhere(dialect, firstParam) {
|
|
|
394
394
|
function decideOwnerWhere(dialect, owner, param) {
|
|
395
395
|
if (owner === null)
|
|
396
396
|
return { sql: "owner IS NULL", params: [] };
|
|
397
|
-
return dialect === "
|
|
397
|
+
return dialect === "mysql" ? { sql: "CAST(owner AS BINARY) = CAST(? AS BINARY)", params: [owner] } : { sql: `owner = ${ph(dialect, param)}`, params: [owner] };
|
|
398
398
|
}
|
|
399
399
|
export class SqlApprovalAskStore {
|
|
400
400
|
db;
|
|
401
401
|
constructor(db) {
|
|
402
402
|
this.db = db;
|
|
403
403
|
}
|
|
404
|
-
q(
|
|
405
|
-
return this.db.dialect === "
|
|
404
|
+
q(mysql, pg) {
|
|
405
|
+
return this.db.dialect === "mysql" ? mysql : pg;
|
|
406
406
|
}
|
|
407
407
|
json(value) {
|
|
408
408
|
return dialectJsonEncoder(this.db.dialect)(value);
|
|
@@ -411,7 +411,7 @@ export class SqlApprovalAskStore {
|
|
|
411
411
|
return isDupKeyError(this.db.dialect, e);
|
|
412
412
|
}
|
|
413
413
|
async getAskOn(conn, askId) {
|
|
414
|
-
const { rows } = await conn.query(this.q(`SELECT ${askCols("
|
|
414
|
+
const { rows } = await conn.query(this.q(`SELECT ${askCols("mysql")} FROM ${APPROVAL_ASK_TABLE} WHERE ask_id = ?`, `SELECT ${askCols("pg")} FROM ${APPROVAL_ASK_TABLE} WHERE ask_id = $1`), [askId]);
|
|
415
415
|
return rows[0] ? mapAskRow(rows[0]) : null;
|
|
416
416
|
}
|
|
417
417
|
async getBatchOn(conn, batchId) {
|
|
@@ -704,40 +704,40 @@ export class SqlApprovalAskStore {
|
|
|
704
704
|
}
|
|
705
705
|
}
|
|
706
706
|
async listByState(state, limit) {
|
|
707
|
-
const { rows } = await this.db.query(this.q(`SELECT ${askCols("
|
|
707
|
+
const { rows } = await this.db.query(this.q(`SELECT ${askCols("mysql")} FROM ${APPROVAL_ASK_TABLE} WHERE state = ? ORDER BY updated_at_ms ASC, created_at_ms ASC LIMIT ?`, `SELECT ${askCols("pg")} FROM ${APPROVAL_ASK_TABLE} WHERE state = $1 ORDER BY updated_at_ms ASC, created_at_ms ASC LIMIT $2`), [state, limit]);
|
|
708
708
|
return rows.map(mapAskRow);
|
|
709
709
|
}
|
|
710
710
|
async listPendingByTask(taskId, signal) {
|
|
711
711
|
if (signal?.aborted === true)
|
|
712
712
|
throw abortedReadError("listPendingByTask");
|
|
713
|
-
const { rows } = await withReadSignal(this.db.query(this.q(`SELECT ${askCols("
|
|
713
|
+
const { rows } = await withReadSignal(this.db.query(this.q(`SELECT ${askCols("mysql")} FROM ${APPROVAL_ASK_TABLE} WHERE task_id = ? AND state = 'STREAM_PENDING' ORDER BY created_at_ms ASC`, `SELECT ${askCols("pg")} FROM ${APPROVAL_ASK_TABLE} WHERE task_id = $1 AND state = 'STREAM_PENDING' ORDER BY created_at_ms ASC`), [taskId]), signal, "listPendingByTask");
|
|
714
714
|
return rows.map(mapAskRow);
|
|
715
715
|
}
|
|
716
716
|
async listPendingBySession(sessionId, owner, signal) {
|
|
717
717
|
if (signal?.aborted === true)
|
|
718
718
|
throw abortedReadError("listPendingBySession");
|
|
719
719
|
const { rows } = await withReadSignal(this.db.query(owner === null
|
|
720
|
-
? this.q(`SELECT ${askCols("
|
|
721
|
-
: this.q(`SELECT ${askCols("
|
|
720
|
+
? this.q(`SELECT ${askCols("mysql")} FROM ${APPROVAL_ASK_TABLE} WHERE session_id = ? AND state = 'STREAM_PENDING' AND owner IS NULL ORDER BY created_at_ms ASC`, `SELECT ${askCols("pg")} FROM ${APPROVAL_ASK_TABLE} WHERE session_id = $1 AND state = 'STREAM_PENDING' AND owner IS NULL ORDER BY created_at_ms ASC`)
|
|
721
|
+
: this.q(`SELECT ${askCols("mysql")} FROM ${APPROVAL_ASK_TABLE} WHERE session_id = ? AND state = 'STREAM_PENDING' AND owner = ? ORDER BY created_at_ms ASC`, `SELECT ${askCols("pg")} FROM ${APPROVAL_ASK_TABLE} WHERE session_id = $1 AND state = 'STREAM_PENDING' AND owner = $2 ORDER BY created_at_ms ASC`), owner === null ? [sessionId] : [sessionId, owner]), signal, "listPendingBySession");
|
|
722
722
|
return rows.map(mapAskRow);
|
|
723
723
|
}
|
|
724
724
|
async listByCall(call) {
|
|
725
|
-
const { rows } = await this.db.query(this.q(`SELECT ${askCols("
|
|
725
|
+
const { rows } = await this.db.query(this.q(`SELECT ${askCols("mysql")} FROM ${APPROVAL_ASK_TABLE} WHERE task_id = ? AND leg_key = ? AND tool_call_id = ? AND source_task_id = ? ORDER BY created_at_ms ASC, ask_id ASC`, `SELECT ${askCols("pg")} FROM ${APPROVAL_ASK_TABLE} WHERE task_id = $1 AND leg_key = $2 AND tool_call_id = $3 AND source_task_id = $4 ORDER BY created_at_ms ASC, ask_id ASC`), [call.taskId, call.legKey, call.toolCallId, call.sourceTaskId]);
|
|
726
726
|
return rows.map(mapAskRow).filter((r) => askRowOnCall(r, call));
|
|
727
727
|
}
|
|
728
728
|
async listParkingBySession(sessionId) {
|
|
729
|
-
const { rows } = await this.db.query(this.q(`SELECT ${askCols("
|
|
729
|
+
const { rows } = await this.db.query(this.q(`SELECT ${askCols("mysql")} FROM ${APPROVAL_ASK_TABLE} WHERE session_id = ? AND state = 'PARKING' ORDER BY created_at_ms ASC, ask_id ASC`, `SELECT ${askCols("pg")} FROM ${APPROVAL_ASK_TABLE} WHERE session_id = $1 AND state = 'PARKING' ORDER BY created_at_ms ASC, ask_id ASC`), [sessionId]);
|
|
730
730
|
return rows.map(mapAskRow).filter((r) => r.sessionId === sessionId);
|
|
731
731
|
}
|
|
732
732
|
async getAsk(askId) {
|
|
733
|
-
const { rows } = await this.db.query(this.q(`SELECT ${askCols("
|
|
733
|
+
const { rows } = await this.db.query(this.q(`SELECT ${askCols("mysql")} FROM ${APPROVAL_ASK_TABLE} WHERE ask_id = ?`, `SELECT ${askCols("pg")} FROM ${APPROVAL_ASK_TABLE} WHERE ask_id = $1`), [
|
|
734
734
|
askId,
|
|
735
735
|
]);
|
|
736
736
|
return rows[0] ? mapAskRow(rows[0]) : null;
|
|
737
737
|
}
|
|
738
738
|
async getByIdempotencyKey(taskId, key) {
|
|
739
739
|
assertIdempotencyKeyShape(key);
|
|
740
|
-
const { rows } = await this.db.query(this.q(`SELECT ${askCols("
|
|
740
|
+
const { rows } = await this.db.query(this.q(`SELECT ${askCols("mysql")} FROM ${APPROVAL_ASK_TABLE} WHERE task_id = ? AND idempotency_key = ?`, `SELECT ${askCols("pg")} FROM ${APPROVAL_ASK_TABLE} WHERE task_id = $1 AND idempotency_key = $2`), [taskId, key]);
|
|
741
741
|
return rows[0] ? mapAskRow(rows[0]) : null;
|
|
742
742
|
}
|
|
743
743
|
async deferReconcile(askId, expectedState, expectedRev, nowMs) {
|
|
@@ -772,7 +772,7 @@ export class SqlApprovalAskStore {
|
|
|
772
772
|
const ANCHOR_COLUMNS = ["task_id", "session_id", "source_task_id"];
|
|
773
773
|
let keepSql = "";
|
|
774
774
|
let keepParams = [];
|
|
775
|
-
if (keep.length > 0 && dialect === "
|
|
775
|
+
if (keep.length > 0 && dialect === "mysql") {
|
|
776
776
|
const list = keep.map(() => "?").join(", ");
|
|
777
777
|
keepSql = ANCHOR_COLUMNS.map((c) => ` AND ${c} NOT IN (${list})`).join("");
|
|
778
778
|
keepParams = ANCHOR_COLUMNS.flatMap(() => keep);
|
|
@@ -10,15 +10,15 @@ export class SqlApprovalNonceStore {
|
|
|
10
10
|
constructor(db) {
|
|
11
11
|
this.db = db;
|
|
12
12
|
}
|
|
13
|
-
q(
|
|
14
|
-
return this.db.dialect === "
|
|
13
|
+
q(mysql, pg) {
|
|
14
|
+
return this.db.dialect === "mysql" ? mysql : pg;
|
|
15
15
|
}
|
|
16
16
|
async nowMs() {
|
|
17
17
|
return sqlDbNowMs(this.db, APPROVAL_NONCE_TABLE);
|
|
18
18
|
}
|
|
19
19
|
async consume(sessionId, nonce, expiresAtMs) {
|
|
20
20
|
try {
|
|
21
|
-
await this.db.query(this.q(`INSERT INTO ${APPROVAL_NONCE_TABLE} (session_id, nonce, consumed_at_ms, expires_at_ms) VALUES (?,?,${SQL_DB_NOW_MS.
|
|
21
|
+
await this.db.query(this.q(`INSERT INTO ${APPROVAL_NONCE_TABLE} (session_id, nonce, consumed_at_ms, expires_at_ms) VALUES (?,?,${SQL_DB_NOW_MS.mysql},?)`, `INSERT INTO ${APPROVAL_NONCE_TABLE} (session_id, nonce, consumed_at_ms, expires_at_ms) VALUES ($1,$2,${SQL_DB_NOW_MS.pg},$3)`), [sessionId, nonce, expiresAtMs]);
|
|
22
22
|
return true;
|
|
23
23
|
}
|
|
24
24
|
catch (err) {
|
|
@@ -28,7 +28,7 @@ export class SqlApprovalNonceStore {
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
async reapExpired() {
|
|
31
|
-
const res = await this.db.query(this.q(`DELETE FROM ${APPROVAL_NONCE_TABLE} WHERE expires_at_ms <= ${SQL_DB_NOW_MS.
|
|
31
|
+
const res = await this.db.query(this.q(`DELETE FROM ${APPROVAL_NONCE_TABLE} WHERE expires_at_ms <= ${SQL_DB_NOW_MS.mysql}`, `DELETE FROM ${APPROVAL_NONCE_TABLE} WHERE expires_at_ms <= ${SQL_DB_NOW_MS.pg}`), []);
|
|
32
32
|
return res.affected;
|
|
33
33
|
}
|
|
34
34
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Pool as MysqlPool } from "mysql2/promise";
|
|
2
2
|
import type { Pool as PgPool } from "pg";
|
|
3
|
-
import { type SqlEngineFacts, type SqlEngineReading, type SqlWallSource, type SqlWallsReading } from "./sql-driver.js";
|
|
3
|
+
import { type SqlEngineFacts, type SqlEngineReading, type SqlWallSource, type SqlWallsReading, type SqlDialect } from "./sql-driver.js";
|
|
4
4
|
import { type Logger } from "../observability/logger.js";
|
|
5
5
|
/**
|
|
6
6
|
* 文本协议的 blob 膨胀律 —— mysql2 `query()` 把 Buffer 参数写成 `X'<hex>'`,一字节两个字符,所以承载一个 blob 的语句至少是它的 2 倍。
|
|
@@ -115,7 +115,7 @@ export declare class SqlBlobBackend implements BlobBackend {
|
|
|
115
115
|
private readonly db;
|
|
116
116
|
/** Where a stale connection's replacement is logged ({@link connectJudged}); the boot assembly passes the service logger. */
|
|
117
117
|
private readonly log;
|
|
118
|
-
constructor(dialect:
|
|
118
|
+
constructor(dialect: SqlDialect, pool: MysqlPool | PgPool, maxBytes?: number, table?: string, log?: Pick<Logger, "warn">);
|
|
119
119
|
/** The created_at-refreshing content-addressed upsert — VERBATIM the tidb/pg stores' `putBlobRow`. Refreshes
|
|
120
120
|
* created_at even on a dedup hit so a re-used/just-written blob is "touched=now" and GC-grace-protected.
|
|
121
121
|
* (a): an over-cap blob is REJECTED (typed {@link BlobTooLargeError}) BEFORE the driver ever sees the bytes —
|
|
@@ -140,7 +140,7 @@ export declare class SqlBlobBackend implements BlobBackend {
|
|
|
140
140
|
/** SHARED index op(workspace 浏览面 #3,[ref]②)—— per-blob 字节数,读 SQL INDEX 的 `byte_len`
|
|
141
141
|
* 列(sqlHasBlobs 同款单 IN 查询;MinIO 后端的 bytes-NULL 索引行同样带 byte_len,两后端同源)。
|
|
142
142
|
* tree/file 面的 size 投影与「超限先拒后拉」都靠它——绝不为拿大小去 getBlob 整块字节。 */
|
|
143
|
-
export declare function sqlBlobSizes(dialect:
|
|
143
|
+
export declare function sqlBlobSizes(dialect: SqlDialect, pool: MysqlPool | PgPool, hashes: string[]): Promise<Map<string, number>>;
|
|
144
144
|
export interface MinioBlobBackendConfig {
|
|
145
145
|
endpoint: string;
|
|
146
146
|
bucket: string;
|
|
@@ -198,7 +198,7 @@ export declare class MinioBlobBackend implements BlobBackend {
|
|
|
198
198
|
/** Blob-domain index table ([ref] 域隔离,SqlBlobBackend.table 同款;默认 = E19 纪元的 snapshot_blob)。 */
|
|
199
199
|
private readonly indexTable;
|
|
200
200
|
constructor(cfg: MinioBlobBackendConfig, index?: {
|
|
201
|
-
dialect:
|
|
201
|
+
dialect: SqlDialect;
|
|
202
202
|
pool: MysqlPool | PgPool;
|
|
203
203
|
table?: string;
|
|
204
204
|
});
|
|
@@ -76,7 +76,7 @@ export class SqlBlobBackend {
|
|
|
76
76
|
this.pool = pool;
|
|
77
77
|
this.explicitCapBytes = maxBytes;
|
|
78
78
|
this.table = table;
|
|
79
|
-
this.db = dialect === "
|
|
79
|
+
this.db = dialect === "mysql" ? mysqlDriver(pool) : pgDriver(pool);
|
|
80
80
|
this.log = log ?? createLogger();
|
|
81
81
|
}
|
|
82
82
|
async putBlob(hash, bytes) {
|
|
@@ -90,7 +90,7 @@ export class SqlBlobBackend {
|
|
|
90
90
|
if (judged.refusal !== undefined)
|
|
91
91
|
throw new BlobTooLargeError(bytes.byteLength, judged.refusal.cap);
|
|
92
92
|
try {
|
|
93
|
-
await judged.conn.query(this.dialect === "
|
|
93
|
+
await judged.conn.query(this.dialect === "mysql"
|
|
94
94
|
? `INSERT INTO ${this.table} (blob_hash, byte_len, bytes, created_at) VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE created_at = VALUES(created_at)`
|
|
95
95
|
: `INSERT INTO ${this.table} (blob_hash, byte_len, bytes, created_at) VALUES ($1,$2,$3,$4) ON CONFLICT (blob_hash) DO UPDATE SET created_at = EXCLUDED.created_at`, [hash, bytes.byteLength, Buffer.from(bytes), new Date()]);
|
|
96
96
|
}
|
|
@@ -99,7 +99,7 @@ export class SqlBlobBackend {
|
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
async getBlob(hash) {
|
|
102
|
-
if (this.dialect === "
|
|
102
|
+
if (this.dialect === "mysql") {
|
|
103
103
|
const [rows] = await this.pool.query(`SELECT bytes FROM ${this.table} WHERE blob_hash = ?`, [hash]);
|
|
104
104
|
return rows[0]?.bytes != null ? new Uint8Array(rows[0].bytes) : undefined;
|
|
105
105
|
}
|
|
@@ -118,7 +118,7 @@ async function sqlHasBlobs(dialect, pool, hashes, table = "snapshot_blob") {
|
|
|
118
118
|
if (hashes.length === 0)
|
|
119
119
|
return new Set();
|
|
120
120
|
const distinct = [...new Set(hashes)];
|
|
121
|
-
if (dialect === "
|
|
121
|
+
if (dialect === "mysql") {
|
|
122
122
|
const ph = distinct.map(() => "?").join(",");
|
|
123
123
|
const [rows] = await pool.query(`SELECT blob_hash FROM ${table} WHERE blob_hash IN (${ph})`, distinct);
|
|
124
124
|
return new Set(rows.map((r) => String(r.blob_hash)));
|
|
@@ -135,7 +135,7 @@ export async function sqlBlobSizes(dialect, pool, hashes) {
|
|
|
135
135
|
const out = new Map();
|
|
136
136
|
for (let i = 0; i < distinct.length; i += CHUNK) {
|
|
137
137
|
const part = distinct.slice(i, i + CHUNK);
|
|
138
|
-
if (dialect === "
|
|
138
|
+
if (dialect === "mysql") {
|
|
139
139
|
const ph = part.map(() => "?").join(",");
|
|
140
140
|
const [rows] = await pool.query(`SELECT blob_hash, byte_len FROM snapshot_blob WHERE blob_hash IN (${ph})`, part);
|
|
141
141
|
for (const r of rows)
|
|
@@ -155,7 +155,7 @@ async function sqlDeleteIndexRowsPastGrace(dialect, pool, hashes, table = "snaps
|
|
|
155
155
|
return [];
|
|
156
156
|
const distinct = [...new Set(hashes)];
|
|
157
157
|
const cutoff = new Date(Date.now() - BLOB_GC_GRACE_MS);
|
|
158
|
-
if (dialect === "
|
|
158
|
+
if (dialect === "mysql") {
|
|
159
159
|
const ph = distinct.map(() => "?").join(",");
|
|
160
160
|
const grace = await pool
|
|
161
161
|
.query(`SELECT blob_hash FROM ${table} WHERE created_at < ? AND blob_hash IN (${ph})`, [cutoff, ...distinct])
|
|
@@ -216,7 +216,7 @@ export class MinioBlobBackend {
|
|
|
216
216
|
throw new Error(`MinioBlobBackend.putBlob ${hash}: HTTP ${res.status} ${detail.slice(0, 200)}`);
|
|
217
217
|
}
|
|
218
218
|
if (this.indexPool && this.indexDialect) {
|
|
219
|
-
if (this.indexDialect === "
|
|
219
|
+
if (this.indexDialect === "mysql") {
|
|
220
220
|
await this.indexPool.query(`INSERT INTO ${this.indexTable} (blob_hash, byte_len, bytes, created_at) VALUES (?,?,NULL,?) ON DUPLICATE KEY UPDATE created_at = VALUES(created_at)`, [hash, bytes.byteLength, new Date()]);
|
|
221
221
|
}
|
|
222
222
|
else {
|