@sema-agent/server 1.294.0 → 1.295.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/dist/config.d.ts +2 -0
- package/dist/config.js +2 -0
- package/dist/http/server.d.ts +1 -0
- package/dist/http/server.js +7 -0
- package/dist/main.js +12 -5
- package/dist/plugins/blob-backend.d.ts +7 -1
- package/dist/plugins/blob-backend.js +16 -1
- package/dist/plugins/store-backend.d.ts +1 -0
- package/dist/plugins/store-backend.js +11 -2
- package/package.json +1 -1
package/dist/config.d.ts
CHANGED
package/dist/config.js
CHANGED
|
@@ -536,6 +536,8 @@ export function loadConfig() {
|
|
|
536
536
|
...((ttl) => (ttl !== undefined ? { presignTtlSec: ttl } : {}))(optFinitePositiveEnv("SESSION_SNAPSHOT_TTL_SEC")),
|
|
537
537
|
}
|
|
538
538
|
: undefined,
|
|
539
|
+
snapshotBlobSqlMaxBytes: optFinitePositiveEnv("SNAPSHOT_BLOB_SQL_MAX_BYTES"),
|
|
540
|
+
snapshotBlobAllowSql: process.env.SNAPSHOT_BLOB_ALLOW_SQL_BYTES === "true",
|
|
539
541
|
sendUserFile: (process.env.MINIO_ENDPOINT || process.env.S3_ENDPOINT) && process.env.MINIO_ACCESS_KEY && process.env.MINIO_SECRET_KEY
|
|
540
542
|
? {
|
|
541
543
|
endpoint: (process.env.MINIO_ENDPOINT || process.env.S3_ENDPOINT),
|
package/dist/http/server.d.ts
CHANGED
|
@@ -63,6 +63,7 @@ export interface ServiceDeps {
|
|
|
63
63
|
approvalExemptionStore?: ApprovalExemptionStore;
|
|
64
64
|
sessionPolicyStore?: ServiceSessionPolicyStore;
|
|
65
65
|
fileSnapshotStore?: ServiceFileSnapshotStore;
|
|
66
|
+
snapshotBlobSqlCapBytes?: number;
|
|
66
67
|
backend?: StoreBackend;
|
|
67
68
|
sessionMirrorRuling?: (principal: string | undefined) => Promise<SessionMirrorRuling | undefined>;
|
|
68
69
|
approvalStore?: ApprovalStore;
|
package/dist/http/server.js
CHANGED
|
@@ -4062,6 +4062,13 @@ export function createHttpServer(deps) {
|
|
|
4062
4062
|
return;
|
|
4063
4063
|
}
|
|
4064
4064
|
const body = await readRawBody(req, SYNC_BLOB_MAX_BYTES);
|
|
4065
|
+
if (deps.snapshotBlobSqlCapBytes !== undefined && body.byteLength > deps.snapshotBlobSqlCapBytes) {
|
|
4066
|
+
sendJson(res, 413, {
|
|
4067
|
+
code: "blob_too_large_for_sql",
|
|
4068
|
+
error: `blob of ${body.byteLength} bytes exceeds this deployment's SQL snapshot-blob cap (${deps.snapshotBlobSqlCapBytes} bytes; mysql-protocol packet limit) — configure MinIO object storage (MINIO_ENDPOINT/MINIO_ACCESS_KEY/MINIO_SECRET_KEY) for large snapshot blobs`,
|
|
4069
|
+
});
|
|
4070
|
+
return;
|
|
4071
|
+
}
|
|
4065
4072
|
if (createHash("sha256").update(body).digest("hex") !== hash) {
|
|
4066
4073
|
sendJson(res, 400, { error: "blob hash mismatch" });
|
|
4067
4074
|
return;
|
package/dist/main.js
CHANGED
|
@@ -28,7 +28,7 @@ import { loadConfig, logConfigDiagnostics } from "./config.js";
|
|
|
28
28
|
import { resourceSuspendOptIn } from "./resource-suspend.js";
|
|
29
29
|
import { createSessionStore, ensureChildSessionDurableWithPromotion } from "./plugins/session-store.js";
|
|
30
30
|
import { ForkRoutingSessionStore } from "./plugins/fork-routing-session-store.js";
|
|
31
|
-
import { createStoreBackend } from "./plugins/store-backend.js";
|
|
31
|
+
import { createStoreBackend, assertCloudSnapshotBlobPosture } from "./plugins/store-backend.js";
|
|
32
32
|
import { e2bExecutionEnvFactory } from "./plugins/remote-env-e2b.js";
|
|
33
33
|
import { k8sExecutionEnvFactory } from "./plugins/remote-env-k8s.js";
|
|
34
34
|
import { sshExecutionEnvFactory } from "./plugins/remote-env-ssh.js";
|
|
@@ -80,7 +80,7 @@ import { defaultLkgPath, defaultSkillCacheDir, saveLkg, loadLkg } from "./config
|
|
|
80
80
|
import { TiDBRosterStore, PgRosterStore, ensureTiDBRosterSchema, ensurePgRosterSchema } from "./plugins/roster-store-sql.js";
|
|
81
81
|
import { TiDBTaskAttachmentStore, PgTaskAttachmentStore, ensureTiDBTaskAttachmentSchema, ensurePgTaskAttachmentSchema, bindAttachmentsForTask, materializeAttachmentsInto } from "./plugins/task-attachment-store.js";
|
|
82
82
|
import { LocalTaskAttachmentStore } from "./plugins/local-task-attachment-store.js";
|
|
83
|
-
import { MinioBlobBackend } from "./plugins/blob-backend.js";
|
|
83
|
+
import { MinioBlobBackend, SQL_BLOB_DEFAULT_MAX_BYTES } from "./plugins/blob-backend.js";
|
|
84
84
|
import { TiDBBackgroundAgentStore, PgBackgroundAgentStore, ensureTiDBBackgroundAgentSchema, ensurePgBackgroundAgentSchema } from "./plugins/background-agent-store-sql.js";
|
|
85
85
|
import { TiDBMailboxStore, PgMailboxStore, ensureTiDBMailboxSchema, ensurePgMailboxSchema } from "./plugins/mailbox-store-sql.js";
|
|
86
86
|
import { createPrincipalCapsClient, gateExecutionLane, scopedTokenNeedsWorker, applyObserverEnvOptIn } from "./runtime-caps-resolver.js";
|
|
@@ -634,10 +634,14 @@ async function main() {
|
|
|
634
634
|
"the in-memory store cannot enforce session ownership.");
|
|
635
635
|
}
|
|
636
636
|
if (config.requirePrincipal && backend?.kind === "local") {
|
|
637
|
-
throw new Error("REQUIRE_PRINCIPAL=true is not supported on the local
|
|
638
|
-
"process-local and lost on restart
|
|
639
|
-
"
|
|
637
|
+
throw new Error("REQUIRE_PRINCIPAL=true is not supported on the local file backend (DB_BACKEND=local): session/run CONTENT is " +
|
|
638
|
+
"durable there, but OWNER attribution is process-local and lost on restart (store-backend.ts §0.5 — durable " +
|
|
639
|
+
"owners = P1), so it cannot durably enforce multi-tenant session ownership. Use a SQL backend " +
|
|
640
|
+
"(DB_BACKEND=mysql|pg) for multi-tenant, or run local single-user with REQUIRE_PRINCIPAL=false (a BFF may " +
|
|
641
|
+
"still inject x-agent-principal per request for memory scoping and audit attribution).");
|
|
640
642
|
}
|
|
643
|
+
if (backend)
|
|
644
|
+
assertCloudSnapshotBlobPosture(backend.kind, config);
|
|
641
645
|
if (!config.requirePrincipal && sessionStore.ownerOf) {
|
|
642
646
|
logger.warn("principal_optional", {
|
|
643
647
|
note: "owner-aware session store with REQUIRE_PRINCIPAL=false — owned sessions are protected, but headerless callers can create/share anonymous sessions; set REQUIRE_PRINCIPAL=true for multi-tenant",
|
|
@@ -1046,6 +1050,9 @@ async function main() {
|
|
|
1046
1050
|
...(sessionPolicyStore ? { sessionPolicyStore } : {}),
|
|
1047
1051
|
...(runtimeCapsResolver ? { runtimeCapsResolver } : {}),
|
|
1048
1052
|
...(fileSnapshotStore ? { fileSnapshotStore } : {}),
|
|
1053
|
+
...(backend && backend.kind !== "local" && !config.snapshotBlobStore
|
|
1054
|
+
? ((cap) => (cap !== undefined ? { snapshotBlobSqlCapBytes: cap } : {}))(config.snapshotBlobSqlMaxBytes ?? (backend.kind === "mysql" ? SQL_BLOB_DEFAULT_MAX_BYTES : undefined))
|
|
1055
|
+
: {}),
|
|
1049
1056
|
...(executionEnvFactory ? { executionEnvFactory } : {}),
|
|
1050
1057
|
...(lspManager ? { lspManager } : {}),
|
|
1051
1058
|
onBackgroundChildEvent: fleetBackgroundChildPublisher(fleetBus, (msg, fields) => logger.info(msg, fields)),
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { Pool as MysqlPool } from "mysql2/promise";
|
|
2
2
|
import type { Pool as PgPool } from "pg";
|
|
3
|
+
export declare const SQL_BLOB_DEFAULT_MAX_BYTES = 6291456;
|
|
4
|
+
export declare class BlobTooLargeError extends Error {
|
|
5
|
+
readonly code: "blob_too_large_for_sql";
|
|
6
|
+
constructor(byteLen: number, capBytes: number);
|
|
7
|
+
}
|
|
3
8
|
export interface BlobBackend {
|
|
4
9
|
putBlob(hash: string, bytes: Uint8Array): Promise<void>;
|
|
5
10
|
getBlob(hash: string): Promise<Uint8Array | undefined>;
|
|
@@ -9,7 +14,8 @@ export interface BlobBackend {
|
|
|
9
14
|
export declare class SqlBlobBackend implements BlobBackend {
|
|
10
15
|
private readonly dialect;
|
|
11
16
|
private readonly pool;
|
|
12
|
-
|
|
17
|
+
private readonly capBytes;
|
|
18
|
+
constructor(dialect: "tidb" | "pg", pool: MysqlPool | PgPool, maxBytes?: number);
|
|
13
19
|
putBlob(hash: string, bytes: Uint8Array): Promise<void>;
|
|
14
20
|
getBlob(hash: string): Promise<Uint8Array | undefined>;
|
|
15
21
|
hasBlobs(hashes: string[]): Promise<Set<string>>;
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { presignS3Url } from "./s3-presign.js";
|
|
3
3
|
const BLOB_GC_GRACE_MS = 3_600_000;
|
|
4
|
+
export const SQL_BLOB_DEFAULT_MAX_BYTES = 6_291_456;
|
|
5
|
+
export class BlobTooLargeError extends Error {
|
|
6
|
+
code = "blob_too_large_for_sql";
|
|
7
|
+
constructor(byteLen, capBytes) {
|
|
8
|
+
super(`blob of ${byteLen} bytes exceeds the SQL snapshot-blob cap (${capBytes} bytes): a single-row INSERT this large ` +
|
|
9
|
+
`hits TiDB's txn-entry-size-limit (default 6MiB) or the mysql-protocol max_allowed_packet (~2x text-protocol ` +
|
|
10
|
+
`inflation). Configure object storage (MINIO_ENDPOINT/MINIO_ACCESS_KEY/MINIO_SECRET_KEY) for large snapshot ` +
|
|
11
|
+
`blobs, or override the cap with SNAPSHOT_BLOB_SQL_MAX_BYTES if your deployment raised those limits.`);
|
|
12
|
+
this.name = "BlobTooLargeError";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
4
15
|
const MINIO_OP_CONCURRENCY = 16;
|
|
5
16
|
async function mapBounded(items, limit, fn) {
|
|
6
17
|
let i = 0;
|
|
@@ -15,11 +26,15 @@ async function mapBounded(items, limit, fn) {
|
|
|
15
26
|
export class SqlBlobBackend {
|
|
16
27
|
dialect;
|
|
17
28
|
pool;
|
|
18
|
-
|
|
29
|
+
capBytes;
|
|
30
|
+
constructor(dialect, pool, maxBytes) {
|
|
19
31
|
this.dialect = dialect;
|
|
20
32
|
this.pool = pool;
|
|
33
|
+
this.capBytes = maxBytes ?? (dialect === "tidb" ? SQL_BLOB_DEFAULT_MAX_BYTES : Number.POSITIVE_INFINITY);
|
|
21
34
|
}
|
|
22
35
|
async putBlob(hash, bytes) {
|
|
36
|
+
if (bytes.byteLength > this.capBytes)
|
|
37
|
+
throw new BlobTooLargeError(bytes.byteLength, this.capBytes);
|
|
23
38
|
if (this.dialect === "tidb") {
|
|
24
39
|
await this.pool.query("INSERT INTO snapshot_blob (blob_hash, byte_len, bytes, created_at) VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE created_at = VALUES(created_at)", [hash, bytes.byteLength, Buffer.from(bytes), new Date()]);
|
|
25
40
|
}
|
|
@@ -93,6 +93,7 @@ export interface StoreBackend {
|
|
|
93
93
|
pgPool(): PgPool | undefined;
|
|
94
94
|
dbNowMs?(): Promise<number>;
|
|
95
95
|
}
|
|
96
|
+
export declare function assertCloudSnapshotBlobPosture(kind: "mysql" | "pg" | "local", config: Pick<ServiceConfig, "snapshotBlobStore" | "snapshotBlobAllowSql">): void;
|
|
96
97
|
export declare function snapshotBoundsFromConfig(config: ServiceConfig): FileSnapshotBounds;
|
|
97
98
|
export declare function createStoreBackend(config: ServiceConfig): StoreBackend | undefined;
|
|
98
99
|
//# sourceMappingURL=store-backend.d.ts.map
|
|
@@ -21,7 +21,7 @@ import { TiDBWorkflowJournalStore } from "./tidb-workflow-journal-store.js";
|
|
|
21
21
|
import { PgWorkflowJournalStore } from "./pg-workflow-journal-store.js";
|
|
22
22
|
import { TiDBFileSnapshotStore } from "./tidb-file-snapshot-store.js";
|
|
23
23
|
import { PgFileSnapshotStore } from "./pg-file-snapshot-store.js";
|
|
24
|
-
import { MinioBlobBackend } from "./blob-backend.js";
|
|
24
|
+
import { MinioBlobBackend, SqlBlobBackend } from "./blob-backend.js";
|
|
25
25
|
import { createTidbPool, ensureSchema as ensureTidbSchema } from "./tidb-pool.js";
|
|
26
26
|
import { createPgPool, ensurePgSchema, pgPoolOptions } from "./pg-pool.js";
|
|
27
27
|
import { TiDBRunStore } from "./tidb-run-store.js";
|
|
@@ -47,7 +47,7 @@ import { PgSessionStore } from "./pg-session-storage.js";
|
|
|
47
47
|
function snapshotBlobBackend(config, dialect, pool) {
|
|
48
48
|
const s = config.snapshotBlobStore;
|
|
49
49
|
if (!s)
|
|
50
|
-
return
|
|
50
|
+
return new SqlBlobBackend(dialect, pool, config.snapshotBlobSqlMaxBytes);
|
|
51
51
|
return new MinioBlobBackend({
|
|
52
52
|
endpoint: s.endpoint,
|
|
53
53
|
bucket: s.bucket,
|
|
@@ -58,6 +58,15 @@ function snapshotBlobBackend(config, dialect, pool) {
|
|
|
58
58
|
...(s.presignTtlSec ? { presignTtlSec: s.presignTtlSec } : {}),
|
|
59
59
|
}, { dialect, pool });
|
|
60
60
|
}
|
|
61
|
+
export function assertCloudSnapshotBlobPosture(kind, config) {
|
|
62
|
+
if (kind === "local" || config.snapshotBlobStore || config.snapshotBlobAllowSql)
|
|
63
|
+
return;
|
|
64
|
+
throw new Error("cloud deployments (DB_BACKEND=mysql|pg) must configure object storage for snapshot blobs " +
|
|
65
|
+
"(MINIO_ENDPOINT/MINIO_ACCESS_KEY/MINIO_SECRET_KEY): single-row SQL blob writes hit TiDB's " +
|
|
66
|
+
"txn-entry-size-limit (default 6MiB) / the mysql-protocol packet limit (bytes belong in object storage — " +
|
|
67
|
+
"same ruling as the D-1 attachment store). Single-box/test rigs may explicitly opt out with " +
|
|
68
|
+
"SNAPSHOT_BLOB_ALLOW_SQL_BYTES=true (a per-blob cap then applies; oversized blobs are rejected 413).");
|
|
69
|
+
}
|
|
61
70
|
export function snapshotBoundsFromConfig(config) {
|
|
62
71
|
const mb = config.rewindSnapshotMaxMb;
|
|
63
72
|
return mb !== undefined ? { ...DEFAULT_SNAPSHOT_BOUNDS, maxBytes: Math.round(mb * 1024 * 1024) } : DEFAULT_SNAPSHOT_BOUNDS;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.295.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",
|