@sema-agent/server 1.303.0 → 1.305.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 +1 -0
- package/dist/config.js +1 -0
- package/dist/http/server.js +23 -7
- package/dist/http/tar.js +4 -0
- package/dist/main.js +29 -10
- package/dist/plugins/blob-backend.d.ts +3 -0
- package/dist/plugins/blob-backend.js +35 -9
- package/dist/plugins/task-attachment-store.d.ts +5 -0
- package/dist/plugins/task-attachment-store.js +68 -18
- package/dist/trace/project.js +3 -3
- package/package.json +1 -1
package/dist/config.d.ts
CHANGED
package/dist/config.js
CHANGED
|
@@ -538,6 +538,7 @@ export function loadConfig() {
|
|
|
538
538
|
: undefined,
|
|
539
539
|
snapshotBlobSqlMaxBytes: optFinitePositiveEnv("SNAPSHOT_BLOB_SQL_MAX_BYTES"),
|
|
540
540
|
snapshotBlobAllowSql: process.env.SNAPSHOT_BLOB_ALLOW_SQL_BYTES === "true",
|
|
541
|
+
attachmentOrphanGraceMs: ((v) => (v !== undefined && Number.isFinite(v) && v >= 0 ? v : 3_600_000))(process.env.ATTACHMENT_ORPHAN_GRACE_MS !== undefined ? Number(process.env.ATTACHMENT_ORPHAN_GRACE_MS) : undefined),
|
|
541
542
|
workspaceFileMaxBytes: optFinitePositiveEnv("WORKSPACE_FILE_MAX_BYTES") ?? 8 * 1024 * 1024,
|
|
542
543
|
sendUserFile: (process.env.MINIO_ENDPOINT || process.env.S3_ENDPOINT) && process.env.MINIO_ACCESS_KEY && process.env.MINIO_SECRET_KEY
|
|
543
544
|
? {
|
package/dist/http/server.js
CHANGED
|
@@ -274,7 +274,7 @@ export function createHttpServer(deps) {
|
|
|
274
274
|
void handle(req, res).catch((err) => {
|
|
275
275
|
if (err instanceof HttpError) {
|
|
276
276
|
if (!res.headersSent)
|
|
277
|
-
sendJson(res, err.status, { error: err.message, ...(err.code ? { code: err.code } : {}), ...(err.extra ?? {}) });
|
|
277
|
+
sendJson(res, err.status, { error: err.message, ...(err.code ? { code: err.code, errorCode: err.code } : {}), ...(err.extra ?? {}) });
|
|
278
278
|
else
|
|
279
279
|
res.end();
|
|
280
280
|
return;
|
|
@@ -3988,7 +3988,12 @@ export function createHttpServer(deps) {
|
|
|
3988
3988
|
}
|
|
3989
3989
|
snapshots.push({ key: k, files: m.size, ...(bytes !== undefined ? { bytes } : {}) });
|
|
3990
3990
|
}
|
|
3991
|
-
sendJson(res, 200, {
|
|
3991
|
+
sendJson(res, 200, {
|
|
3992
|
+
sessionId: wsSession,
|
|
3993
|
+
snapshots,
|
|
3994
|
+
...(snapshots.length > 0 ? { latest: snapshots[0].key } : {}),
|
|
3995
|
+
total: snapshots.length,
|
|
3996
|
+
});
|
|
3992
3997
|
return;
|
|
3993
3998
|
}
|
|
3994
3999
|
const keyRaw = safeDecode(wm[2]);
|
|
@@ -4089,8 +4094,16 @@ export function createHttpServer(deps) {
|
|
|
4089
4094
|
const write = async (buf) => {
|
|
4090
4095
|
if (res.writableEnded || res.destroyed)
|
|
4091
4096
|
throw new Error("client gone");
|
|
4092
|
-
if (!res.write(buf))
|
|
4093
|
-
await new Promise((
|
|
4097
|
+
if (!res.write(buf)) {
|
|
4098
|
+
await new Promise((resolve, reject) => {
|
|
4099
|
+
const onDrain = () => { cleanup(); resolve(); };
|
|
4100
|
+
const onGone = () => { cleanup(); reject(new Error("client gone (closed while draining)")); };
|
|
4101
|
+
const cleanup = () => { res.off("drain", onDrain); res.off("close", onGone); res.off("error", onGone); };
|
|
4102
|
+
res.once("drain", onDrain);
|
|
4103
|
+
res.once("close", onGone);
|
|
4104
|
+
res.once("error", onGone);
|
|
4105
|
+
});
|
|
4106
|
+
}
|
|
4094
4107
|
};
|
|
4095
4108
|
try {
|
|
4096
4109
|
for (const [p, h] of entries) {
|
|
@@ -4249,7 +4262,8 @@ export function createHttpServer(deps) {
|
|
|
4249
4262
|
if (deps.snapshotBlobSqlCapBytes !== undefined && body.byteLength > deps.snapshotBlobSqlCapBytes) {
|
|
4250
4263
|
sendJson(res, 413, {
|
|
4251
4264
|
code: "blob_too_large_for_sql",
|
|
4252
|
-
|
|
4265
|
+
errorCode: "blob_too_large_for_sql",
|
|
4266
|
+
error: `blob of ${body.byteLength} bytes exceeds this deployment's SQL snapshot-blob cap (${deps.snapshotBlobSqlCapBytes} bytes — TiDB's txn-entry-size-limit is the low wall at 6MiB by default; the mysql-protocol packet limit sits above it) — configure MinIO object storage (MINIO_ENDPOINT/MINIO_ACCESS_KEY/MINIO_SECRET_KEY) for large snapshot blobs, or raise SNAPSHOT_BLOB_SQL_MAX_BYTES if your deployment lifted those limits`,
|
|
4253
4267
|
});
|
|
4254
4268
|
return;
|
|
4255
4269
|
}
|
|
@@ -5180,7 +5194,7 @@ export function createHttpServer(deps) {
|
|
|
5180
5194
|
}
|
|
5181
5195
|
catch (err) {
|
|
5182
5196
|
if (err instanceof HttpError) {
|
|
5183
|
-
sendJson(res, err.status, { error: err.message, ...(err.code ? { code: err.code } : {}), ...(err.extra ?? {}) });
|
|
5197
|
+
sendJson(res, err.status, { error: err.message, ...(err.code ? { code: err.code, errorCode: err.code } : {}), ...(err.extra ?? {}) });
|
|
5184
5198
|
return null;
|
|
5185
5199
|
}
|
|
5186
5200
|
throw err;
|
|
@@ -6807,7 +6821,9 @@ function routeLabel(_method, url) {
|
|
|
6807
6821
|
return "other";
|
|
6808
6822
|
}
|
|
6809
6823
|
function workspaceContentType(relPath) {
|
|
6810
|
-
const
|
|
6824
|
+
const base = relPath.slice(relPath.lastIndexOf("/") + 1);
|
|
6825
|
+
const dot = base.lastIndexOf(".");
|
|
6826
|
+
const ext = dot > 0 ? base.slice(dot + 1).toLowerCase() : "";
|
|
6811
6827
|
const TABLE = {
|
|
6812
6828
|
txt: "text/plain; charset=utf-8", md: "text/plain; charset=utf-8", log: "text/plain; charset=utf-8",
|
|
6813
6829
|
json: "application/json; charset=utf-8", csv: "text/csv; charset=utf-8",
|
package/dist/http/tar.js
CHANGED
|
@@ -19,7 +19,11 @@ export function splitTarPath(relPath) {
|
|
|
19
19
|
}
|
|
20
20
|
return null;
|
|
21
21
|
}
|
|
22
|
+
const USTAR_MAX_SIZE = 8 * 1024 * 1024 * 1024 - 1;
|
|
22
23
|
export function tarHeader(relPath, sizeBytes, mtimeSec) {
|
|
24
|
+
if (!Number.isFinite(sizeBytes) || sizeBytes < 0 || sizeBytes > USTAR_MAX_SIZE) {
|
|
25
|
+
throw new Error(`tar: size ${sizeBytes} not representable in a ustar header (12-octal-byte field, max 8GiB-1): ${relPath}`);
|
|
26
|
+
}
|
|
23
27
|
const split = splitTarPath(relPath);
|
|
24
28
|
if (split === null)
|
|
25
29
|
throw new Error(`tar: path not representable in ustar (must be ≤255 bytes and splittable at "/"): ${relPath}`);
|
package/dist/main.js
CHANGED
|
@@ -1050,9 +1050,6 @@ async function main() {
|
|
|
1050
1050
|
...(sessionPolicyStore ? { sessionPolicyStore } : {}),
|
|
1051
1051
|
...(runtimeCapsResolver ? { runtimeCapsResolver } : {}),
|
|
1052
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
|
-
: {}),
|
|
1056
1053
|
...(executionEnvFactory ? { executionEnvFactory } : {}),
|
|
1057
1054
|
...(lspManager ? { lspManager } : {}),
|
|
1058
1055
|
onBackgroundChildEvent: fleetBackgroundChildPublisher(fleetBus, (msg, fields) => logger.info(msg, fields)),
|
|
@@ -1318,7 +1315,20 @@ async function main() {
|
|
|
1318
1315
|
if (webSearch)
|
|
1319
1316
|
logger.info(`WebSearch enabled (provider=${webSearchCfg.provider}, maxResults=${webSearch.maxResults})`);
|
|
1320
1317
|
const ensureChildSessionDurable = checkpointStore && config.sessionBackend === "tidb" ? (sessionId) => ensureChildSessionDurableWithPromotion(sessionStore, subRunnerSessions, sessionId) : undefined;
|
|
1321
|
-
const webFetchSummarize =
|
|
1318
|
+
const webFetchSummarize = async (content, prompt, signal) => {
|
|
1319
|
+
const model = (() => {
|
|
1320
|
+
try {
|
|
1321
|
+
return coreResolveTaskModel({ modelRole: "summarize" }, { models: config.models, roles: config.roles }).model;
|
|
1322
|
+
}
|
|
1323
|
+
catch (err) {
|
|
1324
|
+
logger.warn("webfetch_summarizer_model_unresolved", { err: String(err), note: "this fetch falls back to raw page content" });
|
|
1325
|
+
return undefined;
|
|
1326
|
+
}
|
|
1327
|
+
})();
|
|
1328
|
+
if (!model)
|
|
1329
|
+
throw new Error("summarize model unresolved for this deployment");
|
|
1330
|
+
return makeWebFetchSummarizer(brain, model)(content, prompt, signal);
|
|
1331
|
+
};
|
|
1322
1332
|
const scenarioDeps = {
|
|
1323
1333
|
runner, subRunner, model: "default", skills, repoClient, requirePrincipal: config.requirePrincipal,
|
|
1324
1334
|
...(webSearch ? { webSearch } : {}), metrics, logger, webFetchSummarize,
|
|
@@ -1372,6 +1382,7 @@ async function main() {
|
|
|
1372
1382
|
}
|
|
1373
1383
|
};
|
|
1374
1384
|
let wfRunReapInFlight = false;
|
|
1385
|
+
let attachmentSweepInFlight = false;
|
|
1375
1386
|
let bgAgentReapInFlight = false;
|
|
1376
1387
|
const reapBgAgents = backgroundAgentStore
|
|
1377
1388
|
? async () => {
|
|
@@ -1440,12 +1451,17 @@ async function main() {
|
|
|
1440
1451
|
void toolResultStore?.reapOlderThan?.(Date.now() - config.toolResultTtlSec * 1000)?.catch(() => undefined);
|
|
1441
1452
|
void fileSnapshotStore?.sweepOrphanBlobs?.().catch(() => undefined);
|
|
1442
1453
|
void taskAttachmentStore?.reapUnbound(Date.now() - config.attachmentUnboundTtlMs).then(reapCount("attachments_reaped_total", {})).catch(() => undefined);
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1454
|
+
if (config.attachmentOrphanGraceMs > 0 && !attachmentSweepInFlight && taskAttachmentStore?.sweepOrphanObjects) {
|
|
1455
|
+
attachmentSweepInFlight = true;
|
|
1456
|
+
void taskAttachmentStore
|
|
1457
|
+
.sweepOrphanObjects(config.attachmentOrphanGraceMs)
|
|
1458
|
+
.then((n) => { if (n > 0) {
|
|
1459
|
+
metrics.inc("attachment_orphan_objects_swept_total", {}, n);
|
|
1460
|
+
logger.info("attachment_orphan_objects_swept", { removed: n });
|
|
1461
|
+
} })
|
|
1462
|
+
.catch((err) => logger.warn("attachment_orphan_sweep_failed", { err: String(err) }))
|
|
1463
|
+
.finally(() => { attachmentSweepInFlight = false; });
|
|
1464
|
+
}
|
|
1449
1465
|
void backend?.session()?.sweepStagingSessions?.().catch(() => undefined);
|
|
1450
1466
|
void imageBakes?.reapStaleBakes(config.imageBakes.staleMs).then(reapCount("bakes_reaped_total", {})).catch(() => undefined);
|
|
1451
1467
|
void worktreeReap?.();
|
|
@@ -1825,6 +1841,9 @@ async function main() {
|
|
|
1825
1841
|
authorize,
|
|
1826
1842
|
sessionStoreLabel: config.sessionBackend === "tidb" && backend ? `durable(${backend.kind})` : config.sessionBackend,
|
|
1827
1843
|
...(taskAttachmentStore ? { taskAttachmentStore } : {}),
|
|
1844
|
+
...(backend && backend.kind !== "local" && !config.snapshotBlobStore
|
|
1845
|
+
? ((cap) => (cap !== undefined ? { snapshotBlobSqlCapBytes: cap } : {}))(config.snapshotBlobSqlMaxBytes ?? (backend.kind === "mysql" ? SQL_BLOB_DEFAULT_MAX_BYTES : undefined))
|
|
1846
|
+
: {}),
|
|
1828
1847
|
modelReady: () => modelReadyState.ready,
|
|
1829
1848
|
scenarioDetails,
|
|
1830
1849
|
...(registryJwtVerifier ? { registryJwtVerifier } : {}),
|
|
@@ -51,6 +51,9 @@ export declare class MinioBlobBackend implements BlobBackend {
|
|
|
51
51
|
hash: string;
|
|
52
52
|
lastModifiedMs: number;
|
|
53
53
|
}>;
|
|
54
|
+
headObject(hash: string): Promise<{
|
|
55
|
+
lastModifiedMs: number;
|
|
56
|
+
} | undefined>;
|
|
54
57
|
deleteBlobs(hashes: string[]): Promise<number>;
|
|
55
58
|
}
|
|
56
59
|
//# sourceMappingURL=blob-backend.d.ts.map
|
|
@@ -75,14 +75,24 @@ export async function sqlBlobSizes(dialect, pool, hashes) {
|
|
|
75
75
|
if (hashes.length === 0)
|
|
76
76
|
return new Map();
|
|
77
77
|
const distinct = [...new Set(hashes)];
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
78
|
+
const CHUNK = 1000;
|
|
79
|
+
const out = new Map();
|
|
80
|
+
for (let i = 0; i < distinct.length; i += CHUNK) {
|
|
81
|
+
const part = distinct.slice(i, i + CHUNK);
|
|
82
|
+
if (dialect === "tidb") {
|
|
83
|
+
const ph = part.map(() => "?").join(",");
|
|
84
|
+
const [rows] = await pool.query(`SELECT blob_hash, byte_len FROM snapshot_blob WHERE blob_hash IN (${ph})`, part);
|
|
85
|
+
for (const r of rows)
|
|
86
|
+
out.set(String(r.blob_hash), Number(r.byte_len));
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
const ph = part.map((_, j) => `$${j + 1}`).join(",");
|
|
90
|
+
const r = await pool.query(`SELECT blob_hash, byte_len FROM snapshot_blob WHERE blob_hash IN (${ph})`, part);
|
|
91
|
+
for (const row of r.rows)
|
|
92
|
+
out.set(row.blob_hash, Number(row.byte_len));
|
|
93
|
+
}
|
|
82
94
|
}
|
|
83
|
-
|
|
84
|
-
const r = await pool.query(`SELECT blob_hash, byte_len FROM snapshot_blob WHERE blob_hash IN (${ph})`, distinct);
|
|
85
|
-
return new Map(r.rows.map((row) => [row.blob_hash, Number(row.byte_len)]));
|
|
95
|
+
return out;
|
|
86
96
|
}
|
|
87
97
|
async function sqlDeleteIndexRowsPastGrace(dialect, pool, hashes) {
|
|
88
98
|
if (hashes.length === 0)
|
|
@@ -194,7 +204,11 @@ export class MinioBlobBackend {
|
|
|
194
204
|
async *listObjects() {
|
|
195
205
|
const f = this.cfg.fetchImpl ?? fetch;
|
|
196
206
|
let token;
|
|
207
|
+
const MAX_PAGES = 1000;
|
|
208
|
+
let pages = 0;
|
|
197
209
|
do {
|
|
210
|
+
if (++pages > MAX_PAGES)
|
|
211
|
+
throw new Error(`listObjects: exceeded ${MAX_PAGES} pages (max-keys 1000/page) — the backend is looping its continuation-token or the prefix is unexpectedly large; refusing to report a partial orphan set`);
|
|
198
212
|
const url = presignS3ListUrl({
|
|
199
213
|
endpoint: this.cfg.endpoint,
|
|
200
214
|
bucket: this.cfg.bucket,
|
|
@@ -225,6 +239,15 @@ export class MinioBlobBackend {
|
|
|
225
239
|
throw new Error("listObjects: truncated response without NextContinuationToken");
|
|
226
240
|
} while (token !== undefined);
|
|
227
241
|
}
|
|
242
|
+
async headObject(hash) {
|
|
243
|
+
const f = this.cfg.fetchImpl ?? fetch;
|
|
244
|
+
const res = await f(this.presign(hash, "HEAD"), { method: "HEAD" });
|
|
245
|
+
if (!res.ok)
|
|
246
|
+
return undefined;
|
|
247
|
+
const lm = res.headers.get("last-modified");
|
|
248
|
+
const ms = lm ? Date.parse(lm) : NaN;
|
|
249
|
+
return { lastModifiedMs: Number.isFinite(ms) ? ms : Date.now() };
|
|
250
|
+
}
|
|
228
251
|
async deleteBlobs(hashes) {
|
|
229
252
|
if (hashes.length === 0)
|
|
230
253
|
return 0;
|
|
@@ -237,14 +260,17 @@ export class MinioBlobBackend {
|
|
|
237
260
|
}
|
|
238
261
|
if (toDelete.length === 0)
|
|
239
262
|
return 0;
|
|
263
|
+
let okCount = 0;
|
|
240
264
|
await mapBounded(toDelete, MINIO_OP_CONCURRENCY, async (hash) => {
|
|
241
265
|
try {
|
|
242
|
-
await fetch(this.presign(hash, "DELETE"), { method: "DELETE" });
|
|
266
|
+
const res = await (this.cfg.fetchImpl ?? fetch)(this.presign(hash, "DELETE"), { method: "DELETE" });
|
|
267
|
+
if (res.ok || res.status === 404)
|
|
268
|
+
okCount++;
|
|
243
269
|
}
|
|
244
270
|
catch {
|
|
245
271
|
}
|
|
246
272
|
});
|
|
247
|
-
return
|
|
273
|
+
return okCount;
|
|
248
274
|
}
|
|
249
275
|
}
|
|
250
276
|
//# sourceMappingURL=blob-backend.js.map
|
|
@@ -33,6 +33,9 @@ export interface AttachmentBytesStore {
|
|
|
33
33
|
hash: string;
|
|
34
34
|
lastModifiedMs: number;
|
|
35
35
|
}>;
|
|
36
|
+
headObject?(hash: string): Promise<{
|
|
37
|
+
lastModifiedMs: number;
|
|
38
|
+
} | undefined>;
|
|
36
39
|
}
|
|
37
40
|
export declare function materializedRelPaths(atts: TaskAttachmentRecord[]): Map<string, string>;
|
|
38
41
|
export declare function bindAttachmentsForTask(opts: {
|
|
@@ -85,6 +88,7 @@ export declare class TiDBTaskAttachmentStore implements TaskAttachmentStore {
|
|
|
85
88
|
reapUnbound(olderThanMs: number): Promise<number>;
|
|
86
89
|
sweepOrphanObjects(graceMs: number, nowMs?: number): Promise<number>;
|
|
87
90
|
private referencedShas;
|
|
91
|
+
private filterStillOrphan;
|
|
88
92
|
}
|
|
89
93
|
export declare class PgTaskAttachmentStore implements TaskAttachmentStore {
|
|
90
94
|
private readonly q;
|
|
@@ -103,5 +107,6 @@ export declare class PgTaskAttachmentStore implements TaskAttachmentStore {
|
|
|
103
107
|
reapUnbound(olderThanMs: number): Promise<number>;
|
|
104
108
|
sweepOrphanObjects(graceMs: number, nowMs?: number): Promise<number>;
|
|
105
109
|
private referencedShas;
|
|
110
|
+
private filterStillOrphan;
|
|
106
111
|
}
|
|
107
112
|
//# sourceMappingURL=task-attachment-store.d.ts.map
|
|
@@ -163,19 +163,26 @@ export class TiDBTaskAttachmentStore {
|
|
|
163
163
|
const lister = this.bytes;
|
|
164
164
|
if (typeof lister.listObjects !== "function")
|
|
165
165
|
return 0;
|
|
166
|
-
const candidates = [];
|
|
167
|
-
for await (const o of lister.listObjects()) {
|
|
168
|
-
if (nowMs - o.lastModifiedMs >= graceMs)
|
|
169
|
-
candidates.push(o.hash);
|
|
170
|
-
}
|
|
171
166
|
let removed = 0;
|
|
172
|
-
|
|
173
|
-
|
|
167
|
+
let batch = [];
|
|
168
|
+
const flush = async () => {
|
|
169
|
+
if (batch.length === 0)
|
|
170
|
+
return;
|
|
174
171
|
const referenced = await this.referencedShas(batch);
|
|
175
172
|
const orphans = batch.filter((h) => !referenced.has(h));
|
|
176
|
-
|
|
177
|
-
|
|
173
|
+
const safe = await this.filterStillOrphan(orphans, graceMs, nowMs);
|
|
174
|
+
if (safe.length > 0)
|
|
175
|
+
removed += await this.bytes.deleteBlobs(safe);
|
|
176
|
+
batch = [];
|
|
177
|
+
};
|
|
178
|
+
for await (const o of lister.listObjects()) {
|
|
179
|
+
if (nowMs - o.lastModifiedMs < graceMs)
|
|
180
|
+
continue;
|
|
181
|
+
batch.push(o.hash);
|
|
182
|
+
if (batch.length >= 200)
|
|
183
|
+
await flush();
|
|
178
184
|
}
|
|
185
|
+
await flush();
|
|
179
186
|
return removed;
|
|
180
187
|
}
|
|
181
188
|
async referencedShas(shas) {
|
|
@@ -185,6 +192,24 @@ export class TiDBTaskAttachmentStore {
|
|
|
185
192
|
const [rows] = await this.pool.query(`SELECT DISTINCT sha256 FROM ${TASK_ATTACHMENT_TABLE} WHERE sha256 IN (${ph})`, shas);
|
|
186
193
|
return new Set(rows.map((r) => String(r.sha256)));
|
|
187
194
|
}
|
|
195
|
+
async filterStillOrphan(hashes, graceMs, nowMs) {
|
|
196
|
+
if (hashes.length === 0)
|
|
197
|
+
return [];
|
|
198
|
+
const head = this.bytes.headObject?.bind(this.bytes);
|
|
199
|
+
if (!head)
|
|
200
|
+
return [];
|
|
201
|
+
const out = [];
|
|
202
|
+
for (const h of hashes) {
|
|
203
|
+
try {
|
|
204
|
+
const cur = await head(h);
|
|
205
|
+
if (cur && nowMs - cur.lastModifiedMs >= graceMs)
|
|
206
|
+
out.push(h);
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return out;
|
|
212
|
+
}
|
|
188
213
|
}
|
|
189
214
|
export class PgTaskAttachmentStore {
|
|
190
215
|
q;
|
|
@@ -250,19 +275,26 @@ export class PgTaskAttachmentStore {
|
|
|
250
275
|
const lister = this.bytes;
|
|
251
276
|
if (typeof lister.listObjects !== "function")
|
|
252
277
|
return 0;
|
|
253
|
-
const candidates = [];
|
|
254
|
-
for await (const o of lister.listObjects()) {
|
|
255
|
-
if (nowMs - o.lastModifiedMs >= graceMs)
|
|
256
|
-
candidates.push(o.hash);
|
|
257
|
-
}
|
|
258
278
|
let removed = 0;
|
|
259
|
-
|
|
260
|
-
|
|
279
|
+
let batch = [];
|
|
280
|
+
const flush = async () => {
|
|
281
|
+
if (batch.length === 0)
|
|
282
|
+
return;
|
|
261
283
|
const referenced = await this.referencedShas(batch);
|
|
262
284
|
const orphans = batch.filter((h) => !referenced.has(h));
|
|
263
|
-
|
|
264
|
-
|
|
285
|
+
const safe = await this.filterStillOrphan(orphans, graceMs, nowMs);
|
|
286
|
+
if (safe.length > 0)
|
|
287
|
+
removed += await this.bytes.deleteBlobs(safe);
|
|
288
|
+
batch = [];
|
|
289
|
+
};
|
|
290
|
+
for await (const o of lister.listObjects()) {
|
|
291
|
+
if (nowMs - o.lastModifiedMs < graceMs)
|
|
292
|
+
continue;
|
|
293
|
+
batch.push(o.hash);
|
|
294
|
+
if (batch.length >= 200)
|
|
295
|
+
await flush();
|
|
265
296
|
}
|
|
297
|
+
await flush();
|
|
266
298
|
return removed;
|
|
267
299
|
}
|
|
268
300
|
async referencedShas(shas) {
|
|
@@ -272,5 +304,23 @@ export class PgTaskAttachmentStore {
|
|
|
272
304
|
const r = await this.q(`SELECT DISTINCT sha256 FROM ${TASK_ATTACHMENT_TABLE} WHERE sha256 IN (${ph})`, shas);
|
|
273
305
|
return new Set(r.rows.map((row) => String(row.sha256)));
|
|
274
306
|
}
|
|
307
|
+
async filterStillOrphan(hashes, graceMs, nowMs) {
|
|
308
|
+
if (hashes.length === 0)
|
|
309
|
+
return [];
|
|
310
|
+
const head = this.bytes.headObject?.bind(this.bytes);
|
|
311
|
+
if (!head)
|
|
312
|
+
return [];
|
|
313
|
+
const out = [];
|
|
314
|
+
for (const h of hashes) {
|
|
315
|
+
try {
|
|
316
|
+
const cur = await head(h);
|
|
317
|
+
if (cur && nowMs - cur.lastModifiedMs >= graceMs)
|
|
318
|
+
out.push(h);
|
|
319
|
+
}
|
|
320
|
+
catch {
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return out;
|
|
324
|
+
}
|
|
275
325
|
}
|
|
276
326
|
//# sourceMappingURL=task-attachment-store.js.map
|
package/dist/trace/project.js
CHANGED
|
@@ -62,7 +62,7 @@ export function toolEndEventData(ev) {
|
|
|
62
62
|
isError: ev.isError,
|
|
63
63
|
...(ev.output !== undefined ? { output: redactDeep(ev.output) } : {}),
|
|
64
64
|
...(ev.truncated ? { truncated: true } : {}),
|
|
65
|
-
...(
|
|
65
|
+
...(Number.isFinite(ev.totalChars) ? { totalChars: ev.totalChars } : {}),
|
|
66
66
|
...(ev.structured !== undefined ? { structured: redactDeep(ev.structured) } : {}),
|
|
67
67
|
...identityFields(ev),
|
|
68
68
|
};
|
|
@@ -241,7 +241,7 @@ export function projectEvents(events, opts = {}) {
|
|
|
241
241
|
break;
|
|
242
242
|
case "tool_end": {
|
|
243
243
|
const callId = String(d.toolCallId ?? "");
|
|
244
|
-
blocks.push({ type: "tool-result", callId, ...(typeof d.label === "string" ? { label: d.label } : {}), output: d.output, ...(d.isError ? { isError: true } : {}), ...(d.truncated ? { truncated: true } : {}), ...(
|
|
244
|
+
blocks.push({ type: "tool-result", callId, ...(typeof d.label === "string" ? { label: d.label } : {}), output: d.output, ...(d.isError ? { isError: true } : {}), ...(d.truncated ? { truncated: true } : {}), ...(Number.isFinite(d.totalChars) ? { totalChars: d.totalChars } : {}), ...(d.structured !== undefined ? { structured: d.structured } : {}), ...identityFields(d) });
|
|
245
245
|
break;
|
|
246
246
|
}
|
|
247
247
|
case "prompt_assembled":
|
|
@@ -307,7 +307,7 @@ export function mapTraceEvent(type, seq, data) {
|
|
|
307
307
|
case "tool_start":
|
|
308
308
|
return { event: "tool-call", data: { seq, id: data.toolCallId, name: data.toolName, ...(typeof data.label === "string" ? { label: data.label } : {}), input: data.args, ...identityFields(data) } };
|
|
309
309
|
case "tool_end":
|
|
310
|
-
return { event: "tool-result", data: { seq, callId: data.toolCallId, ...(typeof data.label === "string" ? { label: data.label } : {}), isError: Boolean(data.isError), ...(data.output !== undefined ? { output: data.output } : {}), ...(data.truncated ? { truncated: true } : {}), ...(
|
|
310
|
+
return { event: "tool-result", data: { seq, callId: data.toolCallId, ...(typeof data.label === "string" ? { label: data.label } : {}), isError: Boolean(data.isError), ...(data.output !== undefined ? { output: data.output } : {}), ...(data.truncated ? { truncated: true } : {}), ...(Number.isFinite(data.totalChars) ? { totalChars: data.totalChars } : {}), ...(data.structured !== undefined ? { structured: data.structured } : {}), ...identityFields(data) } };
|
|
311
311
|
case "turn_end": {
|
|
312
312
|
const tokens = toContractTokens(data.usage);
|
|
313
313
|
return { event: "turn", data: { seq, ...(tokens ? { tokens } : {}) } };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.305.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",
|