@sema-agent/server 1.284.0 → 1.285.1
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/README.md +10 -0
- package/dist/plugins/background-agent-store-sql.js +5 -15
- package/dist/plugins/pg-approval-store.js +3 -1
- package/dist/plugins/pg-outcome-ledger.js +3 -1
- package/dist/plugins/pg-pool.js +25 -6
- package/dist/plugins/pg-session-storage.js +2 -1
- package/dist/plugins/roster-store-sql.js +6 -16
- package/dist/plugins/tidb-pool.js +90 -68
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -77,6 +77,16 @@ model gateways, and cloud agent execution behind an HTTP/SSE contract.
|
|
|
77
77
|
|
|
78
78
|
Requirements: Node ≥ 20 (npm path) and an OpenAI-compatible model gateway.
|
|
79
79
|
|
|
80
|
+
> **A note on the `glob@11` deprecation warning at install time.** `npm install` prints a deprecation
|
|
81
|
+
> warning for `glob@11.1.0`, pulled in transitively by `e2b` (the E2B sandbox SDK). It is **install-time
|
|
82
|
+
> noise with no runtime exposure here**: `glob` has exactly one load site inside `e2b` — a `dynamicImport`
|
|
83
|
+
> in its *template-build* file-packing path — and this server only ever touches E2B's *sandbox runtime*
|
|
84
|
+
> API. Verified by execution, not by reading: importing `e2b`, constructing the adapter and driving a real
|
|
85
|
+
> `exec` never puts `glob` in the module cache. We cannot silence it for you — npm `overrides` only apply
|
|
86
|
+
> when the package.json being read *is the project npm was invoked on*, so ours is ignored when this
|
|
87
|
+
> package is installed as a dependency. If the warning bothers you, add `"overrides": { "glob": "^13" }`
|
|
88
|
+
> to **your own** project's package.json (that is the one npm reads); the real fix is upstream in `e2b`.
|
|
89
|
+
|
|
80
90
|
```bash
|
|
81
91
|
# A) npm
|
|
82
92
|
npm install @sema-agent/server
|
|
@@ -105,6 +105,7 @@ export async function ensureTiDBBackgroundAgentSchema(pool) {
|
|
|
105
105
|
session_scoped TINYINT NOT NULL,
|
|
106
106
|
session_id VARCHAR(190) NULL,
|
|
107
107
|
parent_session_id VARCHAR(190) NULL,
|
|
108
|
+
-- core 1.367 δ(additive):root 锚(listBySession 第二臂;NULL = 该记录无 root 锚,谓词天然拒)
|
|
108
109
|
root_session_id VARCHAR(190) NULL,
|
|
109
110
|
session_anchor VARCHAR(190) NULL,
|
|
110
111
|
name VARCHAR(255) NULL,
|
|
@@ -115,6 +116,8 @@ export async function ensureTiDBBackgroundAgentSchema(pool) {
|
|
|
115
116
|
settled_at_ms BIGINT NULL,
|
|
116
117
|
usage_json TEXT NULL,
|
|
117
118
|
rev BIGINT NOT NULL,
|
|
119
|
+
-- core 1.383([1565] / design/153 件3a)ε:updateIf 的 CAS 守卫**专用列**——守卫必须在库侧对当前
|
|
120
|
+
-- 行状态原子求值,不能只靠 record_json blob(那要求先读后写,读写之间就是 CAS 本要堵的竞态窗)。
|
|
118
121
|
parked_checkpoint_token VARCHAR(190) NULL,
|
|
119
122
|
park_claim_id VARCHAR(190) NULL,
|
|
120
123
|
record_json LONGTEXT NOT NULL,
|
|
@@ -122,18 +125,6 @@ export async function ensureTiDBBackgroundAgentSchema(pool) {
|
|
|
122
125
|
KEY idx_bga_anchor (scope_key, session_anchor, spawned_at_ms),
|
|
123
126
|
KEY idx_bga_status (scope_key, status, updated_at_ms)
|
|
124
127
|
) COLLATE utf8mb4_bin`);
|
|
125
|
-
const [rootCol] = (await pool.query(`SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = 'root_session_id'`, [BACKGROUND_AGENT_TABLE]));
|
|
126
|
-
if (rootCol.length === 0) {
|
|
127
|
-
await pool.query(`ALTER TABLE ${BACKGROUND_AGENT_TABLE} ADD COLUMN root_session_id VARCHAR(190) NULL AFTER parent_session_id`);
|
|
128
|
-
}
|
|
129
|
-
const [parkCols] = (await pool.query(`SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME IN ('parked_checkpoint_token', 'park_claim_id')`, [BACKGROUND_AGENT_TABLE]));
|
|
130
|
-
const existingParkCols = new Set(parkCols.map((r) => r.COLUMN_NAME));
|
|
131
|
-
if (!existingParkCols.has("parked_checkpoint_token")) {
|
|
132
|
-
await pool.query(`ALTER TABLE ${BACKGROUND_AGENT_TABLE} ADD COLUMN parked_checkpoint_token VARCHAR(190) NULL`);
|
|
133
|
-
}
|
|
134
|
-
if (!existingParkCols.has("park_claim_id")) {
|
|
135
|
-
await pool.query(`ALTER TABLE ${BACKGROUND_AGENT_TABLE} ADD COLUMN park_claim_id VARCHAR(190) NULL`);
|
|
136
|
-
}
|
|
137
128
|
}
|
|
138
129
|
export async function ensurePgBackgroundAgentSchema(q) {
|
|
139
130
|
await q(`CREATE TABLE IF NOT EXISTS ${BACKGROUND_AGENT_TABLE} (
|
|
@@ -144,6 +135,7 @@ export async function ensurePgBackgroundAgentSchema(q) {
|
|
|
144
135
|
session_scoped BOOLEAN NOT NULL,
|
|
145
136
|
session_id VARCHAR(190),
|
|
146
137
|
parent_session_id VARCHAR(190),
|
|
138
|
+
-- core 1.367 δ(additive,TiDB 同案):root 锚(listBySession 第二臂)
|
|
147
139
|
root_session_id VARCHAR(190),
|
|
148
140
|
session_anchor VARCHAR(190),
|
|
149
141
|
name VARCHAR(255),
|
|
@@ -154,6 +146,7 @@ export async function ensurePgBackgroundAgentSchema(q) {
|
|
|
154
146
|
settled_at_ms BIGINT,
|
|
155
147
|
usage_json TEXT,
|
|
156
148
|
rev BIGINT NOT NULL,
|
|
149
|
+
-- core 1.383([1565] / design/153 件3a)ε(TiDB 同案注):updateIf 的 CAS 守卫专用列
|
|
157
150
|
parked_checkpoint_token VARCHAR(190),
|
|
158
151
|
park_claim_id VARCHAR(190),
|
|
159
152
|
record_json TEXT NOT NULL,
|
|
@@ -161,9 +154,6 @@ export async function ensurePgBackgroundAgentSchema(q) {
|
|
|
161
154
|
)`);
|
|
162
155
|
await q(`CREATE INDEX IF NOT EXISTS idx_bga_anchor ON ${BACKGROUND_AGENT_TABLE} (scope_key, session_anchor, spawned_at_ms)`);
|
|
163
156
|
await q(`CREATE INDEX IF NOT EXISTS idx_bga_status ON ${BACKGROUND_AGENT_TABLE} (scope_key, status, updated_at_ms)`);
|
|
164
|
-
await q(`ALTER TABLE ${BACKGROUND_AGENT_TABLE} ADD COLUMN IF NOT EXISTS root_session_id VARCHAR(190)`);
|
|
165
|
-
await q(`ALTER TABLE ${BACKGROUND_AGENT_TABLE} ADD COLUMN IF NOT EXISTS parked_checkpoint_token VARCHAR(190)`);
|
|
166
|
-
await q(`ALTER TABLE ${BACKGROUND_AGENT_TABLE} ADD COLUMN IF NOT EXISTS park_claim_id VARCHAR(190)`);
|
|
167
157
|
}
|
|
168
158
|
export class TiDBBackgroundAgentStore {
|
|
169
159
|
pool;
|
|
@@ -6,6 +6,9 @@ export const PG_APPROVAL_SCHEMA = [
|
|
|
6
6
|
task_id VARCHAR(64),
|
|
7
7
|
session_id VARCHAR(64),
|
|
8
8
|
owner VARCHAR(190),
|
|
9
|
+
-- Single-DB fleet scope guard (tenant isolation). NULLABLE on purpose: a NULL-scope row is an untenanted
|
|
10
|
+
-- run, and every read/decide path matches it with \`scope IS NOT DISTINCT FROM $n\` (the PG twin of TiDB
|
|
11
|
+
-- \`<=>\`) so NULL matches only NULL and one tenant's scope NEVER matches another's.
|
|
9
12
|
scope VARCHAR(190),
|
|
10
13
|
tool_name VARCHAR(190) NOT NULL,
|
|
11
14
|
args JSONB,
|
|
@@ -16,7 +19,6 @@ export const PG_APPROVAL_SCHEMA = [
|
|
|
16
19
|
decided_at TIMESTAMPTZ(3),
|
|
17
20
|
PRIMARY KEY (id)
|
|
18
21
|
)`,
|
|
19
|
-
`ALTER TABLE approval ADD COLUMN IF NOT EXISTS scope VARCHAR(190)`,
|
|
20
22
|
`CREATE INDEX IF NOT EXISTS idx_approval_owner_status ON approval (owner, status)`,
|
|
21
23
|
`CREATE INDEX IF NOT EXISTS idx_approval_status ON approval (status)`,
|
|
22
24
|
`CREATE INDEX IF NOT EXISTS idx_approval_scope_status ON approval (scope, status)`,
|
|
@@ -22,11 +22,13 @@ export const PG_OUTCOME_LEDGER_SCHEMA = [
|
|
|
22
22
|
outcome VARCHAR(8),
|
|
23
23
|
llm_assisted JSONB,
|
|
24
24
|
signature_inputs JSONB,
|
|
25
|
+
-- design/73 §1 bridge: the VERBATIM core TaskOutcome fact (oracleHadRedRun / status / oracle blob) as it
|
|
26
|
+
-- came off core, kept alongside the derived mechanical columns. NULL when the caller recorded through the
|
|
27
|
+
-- non-core \`record()\` path (no core fact to bridge).
|
|
25
28
|
core_outcome JSONB,
|
|
26
29
|
created_at TIMESTAMPTZ(3) NOT NULL,
|
|
27
30
|
PRIMARY KEY (id)
|
|
28
31
|
)`,
|
|
29
|
-
`ALTER TABLE outcome_ledger ADD COLUMN IF NOT EXISTS core_outcome JSONB`,
|
|
30
32
|
`CREATE INDEX IF NOT EXISTS idx_outcome_ledger_sig_model ON outcome_ledger (task_signature, model, run_status)`,
|
|
31
33
|
`CREATE INDEX IF NOT EXISTS idx_outcome_ledger_outcome ON outcome_ledger (outcome)`,
|
|
32
34
|
`CREATE INDEX IF NOT EXISTS idx_outcome_ledger_created ON outcome_ledger (created_at)`,
|
package/dist/plugins/pg-pool.js
CHANGED
|
@@ -111,6 +111,9 @@ export const PG_SCHEMA_STATEMENTS = [
|
|
|
111
111
|
`CREATE TABLE IF NOT EXISTS session_policy (
|
|
112
112
|
policy_key CHAR(64) NOT NULL,
|
|
113
113
|
session_id VARCHAR(64) NOT NULL,
|
|
114
|
+
-- 2c DENORMALIZED principal, carried solely so listBySession can ENUMERATE: policy_key is a one-way
|
|
115
|
+
-- sha256, so the principal is not recoverable from it. Enumeration-only — it is NOT part of the key and
|
|
116
|
+
-- NOT part of the CAS / tighten-only path. NULL = a session-wide (principal-less) policy row.
|
|
114
117
|
principal VARCHAR(190),
|
|
115
118
|
rules JSONB NOT NULL,
|
|
116
119
|
rev BIGINT NOT NULL,
|
|
@@ -118,7 +121,6 @@ export const PG_SCHEMA_STATEMENTS = [
|
|
|
118
121
|
updated_at TIMESTAMPTZ(3) NOT NULL,
|
|
119
122
|
PRIMARY KEY (policy_key)
|
|
120
123
|
)`,
|
|
121
|
-
`ALTER TABLE session_policy ADD COLUMN IF NOT EXISTS principal VARCHAR(190)`,
|
|
122
124
|
`CREATE INDEX IF NOT EXISTS idx_session_policy_session ON session_policy (session_id)`,
|
|
123
125
|
`CREATE TABLE IF NOT EXISTS snapshot_manifest (
|
|
124
126
|
scope VARCHAR(64) NOT NULL,
|
|
@@ -131,21 +133,30 @@ export const PG_SCHEMA_STATEMENTS = [
|
|
|
131
133
|
`CREATE TABLE IF NOT EXISTS snapshot_blob (
|
|
132
134
|
blob_hash CHAR(64) PRIMARY KEY,
|
|
133
135
|
byte_len BIGINT NOT NULL,
|
|
136
|
+
-- \`bytes\` is NULLABLE and that nullability is LOAD-BEARING, not laxness: a SQL-backend row carries the
|
|
137
|
+
-- bytes inline; a MinIO-backend row keeps ONLY the INDEX (blob_hash, byte_len, created_at) here with
|
|
138
|
+
-- bytes NULL — the bytes themselves live in MinIO. The orphan/grace reference-tracking (gcOrphanBlobs /
|
|
139
|
+
-- reap / deleteBySession / sweepOrphanBlobs) reads snapshot_blob, so it works IDENTICALLY for BOTH
|
|
140
|
+
-- backends (and an E21 purge can delete the MinIO object). See blob-backend.ts MinioBlobBackend.
|
|
141
|
+
-- ⇒ NEVER re-add NOT NULL here: it would make the MinIO-backend index-only row un-insertable.
|
|
134
142
|
bytes BYTEA NULL,
|
|
135
143
|
created_at TIMESTAMPTZ(3) NOT NULL
|
|
136
144
|
)`,
|
|
137
145
|
`CREATE INDEX IF NOT EXISTS idx_snapshot_blob_created ON snapshot_blob (created_at)`,
|
|
138
|
-
`ALTER TABLE snapshot_blob ALTER COLUMN bytes DROP NOT NULL`,
|
|
139
146
|
`CREATE TABLE IF NOT EXISTS workflow_journal (
|
|
140
147
|
run_id VARCHAR(191) NOT NULL,
|
|
141
148
|
ordinal INT NOT NULL,
|
|
149
|
+
-- SVC-2 / CORE-9 audit BLOCKER: cross-tenant resume isolation (the WorkflowJournalStore seam takes a scope
|
|
150
|
+
-- param). NOT NULL DEFAULT '' — a row written without a scope lands in the '' bucket, which is
|
|
151
|
+
-- CROSS-SCOPE-ORPHANED but harmless: a real resume always filters \`WHERE scope = <caller>\`, so '' rows are
|
|
152
|
+
-- never handed to a tenant. Keep the DEFAULT: it is what makes a scope-less write land in that dead bucket
|
|
153
|
+
-- instead of failing or leaking into a real tenant's scope.
|
|
142
154
|
scope VARCHAR(190) NOT NULL DEFAULT '',
|
|
143
155
|
call_key VARCHAR(255) NOT NULL,
|
|
144
156
|
result TEXT NOT NULL,
|
|
145
157
|
created_at BIGINT NOT NULL,
|
|
146
158
|
PRIMARY KEY (run_id, ordinal)
|
|
147
159
|
)`,
|
|
148
|
-
`ALTER TABLE workflow_journal ADD COLUMN IF NOT EXISTS scope VARCHAR(190) NOT NULL DEFAULT ''`,
|
|
149
160
|
`CREATE TABLE IF NOT EXISTS workflow_run (
|
|
150
161
|
id VARCHAR(191) NOT NULL,
|
|
151
162
|
scope VARCHAR(190) NOT NULL,
|
|
@@ -166,13 +177,21 @@ export const PG_SCHEMA_STATEMENTS = [
|
|
|
166
177
|
status VARCHAR(16) NOT NULL,
|
|
167
178
|
summary TEXT NOT NULL,
|
|
168
179
|
enqueued_at BIGINT NOT NULL,
|
|
180
|
+
-- 1.109 delivery envelope. kind NULL = a LEGACY \`workflow_complete\` row (the shape that predates the
|
|
181
|
+
-- envelope); readers must keep treating NULL as that kind, it is not "unknown". payload = the kind's
|
|
182
|
+
-- body, NULL for the legacy shape.
|
|
169
183
|
kind VARCHAR(24),
|
|
170
184
|
payload TEXT,
|
|
171
|
-
PRIMARY KEY (session_id, run_id)
|
|
185
|
+
PRIMARY KEY (session_id, run_id),
|
|
186
|
+
-- Twin-alignment with TiDB's \`UNIQUE KEY uq_wfinbox_seq (seq)\` (same constraint name on purpose, so the two
|
|
187
|
+
-- schemas can be diffed by name). This is NOT a correctness fix: seq is BIGSERIAL, so it is already unique in
|
|
188
|
+
-- practice, and the only seq-keyed write — the overflow trim \`DELETE … WHERE session_id = $1 AND seq = ANY(…)\`
|
|
189
|
+
-- — is fenced by its session_id predicate, so a hypothetical duplicate seq could not cross sessions anyway.
|
|
190
|
+
-- What it buys: the implicit "BIGSERIAL is unique" assumption behind \`ORDER BY seq\` (the inbox's total order)
|
|
191
|
+
-- becomes MACHINE-ENFORCED rather than merely conventional, in both engines.
|
|
192
|
+
CONSTRAINT uq_wfinbox_seq UNIQUE (seq)
|
|
172
193
|
)`,
|
|
173
194
|
`CREATE INDEX IF NOT EXISTS idx_wfinbox_session_seq ON workflow_completion_inbox (session_id, seq)`,
|
|
174
|
-
`ALTER TABLE workflow_completion_inbox ADD COLUMN IF NOT EXISTS kind VARCHAR(24)`,
|
|
175
|
-
`ALTER TABLE workflow_completion_inbox ADD COLUMN IF NOT EXISTS payload TEXT`,
|
|
176
195
|
`CREATE TABLE IF NOT EXISTS workflow_inbox_fence (
|
|
177
196
|
session_id VARCHAR(190) NOT NULL,
|
|
178
197
|
kind VARCHAR(8) NOT NULL,
|
|
@@ -23,11 +23,12 @@ export const PG_SESSION_SCHEMA = [
|
|
|
23
23
|
updated_at TIMESTAMPTZ(3) NOT NULL,
|
|
24
24
|
leaf_id VARCHAR(64),
|
|
25
25
|
leaf_seq BIGINT NOT NULL DEFAULT 0,
|
|
26
|
+
-- Write-once auto-title (setTitleIfNull / probeTitle): NULL = "untitled", i.e. still claimable; the
|
|
27
|
+
-- three-state probe distinguishes it from "no such session". PG twin of the TiDB COLUMN_MIGRATIONS leg.
|
|
26
28
|
title VARCHAR(120),
|
|
27
29
|
PRIMARY KEY (session_id)
|
|
28
30
|
)`,
|
|
29
31
|
`CREATE INDEX IF NOT EXISTS idx_session_meta_owner ON session_meta (owner)`,
|
|
30
|
-
`ALTER TABLE session_meta ADD COLUMN IF NOT EXISTS title VARCHAR(120)`,
|
|
31
32
|
`CREATE TABLE IF NOT EXISTS session_event (
|
|
32
33
|
session_id VARCHAR(64) NOT NULL,
|
|
33
34
|
seq BIGINT NOT NULL,
|
|
@@ -27,27 +27,19 @@ export async function ensureTiDBRosterSchema(pool) {
|
|
|
27
27
|
agent_id VARCHAR(190) NOT NULL,
|
|
28
28
|
session_id VARCHAR(190) NULL,
|
|
29
29
|
tool_use_id VARCHAR(190) NULL,
|
|
30
|
+
-- owner/scope:core 1.365 BREAKING 双轴必填(default-deny 谓词的两根轴;顶注 ① 记了曾 nullable 的由来)
|
|
30
31
|
owner VARCHAR(190) NOT NULL,
|
|
31
32
|
scope VARCHAR(190) NOT NULL,
|
|
32
33
|
session_scoped TINYINT NULL,
|
|
34
|
+
-- core 1.367 δ(additive):RosterEntry.rootSessionId verbatim 存(枚举/恢复读者;谓词暂无臂)
|
|
33
35
|
root_session_id VARCHAR(190) NULL,
|
|
36
|
+
-- core 1.373 additive:spawn 记录的解析后模型 id(NULL = 该 spawn 未记录模型)
|
|
34
37
|
model VARCHAR(190) NULL,
|
|
35
38
|
created_at_ms BIGINT NOT NULL,
|
|
36
39
|
recorded_at_ms BIGINT NOT NULL,
|
|
37
40
|
PRIMARY KEY (name_key, owner_key, scope_key),
|
|
38
41
|
KEY idx_roster_agent (agent_id)
|
|
39
42
|
) COLLATE utf8mb4_bin`);
|
|
40
|
-
await pool.query(`DELETE FROM ${ROSTER_TABLE} WHERE owner IS NULL OR scope IS NULL`);
|
|
41
|
-
await pool.query(`ALTER TABLE ${ROSTER_TABLE} MODIFY COLUMN owner VARCHAR(190) NOT NULL`);
|
|
42
|
-
await pool.query(`ALTER TABLE ${ROSTER_TABLE} MODIFY COLUMN scope VARCHAR(190) NOT NULL`);
|
|
43
|
-
const [rootCol] = (await pool.query(`SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = 'root_session_id'`, [ROSTER_TABLE]));
|
|
44
|
-
if (rootCol.length === 0) {
|
|
45
|
-
await pool.query(`ALTER TABLE ${ROSTER_TABLE} ADD COLUMN root_session_id VARCHAR(190) NULL AFTER session_scoped`);
|
|
46
|
-
}
|
|
47
|
-
const [modelCol] = (await pool.query(`SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = 'model'`, [ROSTER_TABLE]));
|
|
48
|
-
if (modelCol.length === 0) {
|
|
49
|
-
await pool.query(`ALTER TABLE ${ROSTER_TABLE} ADD COLUMN model VARCHAR(190) NULL AFTER root_session_id`);
|
|
50
|
-
}
|
|
51
43
|
}
|
|
52
44
|
export async function ensurePgRosterSchema(q) {
|
|
53
45
|
await q(`CREATE TABLE IF NOT EXISTS ${ROSTER_TABLE} (
|
|
@@ -58,21 +50,19 @@ export async function ensurePgRosterSchema(q) {
|
|
|
58
50
|
agent_id VARCHAR(190) NOT NULL,
|
|
59
51
|
session_id VARCHAR(190),
|
|
60
52
|
tool_use_id VARCHAR(190),
|
|
53
|
+
-- owner/scope:core 1.365 BREAKING 双轴必填(TiDB 同案;曾 nullable 的由来见两 ensure 上方顶注 ①)
|
|
61
54
|
owner VARCHAR(190) COLLATE "C" NOT NULL,
|
|
62
55
|
scope VARCHAR(190) COLLATE "C" NOT NULL,
|
|
63
56
|
session_scoped BOOLEAN,
|
|
57
|
+
-- core 1.367 δ(additive,TiDB 同案):RosterEntry.rootSessionId verbatim 存
|
|
64
58
|
root_session_id VARCHAR(190),
|
|
59
|
+
-- core 1.373 additive(TiDB 同案):解析后模型 id(NULL = 该 spawn 未记录模型)
|
|
65
60
|
model VARCHAR(190),
|
|
66
61
|
created_at_ms BIGINT NOT NULL,
|
|
67
62
|
recorded_at_ms BIGINT NOT NULL,
|
|
68
63
|
PRIMARY KEY (name_key, owner_key, scope_key)
|
|
69
64
|
)`);
|
|
70
65
|
await q(`CREATE INDEX IF NOT EXISTS idx_roster_agent ON ${ROSTER_TABLE} (agent_id)`);
|
|
71
|
-
await q(`DELETE FROM ${ROSTER_TABLE} WHERE owner IS NULL OR scope IS NULL`);
|
|
72
|
-
await q(`ALTER TABLE ${ROSTER_TABLE} ALTER COLUMN owner SET NOT NULL`);
|
|
73
|
-
await q(`ALTER TABLE ${ROSTER_TABLE} ALTER COLUMN scope SET NOT NULL`);
|
|
74
|
-
await q(`ALTER TABLE ${ROSTER_TABLE} ADD COLUMN IF NOT EXISTS root_session_id VARCHAR(190)`);
|
|
75
|
-
await q(`ALTER TABLE ${ROSTER_TABLE} ADD COLUMN IF NOT EXISTS model VARCHAR(190)`);
|
|
76
66
|
}
|
|
77
67
|
function upsertParams(entry, now) {
|
|
78
68
|
return [
|
|
@@ -33,6 +33,8 @@ export const SCHEMA_STATEMENTS = [
|
|
|
33
33
|
updated_at DATETIME(3) NOT NULL,
|
|
34
34
|
leaf_id VARCHAR(64) NULL,
|
|
35
35
|
leaf_seq BIGINT NOT NULL DEFAULT 0,
|
|
36
|
+
-- title: auto-generated session title (cheap-model one-liner at first submit; write-once via
|
|
37
|
+
-- setTitleIfNull — the user-facing RENAME layer lives BFF-side as a label overlay, not here).
|
|
36
38
|
title VARCHAR(120) NULL,
|
|
37
39
|
PRIMARY KEY (session_id),
|
|
38
40
|
KEY idx_owner (owner)
|
|
@@ -55,21 +57,46 @@ export const SCHEMA_STATEMENTS = [
|
|
|
55
57
|
status VARCHAR(16) NOT NULL,
|
|
56
58
|
result JSON NULL,
|
|
57
59
|
error TEXT NULL,
|
|
60
|
+
-- error_code: the structured failure code denormalized out of the result blob (tidb-run-store fail()).
|
|
61
|
+
-- ⚠️ NO DESIGN PROVENANCE ON RECORD: its ADD COLUMN seam carried no rationale comment and neither git
|
|
62
|
+
-- history nor any doc records who asked for it (verified 2026-07-26 while folding the seams in). The only
|
|
63
|
+
-- consumer today is the projection (getRun/listRuns → errorCode) — nothing FILTERS on it; see the
|
|
64
|
+
-- deliberately-absent idx_error_code note below.
|
|
58
65
|
error_code VARCHAR(64) NULL,
|
|
59
66
|
instance_id VARCHAR(64) NULL,
|
|
67
|
+
-- job_id (work-view correlation substrate): groups the runs of one logical task across BOTH client doors.
|
|
60
68
|
job_id VARCHAR(64) NULL,
|
|
69
|
+
-- source (credential-derived system identity, clay 2026-06-12): which door submitted the run
|
|
70
|
+
-- (oa / cc-mcp / portal).
|
|
61
71
|
source VARCHAR(64) NULL,
|
|
72
|
+
-- objective_preview: the list's "what is this task" glance — redacted + truncated at write.
|
|
62
73
|
objective_preview VARCHAR(160) NULL,
|
|
74
|
+
-- cancel_requested: durable cross-replica CANCEL flag. The running instance's heartbeat tick honors it
|
|
75
|
+
-- (+ a local AbortController fast path when the cancel lands on the same replica).
|
|
76
|
+
-- Terminal = "failed" + errorCode:"cancelled".
|
|
63
77
|
cancel_requested TINYINT(1) NOT NULL DEFAULT 0,
|
|
78
|
+
-- preempt_requested (design/80 seam #2): durable cross-replica PREEMPT flag — the SCHEDULER's graceful
|
|
79
|
+
-- "yield this task" (≠ cancel's kill). The owning instance's heartbeat tick honors it (+ a local
|
|
80
|
+
-- preemptSignal fast path when the preempt lands on the same replica) → the task durably SUSPENDS at the
|
|
81
|
+
-- next clean turn boundary (gate resource_limit, reason preempt) and is resumable. Reset to 0 on resume
|
|
82
|
+
-- (markResuming) so a resumed leg is not re-preempted by a stale flag (the durable twin of core's
|
|
83
|
+
-- pre-aborted-signal strip).
|
|
64
84
|
preempt_requested TINYINT(1) NOT NULL DEFAULT 0,
|
|
65
85
|
created_at DATETIME(3) NOT NULL,
|
|
66
86
|
updated_at DATETIME(3) NOT NULL,
|
|
67
87
|
PRIMARY KEY (task_id),
|
|
68
88
|
KEY idx_session_status (session_id, status),
|
|
69
89
|
KEY idx_owner (owner),
|
|
70
|
-
|
|
90
|
+
-- idx_job: WHERE job_id = ? (work-view grouping) would full-scan without it.
|
|
71
91
|
KEY idx_job (job_id),
|
|
92
|
+
-- idx_created: listRuns ALWAYS ORDER BY created_at DESC — without this the trace list does a filesort on
|
|
93
|
+
-- every query (deepseek council #2). Covers the unfiltered newest-first list; filtered lists narrow via
|
|
94
|
+
-- idx_owner / idx_job first.
|
|
72
95
|
KEY idx_created (created_at)
|
|
96
|
+
-- ⛔ DELIBERATELY NO KEY idx_error_code (error_code) (dropped 2026-07-26, clay 裁): a whole-repo sweep found
|
|
97
|
+
-- ZERO code paths that filter on error_code — every reference is a SET or a projection — so the index only
|
|
98
|
+
-- ever served an operator's ad-hoc WHERE error_code LIKE 'budget.%', while every task_run write paid its
|
|
99
|
+
-- maintenance cost. If you need that ad-hoc query, build the index temporarily and drop it after.
|
|
73
100
|
)`,
|
|
74
101
|
`CREATE TABLE IF NOT EXISTS circuit_breaker (
|
|
75
102
|
breaker_key VARCHAR(190) NOT NULL,
|
|
@@ -122,6 +149,11 @@ export const SCHEMA_STATEMENTS = [
|
|
|
122
149
|
task_id VARCHAR(64) NULL,
|
|
123
150
|
session_id VARCHAR(64) NULL,
|
|
124
151
|
owner VARCHAR(190) NULL,
|
|
152
|
+
-- scope — single-DB fleet scope guard (defense-in-depth): the run's tenant identity (owner-sourced), so the
|
|
153
|
+
-- approval READ paths (getStatus / get / listPending / decide) can null-safe-filter on it. A NULL scope row
|
|
154
|
+
-- (an untenanted run — and, on the pre-folding schema, any PRE-MIGRATION row) is matched by a NULL scope arg
|
|
155
|
+
-- via MySQL <=> / PG IS NOT DISTINCT FROM — see tidb-approval-store.ts / pg-approval-store.ts.
|
|
156
|
+
-- expireStale deliberately stays GLOBAL (fleet maintenance, not a tenant read).
|
|
125
157
|
scope VARCHAR(190) NULL,
|
|
126
158
|
tool_name VARCHAR(190) NOT NULL,
|
|
127
159
|
args JSON NULL,
|
|
@@ -132,6 +164,7 @@ export const SCHEMA_STATEMENTS = [
|
|
|
132
164
|
decided_at DATETIME(3) NULL,
|
|
133
165
|
PRIMARY KEY (id),
|
|
134
166
|
KEY idx_owner_status (owner, status),
|
|
167
|
+
-- idx_scope_status: the scope-filtered READ paths (listPending WHERE scope <=> ?) would full-scan without it.
|
|
135
168
|
KEY idx_scope_status (scope, status),
|
|
136
169
|
KEY idx_status (status)
|
|
137
170
|
)`,
|
|
@@ -143,18 +176,48 @@ export const SCHEMA_STATEMENTS = [
|
|
|
143
176
|
status VARCHAR(16) NOT NULL DEFAULT 'pending',
|
|
144
177
|
tool_name VARCHAR(190) NULL,
|
|
145
178
|
tool_call_id VARCHAR(190) NULL,
|
|
179
|
+
-- tool_input: the pending-approval tool args = the approval card's payload, surfaced in listPending so the
|
|
180
|
+
-- BFF need not N+1-fetch trace.turns per pending item. Redacted + bounded at put().
|
|
181
|
+
-- NULL on PRE-MIGRATION rows (suspended before the column existed) ⇒ the consumer falls back.
|
|
146
182
|
tool_input JSON NULL,
|
|
147
183
|
checkpoint JSON NOT NULL,
|
|
148
184
|
outcome JSON NULL,
|
|
149
185
|
deadline BIGINT NULL,
|
|
150
186
|
created_at BIGINT NOT NULL,
|
|
151
187
|
decided_at BIGINT NULL,
|
|
188
|
+
-- rev (design/80 D-1): monotonic optimistic-concurrency counter bumped on every resolve/reopen, so a
|
|
189
|
+
-- resolve(expect) requires the rev the resume observed to still be live → fail-closed on a concurrent
|
|
190
|
+
-- resolve-reopen cycle (core → checkpoint.reopened_concurrently).
|
|
191
|
+
-- PRE-MIGRATION rows ⇒ 0 (the LEGACY CONTRACT; core's .d.ts spells it "Absent ⇒ legacy 0").
|
|
152
192
|
rev BIGINT NOT NULL DEFAULT 0,
|
|
193
|
+
-- reopen_reason — reopen-by-reason: env_failed (a re-resume MUST replay the persisted winner) vs
|
|
194
|
+
-- tool_unavailable (a fresh decision is allowed). Drives core's reopen-revote validation.
|
|
195
|
+
-- NULL = NEVER REOPENED (the first resume is unconstrained).
|
|
153
196
|
reopen_reason VARCHAR(32) NULL,
|
|
197
|
+
-- terminal_at: crash-safe ABSOLUTE lifetime backstop (design/80 §3 inv#3), distinct from the per-approval
|
|
198
|
+
-- deadline, stamped at put(). NULL on PRE-MIGRATION rows — those keep their DEADLINE-BASED expiry (the old
|
|
199
|
+
-- path); the backstop only covers rows suspended after the column existed.
|
|
154
200
|
terminal_at BIGINT NULL,
|
|
201
|
+
-- gate_kind (design/80 D-D, SLA-timer): the CheckpointGate.kind, stamped at put(), so the SLA sweep splits by
|
|
202
|
+
-- kind WITHOUT parsing the JSON blob per row — human/irreversible_ask past deadline are resolve-DENIED (the
|
|
203
|
+
-- model continues with a denial), resource_limit/needs_review are abandonment-TTL → expire().
|
|
204
|
+
-- NULL (a PRE-MIGRATION row, or a gate carrying no kind) ⇒ the LEGACY UNIFORM expire() path: the deny split
|
|
205
|
+
-- is NEW-ROWS-ONLY. See tidb-checkpoint-store.ts reapExpired / listExpiredApprovalGates.
|
|
155
206
|
gate_kind VARCHAR(32) NULL,
|
|
207
|
+
-- bound_input_hash (design/80 D-1): the server-minted opaque boundInputHash, stamped at put() from
|
|
208
|
+
-- pendingAction.boundInputHash, surfaced via listPending so the operator/portal can ECHO it on /decide (the
|
|
209
|
+
-- D-1 TOCTOU binding guard is UNREACHABLE by the portal without it — it can then only do the unbound legacy
|
|
210
|
+
-- fallback). NULL on PRE-D-1 rows ⇒ that unbound fallback.
|
|
156
211
|
bound_input_hash VARCHAR(190) NULL,
|
|
212
|
+
-- pending_steer (design/80 D-A, durable steering): the parked steer ({text,trusted} JSON) set by
|
|
213
|
+
-- setPendingSteer on a STILL-PENDING checkpoint (last-writer-wins, CAS on status='pending'), surfaced via
|
|
214
|
+
-- get() as state.pendingSteer so core injects it on resume. NULL when no steer is parked.
|
|
215
|
+
-- Never touches status/resolve (design/80 inv #4).
|
|
157
216
|
pending_steer TEXT NULL,
|
|
217
|
+
-- risk_descriptor (design/80 riskDescriptor inbox): core's INERT CheckpointGate.riskDescriptor ({severity
|
|
218
|
+
-- 1-5, axes, toolName, redacted summary, touchedPaths}), stamped at put() so listPending can surface it +
|
|
219
|
+
-- triage-sort the supervisor inbox by severity DESC — WITHOUT parsing the checkpoint blob per row.
|
|
220
|
+
-- NULL on gates with no descriptor.
|
|
158
221
|
risk_descriptor TEXT NULL,
|
|
159
222
|
PRIMARY KEY (token),
|
|
160
223
|
KEY idx_status_deadline (status, deadline),
|
|
@@ -200,6 +263,10 @@ export const SCHEMA_STATEMENTS = [
|
|
|
200
263
|
`CREATE TABLE IF NOT EXISTS session_policy (
|
|
201
264
|
policy_key CHAR(64) NOT NULL,
|
|
202
265
|
session_id VARCHAR(64) NOT NULL,
|
|
266
|
+
-- principal (2c session-sync): DENORMALIZED so listBySession can ENUMERATE a session's per-principal rows
|
|
267
|
+
-- (the sha256 policy_key is one-way → the principal is not recoverable from it). Enumeration-ONLY: not a key,
|
|
268
|
+
-- not part of the CAS/tighten logic — putRules just writes it. NULL = the session-wide row (principal
|
|
269
|
+
-- undefined). PRE-MIGRATION rows (E6 was new when the column landed) ⇒ NULL, i.e. read as session-wide.
|
|
203
270
|
principal VARCHAR(190) NULL,
|
|
204
271
|
rules JSON NOT NULL,
|
|
205
272
|
rev BIGINT NOT NULL,
|
|
@@ -219,6 +286,13 @@ export const SCHEMA_STATEMENTS = [
|
|
|
219
286
|
`CREATE TABLE IF NOT EXISTS snapshot_blob (
|
|
220
287
|
blob_hash CHAR(64) NOT NULL,
|
|
221
288
|
byte_len BIGINT NOT NULL,
|
|
289
|
+
-- bytes is NULLABLE — and that nullability IS the two-backend reference-tracking contract (see the block
|
|
290
|
+
-- comment above; this column is where it is enforced). It was originally LONGBLOB NOT NULL back when the
|
|
291
|
+
-- SQL store was the ONLY blob backend; the MinIO offload then required an INDEX-ONLY row here (bytes NULL,
|
|
292
|
+
-- the bytes living in MinIO) so that gcOrphanBlobs / reap / deleteBySession (E21 purge) / sweepOrphanBlobs
|
|
293
|
+
-- keep computing the orphan/grace SET from snapshot_blob for BOTH backends identically — and so an E21 purge
|
|
294
|
+
-- can DELETE the MinIO object instead of leaking it. Re-adding NOT NULL here would silently break MinIO-mode
|
|
295
|
+
-- reference tracking. See blob-backend.ts MinioBlobBackend.
|
|
222
296
|
bytes LONGBLOB NULL,
|
|
223
297
|
created_at DATETIME(3) NOT NULL,
|
|
224
298
|
PRIMARY KEY (blob_hash),
|
|
@@ -227,6 +301,12 @@ export const SCHEMA_STATEMENTS = [
|
|
|
227
301
|
`CREATE TABLE IF NOT EXISTS workflow_journal (
|
|
228
302
|
run_id VARCHAR(191) NOT NULL,
|
|
229
303
|
ordinal INT NOT NULL,
|
|
304
|
+
-- scope (SVC-2 / CORE-9 audit BLOCKER): the table predates scope — it was added when the
|
|
305
|
+
-- WorkflowJournalStore seam grew a scope param for cross-tenant resume isolation. Every new append writes the
|
|
306
|
+
-- run's REAL scope. EXISTING (pre-migration) rows carry '' = CROSS-SCOPE ORPHANS, harmless by construction: a
|
|
307
|
+
-- real resume filters WHERE scope = <caller>, so the blank-scope residue never matches a tenant. The
|
|
308
|
+
-- NOT NULL DEFAULT '' is what let the original ALTER succeed on a table that already had rows — keep the
|
|
309
|
+
-- default so the "'' = orphan, never a tenant" reading stays true.
|
|
230
310
|
scope VARCHAR(190) NOT NULL DEFAULT '',
|
|
231
311
|
call_key VARCHAR(255) NOT NULL,
|
|
232
312
|
result MEDIUMTEXT NOT NULL,
|
|
@@ -253,7 +333,14 @@ export const SCHEMA_STATEMENTS = [
|
|
|
253
333
|
status VARCHAR(16) NOT NULL,
|
|
254
334
|
summary TEXT NOT NULL,
|
|
255
335
|
enqueued_at BIGINT NOT NULL,
|
|
336
|
+
-- kind (1.109): task_notification entries SHARE this inbox — kind discriminates the drain frame family.
|
|
337
|
+
-- 🔴 NULL = a legacy workflow_complete row, AND it stays a LIVE value: the workflow_complete enqueue path
|
|
338
|
+
-- never sets kind, so new rows are written NULL too. The read side has the matching compat branch —
|
|
339
|
+
-- tidb-workflow-run-store.ts projects kind only when non-null and workflow-completion-inbox.ts routes
|
|
340
|
+
-- kind === "task_notification" vs everything-else. Do NOT "tidy" this into NOT NULL DEFAULT.
|
|
256
341
|
kind VARCHAR(24) NULL,
|
|
342
|
+
-- payload: bounded + redacted JSON extras (task_type / result / …) for the task_notification family.
|
|
343
|
+
-- NULL for legacy / plain workflow_complete rows.
|
|
257
344
|
payload TEXT NULL,
|
|
258
345
|
PRIMARY KEY (session_id, run_id),
|
|
259
346
|
UNIQUE KEY uq_wfinbox_seq (seq),
|
|
@@ -299,6 +386,8 @@ export const SCHEMA_STATEMENTS = [
|
|
|
299
386
|
outcome VARCHAR(8) NULL,
|
|
300
387
|
llm_assisted JSON NULL,
|
|
301
388
|
signature_inputs JSON NULL,
|
|
389
|
+
-- core_outcome (design/73 §1 bridge): the VERBATIM core TaskOutcome fact
|
|
390
|
+
-- (oracleHadRedRun / status / oracle blob). The PG twin adds it via ADD COLUMN IF NOT EXISTS.
|
|
302
391
|
core_outcome JSON NULL,
|
|
303
392
|
created_at DATETIME(3) NOT NULL,
|
|
304
393
|
PRIMARY KEY (id),
|
|
@@ -390,37 +479,6 @@ export const SCHEMA_STATEMENTS = [
|
|
|
390
479
|
PRIMARY KEY (pool)
|
|
391
480
|
)`,
|
|
392
481
|
];
|
|
393
|
-
const COLUMN_MIGRATIONS = [
|
|
394
|
-
{ table: "session_meta", column: "title", ddl: "ALTER TABLE session_meta ADD COLUMN title VARCHAR(120) NULL" },
|
|
395
|
-
{ table: "outcome_ledger", column: "core_outcome", ddl: "ALTER TABLE outcome_ledger ADD COLUMN core_outcome JSON NULL" },
|
|
396
|
-
{ table: "workflow_completion_inbox", column: "kind", ddl: "ALTER TABLE workflow_completion_inbox ADD COLUMN kind VARCHAR(24) NULL" },
|
|
397
|
-
{ table: "workflow_completion_inbox", column: "payload", ddl: "ALTER TABLE workflow_completion_inbox ADD COLUMN payload TEXT NULL" },
|
|
398
|
-
{ table: "task_run", column: "error_code", ddl: "ALTER TABLE task_run ADD COLUMN error_code VARCHAR(64) NULL" },
|
|
399
|
-
{ table: "task_run", column: "job_id", ddl: "ALTER TABLE task_run ADD COLUMN job_id VARCHAR(64) NULL" },
|
|
400
|
-
{ table: "task_run", column: "cancel_requested", ddl: "ALTER TABLE task_run ADD COLUMN cancel_requested TINYINT(1) NOT NULL DEFAULT 0" },
|
|
401
|
-
{ table: "task_run", column: "preempt_requested", ddl: "ALTER TABLE task_run ADD COLUMN preempt_requested TINYINT(1) NOT NULL DEFAULT 0" },
|
|
402
|
-
{ table: "task_run", column: "source", ddl: "ALTER TABLE task_run ADD COLUMN source VARCHAR(64) NULL" },
|
|
403
|
-
{ table: "task_run", column: "objective_preview", ddl: "ALTER TABLE task_run ADD COLUMN objective_preview VARCHAR(160) NULL" },
|
|
404
|
-
{ table: "checkpoint", column: "tool_input", ddl: "ALTER TABLE checkpoint ADD COLUMN tool_input JSON NULL" },
|
|
405
|
-
{ table: "checkpoint", column: "rev", ddl: "ALTER TABLE checkpoint ADD COLUMN rev BIGINT NOT NULL DEFAULT 0" },
|
|
406
|
-
{ table: "checkpoint", column: "reopen_reason", ddl: "ALTER TABLE checkpoint ADD COLUMN reopen_reason VARCHAR(32) NULL" },
|
|
407
|
-
{ table: "checkpoint", column: "terminal_at", ddl: "ALTER TABLE checkpoint ADD COLUMN terminal_at BIGINT NULL" },
|
|
408
|
-
{ table: "checkpoint", column: "gate_kind", ddl: "ALTER TABLE checkpoint ADD COLUMN gate_kind VARCHAR(32) NULL" },
|
|
409
|
-
{ table: "checkpoint", column: "bound_input_hash", ddl: "ALTER TABLE checkpoint ADD COLUMN bound_input_hash VARCHAR(190) NULL" },
|
|
410
|
-
{ table: "checkpoint", column: "pending_steer", ddl: "ALTER TABLE checkpoint ADD COLUMN pending_steer TEXT NULL" },
|
|
411
|
-
{ table: "checkpoint", column: "risk_descriptor", ddl: "ALTER TABLE checkpoint ADD COLUMN risk_descriptor TEXT NULL" },
|
|
412
|
-
{ table: "approval", column: "scope", ddl: "ALTER TABLE approval ADD COLUMN scope VARCHAR(190) NULL" },
|
|
413
|
-
{ table: "session_policy", column: "principal", ddl: "ALTER TABLE session_policy ADD COLUMN principal VARCHAR(190) NULL" },
|
|
414
|
-
{ table: "workflow_journal", column: "scope", ddl: "ALTER TABLE workflow_journal ADD COLUMN scope VARCHAR(190) NOT NULL DEFAULT ''" },
|
|
415
|
-
];
|
|
416
|
-
const INDEX_MIGRATIONS = [
|
|
417
|
-
{ table: "task_run", index: "idx_job", ddl: "CREATE INDEX idx_job ON task_run (job_id)" },
|
|
418
|
-
{ table: "task_run", index: "idx_created", ddl: "CREATE INDEX idx_created ON task_run (created_at)" },
|
|
419
|
-
{ table: "approval", index: "idx_scope_status", ddl: "CREATE INDEX idx_scope_status ON approval (scope, status)" },
|
|
420
|
-
];
|
|
421
|
-
const NULLABILITY_MIGRATIONS = [
|
|
422
|
-
{ table: "snapshot_blob", column: "bytes", ddl: "ALTER TABLE snapshot_blob MODIFY COLUMN bytes LONGBLOB NULL" },
|
|
423
|
-
];
|
|
424
482
|
export const ENSURE_SCHEMA_LOCK = "sema_ensure_schema";
|
|
425
483
|
export async function acquireEnsureSchemaLock(conn, tag = "ensureSchema") {
|
|
426
484
|
await new Promise((r) => setTimeout(r, Math.floor(Math.random() * 400)));
|
|
@@ -454,42 +512,6 @@ export async function ensureSchema(pool) {
|
|
|
454
512
|
await conn.query(stmt);
|
|
455
513
|
}
|
|
456
514
|
await conn.query("INSERT IGNORE INTO image_bake_admit (pool) VALUES ('standard')");
|
|
457
|
-
for (const m of COLUMN_MIGRATIONS) {
|
|
458
|
-
const [rows] = await conn.query("SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = ? AND column_name = ? LIMIT 1", [m.table, m.column]);
|
|
459
|
-
if (rows.length === 0) {
|
|
460
|
-
try {
|
|
461
|
-
await conn.query(m.ddl);
|
|
462
|
-
}
|
|
463
|
-
catch (err) {
|
|
464
|
-
if (Number(err.errno) !== 1060)
|
|
465
|
-
throw err;
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
for (const m of INDEX_MIGRATIONS) {
|
|
470
|
-
const [rows] = await conn.query("SELECT 1 FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = ? AND index_name = ? LIMIT 1", [m.table, m.index]);
|
|
471
|
-
if (rows.length === 0) {
|
|
472
|
-
try {
|
|
473
|
-
await conn.query(m.ddl);
|
|
474
|
-
}
|
|
475
|
-
catch (err) {
|
|
476
|
-
if (Number(err.errno) !== 1061)
|
|
477
|
-
throw err;
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
for (const m of NULLABILITY_MIGRATIONS) {
|
|
482
|
-
const [rows] = await conn.query("SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = ? AND column_name = ? AND is_nullable = 'YES' LIMIT 1", [m.table, m.column]);
|
|
483
|
-
if (rows.length === 0) {
|
|
484
|
-
try {
|
|
485
|
-
await conn.query(m.ddl);
|
|
486
|
-
}
|
|
487
|
-
catch (err) {
|
|
488
|
-
if (Number(err.errno) !== 1060)
|
|
489
|
-
throw err;
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
515
|
}
|
|
494
516
|
finally {
|
|
495
517
|
if (locked)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.285.1",
|
|
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",
|