@sema-agent/server 7.93.7 → 7.95.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/USAGE.md +52 -0
- package/dist/boot/resolve-spec.js +14 -1
- package/dist/boot/stage-09-leader.js +1 -1
- package/dist/boot/stores.js +6 -3
- package/dist/boot/workflow-orchestration.js +5 -1
- package/dist/config-catalog.js +5 -3
- package/dist/config-center/types.d.ts +1 -1
- package/dist/config-invariants.d.ts +2 -2
- package/dist/config-invariants.js +17 -0
- package/dist/config-types.d.ts +44 -3
- package/dist/config-types.js +1 -0
- package/dist/config.js +24 -4
- package/dist/hosted-posture.d.ts +176 -0
- package/dist/hosted-posture.js +76 -0
- package/dist/http/resume-ctx-read.d.ts +40 -0
- package/dist/http/resume-ctx-read.js +25 -0
- package/dist/http/resume-legs.js +62 -42
- package/dist/http/routes/capabilities.js +3 -0
- package/dist/http/routes/images.js +12 -0
- package/dist/http/routes/runs.js +18 -4
- package/dist/http/routes/workflows.js +3 -2
- package/dist/http/wire-types.d.ts +13 -4
- package/dist/observability/corrupt-read-seat.d.ts +56 -0
- package/dist/observability/corrupt-read-seat.js +16 -0
- package/dist/observability/fail-open.d.ts +4 -0
- package/dist/observability/fail-open.js +4 -0
- package/dist/orchestration/workflow-journal-entry.d.ts +24 -6
- package/dist/orchestration/workflow-journal-entry.js +17 -10
- package/dist/plugins/approval-ask-store-sql.js +2 -2
- package/dist/plugins/checkpoint-store-sql.d.ts +16 -3
- package/dist/plugins/checkpoint-store-sql.js +7 -9
- package/dist/plugins/image-bake-store-sql.js +85 -13
- package/dist/plugins/image-index-sql.js +75 -12
- package/dist/plugins/local-checkpoint-store.d.ts +23 -2
- package/dist/plugins/local-checkpoint-store.js +40 -33
- package/dist/plugins/local-session-store.d.ts +14 -0
- package/dist/plugins/local-session-store.js +12 -7
- package/dist/plugins/local-sidecar-quarantine.d.ts +10 -0
- package/dist/plugins/local-sidecar-quarantine.js +15 -0
- package/dist/plugins/local-task-attachment-store.d.ts +1 -1
- package/dist/plugins/local-task-attachment-store.js +5 -2
- package/dist/plugins/mailbox-store-sql.d.ts +6 -3
- package/dist/plugins/mailbox-store-sql.js +45 -43
- package/dist/plugins/memory-embedder-fingerprint.js +8 -12
- package/dist/plugins/memory-engine-pg.js +34 -16
- package/dist/plugins/memory-engine-tidb.js +19 -12
- package/dist/plugins/memory-engine-vector-util.d.ts +26 -0
- package/dist/plugins/memory-engine-vector-util.js +3 -0
- package/dist/plugins/memory-sync-store-pg.d.ts +6 -0
- package/dist/plugins/memory-sync-store-pg.js +14 -10
- package/dist/plugins/memory-sync-store-tidb.js +7 -11
- package/dist/plugins/pg-session-storage.js +26 -28
- package/dist/plugins/session-event-payload.d.ts +37 -0
- package/dist/plugins/session-event-payload.js +20 -0
- package/dist/plugins/sql-json-column.d.ts +124 -22
- package/dist/plugins/sql-json-column.js +22 -1
- package/dist/plugins/sql-row-helpers.d.ts +10 -16
- package/dist/plugins/sql-row-helpers.js +0 -29
- package/dist/plugins/store-backend.js +10 -12
- package/dist/plugins/store-contracts.d.ts +4 -6
- package/dist/plugins/store-contracts.js +0 -60
- package/dist/plugins/task-attachment-store.d.ts +21 -12
- package/dist/plugins/task-attachment-store.js +12 -0
- package/dist/plugins/tidb-session-storage.d.ts +2 -2
- package/dist/plugins/tidb-session-storage.js +12 -11
- package/dist/plugins/tidb-session-store.d.ts +2 -2
- package/dist/plugins/tidb-session-store.js +20 -22
- package/dist/plugins/usage-window-store-sql.d.ts +9 -1
- package/dist/plugins/usage-window-store-sql.js +23 -9
- package/dist/plugins/workflow-journal-store-sql.js +16 -4
- package/dist/run-local.js +4 -10
- package/dist/usage-analytics.d.ts +14 -1
- package/dist/usage-analytics.js +11 -17
- package/package.json +2 -2
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { StoredSession, SessionError, uuidv7, validateEntriesForImport, leafIdAfterEntry } from "@sema-agent/core";
|
|
2
2
|
import { TiDBSessionStorage } from "./tidb-session-storage.js";
|
|
3
|
+
import { decodeSessionEventPayload } from "./session-event-payload.js";
|
|
4
|
+
import { jsonColumnText } from "./sql-json-column.js";
|
|
3
5
|
import { escapeLike } from "./sql-escape.js";
|
|
4
6
|
import { MANAGED_RETENTION } from "./retention-store-sql.js";
|
|
5
7
|
import { contentForkRelation, fastForwardSharedContentDiverged } from "../session-sync-content.js";
|
|
@@ -186,7 +188,7 @@ export class TiDBSessionStore {
|
|
|
186
188
|
if (!metaRows[0])
|
|
187
189
|
return null;
|
|
188
190
|
const leafId = metaRows[0].leaf_id ?? null;
|
|
189
|
-
const [eventRows] = await this.pool.query(
|
|
191
|
+
const [eventRows] = await this.pool.query(`SELECT entry_id, parent_id, type, ${jsonColumnText("tidb", "payload")}, ts FROM session_event WHERE session_id = ? ORDER BY seq ASC`, [sourceId]);
|
|
190
192
|
const newId = uuidv7();
|
|
191
193
|
const now = new Date();
|
|
192
194
|
const conn = await this.pool.getConnection();
|
|
@@ -194,7 +196,7 @@ export class TiDBSessionStore {
|
|
|
194
196
|
await conn.beginTransaction();
|
|
195
197
|
for (let seq = 0; seq < eventRows.length; seq++) {
|
|
196
198
|
const r = eventRows[seq];
|
|
197
|
-
const payload =
|
|
199
|
+
const payload = String(r.payload);
|
|
198
200
|
await conn.query("INSERT INTO session_event (session_id, seq, entry_id, parent_id, type, payload, ts) VALUES (?,?,?,?,?,?,?)", [newId, seq, r.entry_id, r.parent_id ?? null, r.type, payload, r.ts]);
|
|
199
201
|
}
|
|
200
202
|
await conn.query("INSERT INTO session_meta (session_id, owner, created_at, updated_at, leaf_id, leaf_seq) VALUES (?,?,?,?,?,?)", [newId, owner, now, now, leafId, eventRows.length]);
|
|
@@ -217,8 +219,8 @@ export class TiDBSessionStore {
|
|
|
217
219
|
const [metaRows] = await this.pool.query("SELECT 1 FROM session_meta WHERE session_id = ?", [sessionId]);
|
|
218
220
|
if (!metaRows[0])
|
|
219
221
|
return null;
|
|
220
|
-
const [eventRows] = await this.pool.query(
|
|
221
|
-
return eventRows.map((r) => (
|
|
222
|
+
const [eventRows] = await this.pool.query(`SELECT seq, entry_id, parent_id, type, ${jsonColumnText("tidb", "payload")}, ts FROM session_event WHERE session_id = ? ORDER BY seq ASC`, [sessionId]);
|
|
223
|
+
return eventRows.map((r) => decodeSessionEventPayload(r.payload, sessionId, r.seq));
|
|
222
224
|
}
|
|
223
225
|
async listEntryIds(sessionId) {
|
|
224
226
|
const [metaRows] = await this.pool.query("SELECT 1 FROM session_meta WHERE session_id = ?", [sessionId]);
|
|
@@ -236,11 +238,9 @@ export class TiDBSessionStore {
|
|
|
236
238
|
async function* gen() {
|
|
237
239
|
let cursor = opts?.afterSeq ?? -1;
|
|
238
240
|
for (;;) {
|
|
239
|
-
const [rows] = await pool.query(
|
|
240
|
-
for (const r of rows)
|
|
241
|
-
|
|
242
|
-
yield (typeof p === "string" ? JSON.parse(p) : p);
|
|
243
|
-
}
|
|
241
|
+
const [rows] = await pool.query(`SELECT seq, ${jsonColumnText("tidb", "payload")} FROM session_event WHERE session_id = ? AND seq > ? ORDER BY seq ASC LIMIT ?`, [sessionId, cursor, batchSize]);
|
|
242
|
+
for (const r of rows)
|
|
243
|
+
yield decodeSessionEventPayload(r.payload, sessionId, r.seq);
|
|
244
244
|
if (rows.length < batchSize)
|
|
245
245
|
return;
|
|
246
246
|
cursor = Number(rows[rows.length - 1].seq);
|
|
@@ -321,8 +321,8 @@ export class TiDBSessionStore {
|
|
|
321
321
|
}
|
|
322
322
|
}
|
|
323
323
|
async readStagedEntries(stagingId) {
|
|
324
|
-
const [rows] = await this.pool.query(
|
|
325
|
-
return rows.map((r) => (
|
|
324
|
+
const [rows] = await this.pool.query(`SELECT seq, ${jsonColumnText("tidb", "payload")} FROM session_event WHERE session_id = ? ORDER BY seq ASC`, [stagingId]);
|
|
325
|
+
return rows.map((r) => decodeSessionEventPayload(r.payload, stagingId, r.seq));
|
|
326
326
|
}
|
|
327
327
|
async sweepStagingSessions() {
|
|
328
328
|
const cutoff = new Date(Date.now() - STAGING_GC_GRACE_MS);
|
|
@@ -416,12 +416,13 @@ class TiDBStagingHandle {
|
|
|
416
416
|
throw new SyncConflictError(this.realSessionId, rel);
|
|
417
417
|
}
|
|
418
418
|
if (rel.relation === "fast_forward" && opts?.resolution !== "overwrite-dst" && dstIds !== null && dstIds.length > 0) {
|
|
419
|
-
const [dstFull] = await conn.query(
|
|
420
|
-
const [stagedFull] = await conn.query(
|
|
421
|
-
const parseEntry = (p) => (typeof p === "string" ? JSON.parse(p) : p);
|
|
419
|
+
const [dstFull] = await conn.query(`SELECT seq, ${jsonColumnText("tidb", "payload")} FROM session_event WHERE session_id = ? ORDER BY seq ASC`, [this.realSessionId]);
|
|
420
|
+
const [stagedFull] = await conn.query(`SELECT entry_id, ${jsonColumnText("tidb", "payload")} FROM session_event WHERE session_id = ? ORDER BY seq ASC`, [this.stagingId]);
|
|
422
421
|
const dstSet = new Set(dstIds);
|
|
423
|
-
const dstEntries = dstFull.map((r) =>
|
|
424
|
-
const stagedShared = stagedFull
|
|
422
|
+
const dstEntries = dstFull.map((r) => decodeSessionEventPayload(r.payload, this.realSessionId, r.seq));
|
|
423
|
+
const stagedShared = stagedFull
|
|
424
|
+
.filter((r) => dstSet.has(String(r.entry_id)))
|
|
425
|
+
.map((r) => decodeSessionEventPayload(r.payload, this.stagingId, r.entry_id));
|
|
425
426
|
const diverged = fastForwardSharedContentDiverged(stagedShared, dstEntries);
|
|
426
427
|
if (diverged) {
|
|
427
428
|
await conn.rollback();
|
|
@@ -430,13 +431,10 @@ class TiDBStagingHandle {
|
|
|
430
431
|
}
|
|
431
432
|
await conn.query("DELETE FROM session_event WHERE session_id = ?", [this.realSessionId]);
|
|
432
433
|
await conn.query("UPDATE session_event SET session_id = ? WHERE session_id = ?", [this.realSessionId, this.stagingId]);
|
|
433
|
-
const [lastRow] = await conn.query(
|
|
434
|
+
const [lastRow] = await conn.query(`SELECT ${jsonColumnText("tidb", "payload")} FROM session_event WHERE session_id = ? ORDER BY seq DESC LIMIT 1`, [this.realSessionId]);
|
|
434
435
|
let leafId = null;
|
|
435
|
-
if (lastRow[0])
|
|
436
|
-
|
|
437
|
-
const last = (typeof p === "string" ? JSON.parse(p) : p);
|
|
438
|
-
leafId = leafIdAfterEntry(last);
|
|
439
|
-
}
|
|
436
|
+
if (lastRow[0])
|
|
437
|
+
leafId = leafIdAfterEntry(decodeSessionEventPayload(lastRow[0].payload, this.realSessionId, "last staged row"));
|
|
440
438
|
const now = new Date();
|
|
441
439
|
await conn.query("INSERT INTO session_meta (session_id, owner, created_at, updated_at, leaf_id, leaf_seq) VALUES (?,?,?,?,?,?) " +
|
|
442
440
|
"ON DUPLICATE KEY UPDATE owner = VALUES(owner), updated_at = VALUES(updated_at), leaf_id = VALUES(leaf_id), leaf_seq = VALUES(leaf_seq)", [this.realSessionId, owner, now, now, leafId, srcIds.length]);
|
|
@@ -25,8 +25,16 @@
|
|
|
25
25
|
*/
|
|
26
26
|
import type { Pool as MySqlPool } from "mysql2/promise";
|
|
27
27
|
import type { Pool as PgPool } from "pg";
|
|
28
|
-
import type { UsageWindow, UsageWindowReading, UsageWindowStore } from "@sema-agent/core";
|
|
28
|
+
import type { UsageWindow, UsageWindowReading, UsageWindowRecord, UsageWindowStore } from "@sema-agent/core";
|
|
29
|
+
import { type JsonColumnShape } from "./sql-json-column.js";
|
|
29
30
|
export declare const USAGE_WINDOW_TABLE = "usage_window";
|
|
31
|
+
/**
|
|
32
|
+
* `usage_window.record` 的**形读者**(S-550 车③a:本店既有的那一份深度判据逐字搬进来,一个字没改判)。
|
|
33
|
+
*
|
|
34
|
+
* 判据深到内层元素:非数值 tokens 参与累加会得到 `"0oops"` ⇒ `exhausted:false` ⇒ **超额账户被判未耗尽**
|
|
35
|
+
* (与 core `FileUsageWindowStore` 的 validateSlots/validateBuckets 同轴)。
|
|
36
|
+
*/
|
|
37
|
+
export declare function readUsageWindowRecordBody(body: unknown): JsonColumnShape<UsageWindowRecord>;
|
|
30
38
|
export declare function ensureTiDBUsageWindowSchema(pool: MySqlPool): Promise<void>;
|
|
31
39
|
export declare function ensurePgUsageWindowSchema(q: (text: string, params?: unknown[]) => Promise<unknown>): Promise<void>;
|
|
32
40
|
export declare class TiDBUsageWindowStore implements UsageWindowStore {
|
|
@@ -1,18 +1,32 @@
|
|
|
1
1
|
import { EMPTY_USAGE_WINDOW_RECORD, chargeUsageRecord, readUsageRecord } from "@sema-agent/core";
|
|
2
|
+
import { decodeJsonColumn, foldJsonColumn, jsonColumnText } from "./sql-json-column.js";
|
|
2
3
|
export const USAGE_WINDOW_TABLE = "usage_window";
|
|
4
|
+
class UsageRecordShapeProblem extends Error {
|
|
5
|
+
}
|
|
6
|
+
export function readUsageWindowRecordBody(body) {
|
|
7
|
+
try {
|
|
8
|
+
return { value: validateUsageWindowRecord(body) };
|
|
9
|
+
}
|
|
10
|
+
catch (e) {
|
|
11
|
+
if (e instanceof UsageRecordShapeProblem)
|
|
12
|
+
return { problem: e.message };
|
|
13
|
+
throw e;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
3
16
|
function parseRecord(raw, key) {
|
|
4
|
-
let v;
|
|
5
17
|
try {
|
|
6
|
-
|
|
18
|
+
return decodeJsonColumn(raw, readUsageWindowRecordBody, `${USAGE_WINDOW_TABLE}.record for key "${key}"`);
|
|
7
19
|
}
|
|
8
20
|
catch (e) {
|
|
9
|
-
throw
|
|
21
|
+
throw foldJsonColumn(e, (m) => new Error(`${m} — refusing to mis-count a governance window`));
|
|
10
22
|
}
|
|
23
|
+
}
|
|
24
|
+
function validateUsageWindowRecord(v) {
|
|
11
25
|
const r = v;
|
|
12
26
|
if (typeof v !== "object" || v === null || !Array.isArray(r.slots) || !Array.isArray(r.buckets)) {
|
|
13
|
-
throw new
|
|
27
|
+
throw new UsageRecordShapeProblem("expected {slots[],buckets[]}");
|
|
14
28
|
}
|
|
15
|
-
const badShape = (detail) => new
|
|
29
|
+
const badShape = (detail) => new UsageRecordShapeProblem(detail);
|
|
16
30
|
const finite = (x) => typeof x === "number" && Number.isFinite(x);
|
|
17
31
|
const costKeys = (e) => {
|
|
18
32
|
if (e.costMicroUsd !== undefined && (typeof e.costMicroUsd !== "number" || !Number.isSafeInteger(e.costMicroUsd) || e.costMicroUsd < 0)) {
|
|
@@ -70,7 +84,7 @@ export class TiDBUsageWindowStore {
|
|
|
70
84
|
try {
|
|
71
85
|
await c.beginTransaction();
|
|
72
86
|
await c.query(`INSERT INTO ${USAGE_WINDOW_TABLE} (usage_key, record, updated_at_ms) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE usage_key = usage_key`, [key, JSON.stringify(EMPTY_USAGE_WINDOW_RECORD), at]);
|
|
73
|
-
const [rows] = (await c.query(`SELECT record FROM ${USAGE_WINDOW_TABLE} WHERE usage_key = ? FOR UPDATE`, [key]));
|
|
87
|
+
const [rows] = (await c.query(`SELECT ${jsonColumnText("tidb", "record")} FROM ${USAGE_WINDOW_TABLE} WHERE usage_key = ? FOR UPDATE`, [key]));
|
|
74
88
|
if (rows.length === 0) {
|
|
75
89
|
throw new Error(`usage_window row for key "${key}" vanished between ensure and lock — refusing to drop a governance charge`);
|
|
76
90
|
}
|
|
@@ -88,7 +102,7 @@ export class TiDBUsageWindowStore {
|
|
|
88
102
|
}
|
|
89
103
|
}
|
|
90
104
|
async read(key, windows, now) {
|
|
91
|
-
const [rows] = (await this.pool.query(`SELECT record FROM ${USAGE_WINDOW_TABLE} WHERE usage_key = ?`, [key]));
|
|
105
|
+
const [rows] = (await this.pool.query(`SELECT ${jsonColumnText("tidb", "record")} FROM ${USAGE_WINDOW_TABLE} WHERE usage_key = ?`, [key]));
|
|
92
106
|
const record = rows.length === 0 ? EMPTY_USAGE_WINDOW_RECORD : parseRecord(rows[0].record, key);
|
|
93
107
|
return readUsageRecord(record, windows, now);
|
|
94
108
|
}
|
|
@@ -107,7 +121,7 @@ export class PgUsageWindowStore {
|
|
|
107
121
|
JSON.stringify(EMPTY_USAGE_WINDOW_RECORD),
|
|
108
122
|
at,
|
|
109
123
|
]);
|
|
110
|
-
const { rows } = await c.query(`SELECT record FROM ${USAGE_WINDOW_TABLE} WHERE usage_key = $1 FOR UPDATE`, [key]);
|
|
124
|
+
const { rows } = await c.query(`SELECT ${jsonColumnText("pg", "record")} FROM ${USAGE_WINDOW_TABLE} WHERE usage_key = $1 FOR UPDATE`, [key]);
|
|
111
125
|
if (rows.length === 0) {
|
|
112
126
|
throw new Error(`usage_window row for key "${key}" vanished between ensure and lock — refusing to drop a governance charge`);
|
|
113
127
|
}
|
|
@@ -125,7 +139,7 @@ export class PgUsageWindowStore {
|
|
|
125
139
|
}
|
|
126
140
|
}
|
|
127
141
|
async read(key, windows, now) {
|
|
128
|
-
const { rows } = await this.pool.query(`SELECT record FROM ${USAGE_WINDOW_TABLE} WHERE usage_key = $1`, [key]);
|
|
142
|
+
const { rows } = await this.pool.query(`SELECT ${jsonColumnText("pg", "record")} FROM ${USAGE_WINDOW_TABLE} WHERE usage_key = $1`, [key]);
|
|
129
143
|
const record = rows.length === 0 ? EMPTY_USAGE_WINDOW_RECORD : parseRecord(rows[0].record, key);
|
|
130
144
|
return readUsageRecord(record, windows, now);
|
|
131
145
|
}
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import { callKeyOrdinal } from "@sema-agent/core";
|
|
2
2
|
import { assertJournalEntryFits } from "@sema-agent/core";
|
|
3
|
-
import {
|
|
3
|
+
import { journalEntryOfBody, journalRowPayloadOf, readJournalRowBody } from "../orchestration/workflow-journal-entry.js";
|
|
4
4
|
import { mysqlDriver, pgDriver } from "./sql-driver.js";
|
|
5
|
+
import { decodeJsonColumn, foldJsonColumn, jsonColumnText, jsonColumnTextExpr } from "./sql-json-column.js";
|
|
5
6
|
const DEFAULT_RESUME_CLAIM_TTL_MS = 60 * 60 * 1000;
|
|
7
|
+
function decodeJournalResult(cell, where) {
|
|
8
|
+
try {
|
|
9
|
+
return decodeJsonColumn(cell, readJournalRowBody, where);
|
|
10
|
+
}
|
|
11
|
+
catch (e) {
|
|
12
|
+
throw foldJsonColumn(e, (m) => new Error(`workflow journal: ${m} — refusing rather than replay a journal entry nobody can read`));
|
|
13
|
+
}
|
|
14
|
+
}
|
|
6
15
|
export class SqlWorkflowJournalStore {
|
|
7
16
|
db;
|
|
8
17
|
resumeClaimTtlMs;
|
|
@@ -21,8 +30,11 @@ export class SqlWorkflowJournalStore {
|
|
|
21
30
|
"ON CONFLICT (run_id, ordinal) DO UPDATE SET call_key = EXCLUDED.call_key, result = EXCLUDED.result"), [runId, callKeyOrdinal(entry.callKey), scope, entry.callKey, serialized, Date.now()]);
|
|
22
31
|
}
|
|
23
32
|
async load(runId, scope) {
|
|
24
|
-
const { rows } = await this.db.query(this.q(
|
|
25
|
-
return rows.map((r) =>
|
|
33
|
+
const { rows } = await this.db.query(this.q(`SELECT call_key, ${jsonColumnText("tidb", "result")} FROM workflow_journal WHERE run_id = ? AND scope = ? ORDER BY ordinal ASC`, `SELECT call_key, ${jsonColumnText("pg", "result")} FROM workflow_journal WHERE run_id = $1 AND scope = $2 ORDER BY ordinal ASC`), [runId, scope]);
|
|
34
|
+
return rows.map((r) => {
|
|
35
|
+
const callKey = String(r.call_key);
|
|
36
|
+
return journalEntryOfBody(callKey, decodeJournalResult(r.result, `workflow_journal.result for run ${runId} entry ${callKey}`));
|
|
37
|
+
});
|
|
26
38
|
}
|
|
27
39
|
locator(runId, _scope) {
|
|
28
40
|
return `/v1/workflows/${runId}/journal`;
|
|
@@ -31,7 +43,7 @@ export class SqlWorkflowJournalStore {
|
|
|
31
43
|
const params = this.db.dialect === "tidb"
|
|
32
44
|
? [opts.maxResultBytes, runId, scope, opts.limit, opts.offset]
|
|
33
45
|
: [runId, scope, opts.maxResultBytes, opts.limit, opts.offset];
|
|
34
|
-
const { rows } = await this.db.query(this.q(
|
|
46
|
+
const { rows } = await this.db.query(this.q(`SELECT call_key, CASE WHEN LENGTH(result) > ? THEN NULL ELSE ${jsonColumnTextExpr("tidb", "result")} END AS result_json, LENGTH(result) AS result_bytes FROM workflow_journal WHERE run_id = ? AND scope = ? ORDER BY ordinal ASC LIMIT ? OFFSET ?`, `SELECT call_key, CASE WHEN OCTET_LENGTH(result) > $3 THEN NULL ELSE ${jsonColumnTextExpr("pg", "result")} END AS result_json, OCTET_LENGTH(result) AS result_bytes FROM workflow_journal WHERE run_id = $1 AND scope = $2 ORDER BY ordinal ASC LIMIT $4 OFFSET $5`), params);
|
|
35
47
|
return rows.map((r) => ({ callKey: String(r.call_key), resultJson: r.result_json === null ? null : String(r.result_json), resultBytes: Number(r.result_bytes) }));
|
|
36
48
|
}
|
|
37
49
|
async deleteByRun(runId) {
|
package/dist/run-local.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { DEFAULT_MODEL_REF } from "./model-select.js";
|
|
3
|
+
import { buildDurableAbsenceNote, createCorruptReadSeat } from "./observability/corrupt-read-seat.js";
|
|
3
4
|
import { outwardPlane } from "./terminal.js";
|
|
4
5
|
import { homedir } from "node:os";
|
|
5
6
|
import { join } from "node:path";
|
|
@@ -17,6 +18,7 @@ import { taskWallClockSec } from "./task-workflow.js";
|
|
|
17
18
|
import { applyCatalogToSource } from "./capabilities/center-prompts.js";
|
|
18
19
|
import { validatePromptsDomain, CORE_ENGINE_VERSION } from "./prompts-domain-validate.js";
|
|
19
20
|
import { buildUnpricedCeilingNotice, loadConfig, logConfigDiagnostics, parseNumOrFailNonNegative } from "./config.js";
|
|
21
|
+
import { clearHostedEvidenceEnv } from "./hosted-posture.js";
|
|
20
22
|
import { buildStoreOpenRefusalTail } from "./plugins/store-open-refusal.js";
|
|
21
23
|
import { acquireLocalBackendWithWait } from "./plugins/store-lock-wait.js";
|
|
22
24
|
import { createConfigProvider } from "./config-provider.js";
|
|
@@ -175,6 +177,7 @@ export async function runLocal(argv, deps = {}) {
|
|
|
175
177
|
process.env.CONFIG_PROVIDER = "local";
|
|
176
178
|
process.env.SESSION_BACKEND = "memory";
|
|
177
179
|
process.env.REQUIRE_PRINCIPAL = "false";
|
|
180
|
+
clearHostedEvidenceEnv();
|
|
178
181
|
const root = args.root ?? process.env.CONFIG_LOCAL_DIR ?? process.env.AGENT_DATA_DIR ?? join(homedir(), ".ai-agent");
|
|
179
182
|
if (args.root)
|
|
180
183
|
process.env.CONFIG_LOCAL_DIR = args.root;
|
|
@@ -260,16 +263,7 @@ export async function runLocal(argv, deps = {}) {
|
|
|
260
263
|
fileBackend = acquireLocalBackendWithWait(() => new FileStorageBackend({
|
|
261
264
|
root,
|
|
262
265
|
...(config.fileHistory !== undefined ? { fileHistory: config.fileHistory } : {}),
|
|
263
|
-
onCorruptRead: (
|
|
264
|
-
path: info.path,
|
|
265
|
-
reason: info.reason,
|
|
266
|
-
...(info.sessionId !== undefined ? { sessionId: info.sessionId } : {}),
|
|
267
|
-
...(info.principal !== undefined ? { principal: info.principal } : {}),
|
|
268
|
-
note: "a durable read was treated as ABSENT because the bytes were unreadable (never a plain ENOENT). " +
|
|
269
|
-
"Consequence depends on which store read it: session-policy = this run lost its SUBTRACTIVE session-rule " +
|
|
270
|
-
"layer (deployment policy / hooks / shell gate still applied); session repo = a listing silently omitted " +
|
|
271
|
-
"rows; file-snapshot = a snapshot or scope read as missing. Inspect the named path before deciding.",
|
|
272
|
-
}),
|
|
266
|
+
onCorruptRead: createCorruptReadSeat(logger.warn.bind(logger), "file_store_corrupt_read", buildDurableAbsenceNote("file-snapshot = a snapshot or scope read as missing")),
|
|
273
267
|
}), config.storeLockWaitMs, (fields) => logger.warn("store_dir_lock_wait", { ...fields }));
|
|
274
268
|
}
|
|
275
269
|
catch (e) {
|
|
@@ -93,7 +93,20 @@ export type UsageDimension = "principal" | "model";
|
|
|
93
93
|
export declare function usageBreakdown(rows: UsageRow[], dimension: UsageDimension): Array<{
|
|
94
94
|
key: string;
|
|
95
95
|
} & UsageTotals>;
|
|
96
|
-
/**
|
|
96
|
+
/**
|
|
97
|
+
* store 行投影 helper:**已解码的** result 体 → UsageRow.stats。三个后端复用(方言只在 SQL)。
|
|
98
|
+
*
|
|
99
|
+
* 🔴 **本函数不解码**(S-550 车③b 删掉了它的 `typeof result === "string" ? JSON.parse(result) : result`
|
|
100
|
+
* 那一跳):那正是设计稿点名的歧义臂 ——「体是串就再解一次」在一列真 `JSON`/`JSONB` 上等于**解第二次**,
|
|
101
|
+
* 于是一个双重编码的 `task_run.result`(解一次得到的是串)会被解回好体、当成真的用量端出去。三个调用点
|
|
102
|
+
* 逐行亲核过,今天**一处都不递串**:
|
|
103
|
+
* · `plugins/run-store-sql.ts#usageScan` → `usageResultBody(r.result)`(属主按文本投影 + `readJsonObjectColumn`
|
|
104
|
+
* 判形,交出来的是对象或 `undefined`;串在那只读者上就是坏体);
|
|
105
|
+
* · `plugins/file-run-store.ts#usageScan` / `plugins/memory-run-store.ts#usageScan` → 行上的
|
|
106
|
+
* `result: PersistedTaskResult | null`(File 形由 core 的 `readJsonlRecords` 解,内存形本来就是对象)。
|
|
107
|
+
* ⇒ 解码的属主是**店**(列 / 文件 / 内存三种承载各自一个),不是这只聚合投影。递进来一个串不会再被
|
|
108
|
+
* 「宽容地」解开,它会在下面那条「`stats` 不是对象 ⇒ `undefined`」的臂上落成诚实的降级。
|
|
109
|
+
*/
|
|
97
110
|
export declare function projectUsageStats(result: unknown): UsageRow["stats"];
|
|
98
111
|
/** usageScan 的统一行数上限(触顶=结果 truncated:true;试点规模远在限内)。 */
|
|
99
112
|
export declare const USAGE_SCAN_LIMIT = 50000;
|
package/dist/usage-analytics.js
CHANGED
|
@@ -87,24 +87,18 @@ export function usageBreakdown(rows, dimension) {
|
|
|
87
87
|
.map(([key, t]) => ({ key, ...finish(t) }));
|
|
88
88
|
}
|
|
89
89
|
export function projectUsageStats(result) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const stats = parsed?.stats;
|
|
93
|
-
if (!stats || typeof stats !== "object")
|
|
94
|
-
return undefined;
|
|
95
|
-
const out = {};
|
|
96
|
-
if (typeof stats.tokens === "number")
|
|
97
|
-
out.tokens = stats.tokens;
|
|
98
|
-
const mu = stats.modelUsage;
|
|
99
|
-
if (mu && typeof mu === "object" && !Array.isArray(mu))
|
|
100
|
-
out.modelUsage = mu;
|
|
101
|
-
if (stats.usageMissing === true)
|
|
102
|
-
out.usageMissing = true;
|
|
103
|
-
return out;
|
|
104
|
-
}
|
|
105
|
-
catch {
|
|
90
|
+
const stats = result?.stats;
|
|
91
|
+
if (!stats || typeof stats !== "object")
|
|
106
92
|
return undefined;
|
|
107
|
-
}
|
|
93
|
+
const out = {};
|
|
94
|
+
if (typeof stats.tokens === "number")
|
|
95
|
+
out.tokens = stats.tokens;
|
|
96
|
+
const mu = stats.modelUsage;
|
|
97
|
+
if (mu && typeof mu === "object" && !Array.isArray(mu))
|
|
98
|
+
out.modelUsage = mu;
|
|
99
|
+
if (stats.usageMissing === true)
|
|
100
|
+
out.usageMissing = true;
|
|
101
|
+
return out;
|
|
108
102
|
}
|
|
109
103
|
export const USAGE_SCAN_LIMIT = 50_000;
|
|
110
104
|
//# sourceMappingURL=usage-analytics.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/server",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.95.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",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"build:binary:run-local:darwin-arm64": "bun build --compile --target=bun-darwin-arm64 src/run-local.ts --outfile dist/run-local-darwin-arm64"
|
|
60
60
|
},
|
|
61
61
|
"dependencies": {
|
|
62
|
-
"@sema-agent/core": "7.
|
|
62
|
+
"@sema-agent/core": "7.28.1",
|
|
63
63
|
"@sema-agent/settings-schema": "3.0.0",
|
|
64
64
|
"e2b": "^2.28.0",
|
|
65
65
|
"libsodium-wrappers": "^0.8.4",
|