@treeseed/sdk 0.1.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 +565 -0
- package/dist/cli-tools.js +44 -0
- package/dist/content-store.js +237 -0
- package/dist/d1-store.js +549 -0
- package/dist/frontmatter.js +33 -0
- package/dist/git-runtime.js +67 -0
- package/dist/index.js +12 -0
- package/dist/model-registry.js +164 -0
- package/dist/runtime.js +36 -0
- package/dist/scripts/.ts-run-1775616845195-odh4xzphk3l.js +22 -0
- package/dist/scripts/.ts-run-1775616848931-9386s6kwrl.js +126 -0
- package/dist/scripts/assert-release-tag-version.d.ts +1 -0
- package/dist/scripts/assert-release-tag-version.js +23 -0
- package/dist/scripts/build-dist.d.ts +1 -0
- package/dist/scripts/build-dist.js +114 -0
- package/dist/scripts/package-tools.d.ts +15 -0
- package/dist/scripts/package-tools.js +76 -0
- package/dist/scripts/publish-package.d.ts +1 -0
- package/dist/scripts/publish-package.js +20 -0
- package/dist/scripts/release-verify.d.ts +1 -0
- package/dist/scripts/release-verify.js +49 -0
- package/dist/scripts/run-ts.js +45 -0
- package/dist/scripts/test-smoke.d.ts +1 -0
- package/dist/scripts/test-smoke.js +77 -0
- package/dist/sdk-filters.js +77 -0
- package/dist/sdk-types.js +24 -0
- package/dist/sdk.js +232 -0
- package/dist/src/cli-tools.d.ts +3 -0
- package/dist/src/content-store.d.ts +24 -0
- package/dist/src/d1-store.d.ts +108 -0
- package/dist/src/frontmatter.d.ts +6 -0
- package/dist/src/git-runtime.d.ts +16 -0
- package/dist/src/index.d.ts +6 -0
- package/dist/src/model-registry.d.ts +4 -0
- package/dist/src/runtime.d.ts +1 -0
- package/dist/src/sdk-filters.d.ts +4 -0
- package/dist/src/sdk-types.d.ts +285 -0
- package/dist/src/sdk.d.ts +109 -0
- package/dist/src/stores/cursor-store.d.ts +10 -0
- package/dist/src/stores/envelopes.d.ts +116 -0
- package/dist/src/stores/helpers.d.ts +12 -0
- package/dist/src/stores/lease-store.d.ts +18 -0
- package/dist/src/stores/message-store.d.ts +12 -0
- package/dist/src/stores/run-store.d.ts +10 -0
- package/dist/src/stores/subscription-store.d.ts +9 -0
- package/dist/src/types/agents.d.ts +100 -0
- package/dist/src/types/cloudflare.d.ts +32 -0
- package/dist/src/wrangler-d1.d.ts +25 -0
- package/dist/stores/cursor-store.js +158 -0
- package/dist/stores/envelopes.js +219 -0
- package/dist/stores/helpers.js +42 -0
- package/dist/stores/lease-store.js +183 -0
- package/dist/stores/message-store.js +249 -0
- package/dist/stores/run-store.js +166 -0
- package/dist/stores/subscription-store.js +171 -0
- package/dist/test/test-fixture.d.ts +1 -0
- package/dist/test/utils/envelopes.test.d.ts +1 -0
- package/dist/test/utils/sdk.test.d.ts +1 -0
- package/dist/types/agents.js +40 -0
- package/dist/types/cloudflare.js +0 -0
- package/dist/vitest.config.d.ts +2 -0
- package/dist/wrangler-d1.js +84 -0
- package/package.json +130 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { SqliteStoreBase, nowIso, toSqlValue } from "./helpers.js";
|
|
3
|
+
import { createLeaseEnvelope, leaseEntityFromEnvelope, TRESEED_ENVELOPE_SCHEMA_VERSION } from "./envelopes.js";
|
|
4
|
+
function leaseFromRow(row) {
|
|
5
|
+
return {
|
|
6
|
+
model: String(row.model ?? ""),
|
|
7
|
+
itemKey: String(row.itemKey ?? row.item_key ?? ""),
|
|
8
|
+
claimedBy: String(row.claimedBy ?? row.claimed_by ?? ""),
|
|
9
|
+
claimedAt: String(row.claimedAt ?? row.claimed_at ?? ""),
|
|
10
|
+
leaseExpiresAt: String(row.leaseExpiresAt ?? row.lease_expires_at ?? ""),
|
|
11
|
+
token: String(row.token ?? "")
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function buildFilterSql(filters = []) {
|
|
15
|
+
return filters?.length ? `WHERE ${filters.map((filter) => {
|
|
16
|
+
switch (filter.op) {
|
|
17
|
+
case "eq":
|
|
18
|
+
return `${filter.field} = ${toSqlValue(filter.value)}`;
|
|
19
|
+
case "in":
|
|
20
|
+
return `${filter.field} IN (${(Array.isArray(filter.value) ? filter.value : [filter.value]).map(toSqlValue).join(", ")})`;
|
|
21
|
+
case "updated_since":
|
|
22
|
+
return `lease_expires_at >= ${toSqlValue(filter.value)}`;
|
|
23
|
+
default:
|
|
24
|
+
return `${filter.field} LIKE ${toSqlValue(`%${String(filter.value ?? "")}%`)}`;
|
|
25
|
+
}
|
|
26
|
+
}).join(" AND ")}` : "";
|
|
27
|
+
}
|
|
28
|
+
class LeaseStore extends SqliteStoreBase {
|
|
29
|
+
async usesEnvelopeTable() {
|
|
30
|
+
return this.tableExists("lease_state");
|
|
31
|
+
}
|
|
32
|
+
async getByKey(key) {
|
|
33
|
+
const [model, itemKey] = key.split(":", 2);
|
|
34
|
+
if (!model || !itemKey) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
if (await this.usesEnvelopeTable()) {
|
|
38
|
+
const row2 = await this.selectFirst(
|
|
39
|
+
`SELECT * FROM lease_state WHERE model = ${toSqlValue(model)} AND item_key = ${toSqlValue(itemKey)} LIMIT 1`
|
|
40
|
+
);
|
|
41
|
+
return row2 ? leaseEntityFromEnvelope(row2) : null;
|
|
42
|
+
}
|
|
43
|
+
const row = await this.selectFirst(
|
|
44
|
+
`SELECT * FROM content_leases WHERE model = ${toSqlValue(model)} AND item_key = ${toSqlValue(itemKey)} LIMIT 1`
|
|
45
|
+
);
|
|
46
|
+
return row ? leaseFromRow(row) : null;
|
|
47
|
+
}
|
|
48
|
+
async search(request) {
|
|
49
|
+
if (await this.usesEnvelopeTable()) {
|
|
50
|
+
const sql2 = [
|
|
51
|
+
"SELECT * FROM lease_state",
|
|
52
|
+
buildEnvelopeFilterSql(request.filters),
|
|
53
|
+
request.sort?.length ? `ORDER BY ${request.sort.map((entry) => `${leaseSortColumn(entry.field)} ${entry.direction === "asc" ? "ASC" : "DESC"}`).join(", ")}` : "",
|
|
54
|
+
request.limit ? `LIMIT ${request.limit}` : ""
|
|
55
|
+
].filter(Boolean).join(" ");
|
|
56
|
+
const rows2 = await this.selectAll(sql2);
|
|
57
|
+
return rows2.map(leaseEntityFromEnvelope);
|
|
58
|
+
}
|
|
59
|
+
const sql = [
|
|
60
|
+
"SELECT * FROM content_leases",
|
|
61
|
+
buildFilterSql(request.filters),
|
|
62
|
+
request.sort?.length ? `ORDER BY ${request.sort.map((entry) => `${entry.field} ${entry.direction === "asc" ? "ASC" : "DESC"}`).join(", ")}` : "",
|
|
63
|
+
request.limit ? `LIMIT ${request.limit}` : ""
|
|
64
|
+
].filter(Boolean).join(" ");
|
|
65
|
+
const rows = await this.selectAll(sql);
|
|
66
|
+
return rows.map(leaseFromRow);
|
|
67
|
+
}
|
|
68
|
+
async tryClaim(input) {
|
|
69
|
+
if (await this.usesEnvelopeTable()) {
|
|
70
|
+
const existing2 = await this.selectFirst(
|
|
71
|
+
`SELECT * FROM lease_state WHERE model = ${toSqlValue(input.model)} AND item_key = ${toSqlValue(input.itemKey)} LIMIT 1`
|
|
72
|
+
);
|
|
73
|
+
if (existing2 && new Date(String(existing2.lease_expires_at ?? 0)).valueOf() > Date.now()) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
const token2 = crypto.randomUUID();
|
|
77
|
+
const claimedAt = nowIso();
|
|
78
|
+
const leaseExpiresAt = new Date(Date.now() + input.leaseSeconds * 1e3).toISOString();
|
|
79
|
+
const envelope = createLeaseEnvelope({
|
|
80
|
+
token: token2,
|
|
81
|
+
meta: { actor: input.claimedBy }
|
|
82
|
+
});
|
|
83
|
+
await this.execute(
|
|
84
|
+
`INSERT OR REPLACE INTO lease_state (model, item_key, status, schema_version, claimed_by, claimed_at, lease_expires_at, created_at, updated_at, payload_json, meta_json) VALUES (${toSqlValue(input.model)}, ${toSqlValue(input.itemKey)}, ${toSqlValue(envelope.status)}, ${TRESEED_ENVELOPE_SCHEMA_VERSION}, ${toSqlValue(input.claimedBy)}, ${toSqlValue(claimedAt)}, ${toSqlValue(leaseExpiresAt)}, COALESCE((SELECT created_at FROM lease_state WHERE model = ${toSqlValue(input.model)} AND item_key = ${toSqlValue(input.itemKey)}), ${toSqlValue(claimedAt)}), ${toSqlValue(claimedAt)}, ${toSqlValue(JSON.stringify(envelope.payload))}, ${toSqlValue(JSON.stringify(envelope.meta))})`
|
|
85
|
+
);
|
|
86
|
+
return token2;
|
|
87
|
+
}
|
|
88
|
+
const existing = await this.selectFirst(
|
|
89
|
+
`SELECT * FROM content_leases WHERE model = ${toSqlValue(input.model)} AND item_key = ${toSqlValue(input.itemKey)} LIMIT 1`
|
|
90
|
+
);
|
|
91
|
+
if (existing && new Date(String(existing.lease_expires_at ?? 0)).valueOf() > Date.now()) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
const token = crypto.randomUUID();
|
|
95
|
+
await this.execute(
|
|
96
|
+
`INSERT OR REPLACE INTO content_leases (model, item_key, claimed_by, claimed_at, lease_expires_at, token) VALUES (${toSqlValue(input.model)}, ${toSqlValue(input.itemKey)}, ${toSqlValue(input.claimedBy)}, ${toSqlValue(nowIso())}, ${toSqlValue(new Date(Date.now() + input.leaseSeconds * 1e3).toISOString())}, ${toSqlValue(token)})`
|
|
97
|
+
);
|
|
98
|
+
return token;
|
|
99
|
+
}
|
|
100
|
+
async create(input) {
|
|
101
|
+
const token = await this.tryClaim(input);
|
|
102
|
+
if (!token) {
|
|
103
|
+
return this.getByKey(`${input.model}:${input.itemKey}`);
|
|
104
|
+
}
|
|
105
|
+
return this.getByKey(`${input.model}:${input.itemKey}`);
|
|
106
|
+
}
|
|
107
|
+
async release(request) {
|
|
108
|
+
if (await this.usesEnvelopeTable()) {
|
|
109
|
+
await this.execute(
|
|
110
|
+
`DELETE FROM lease_state WHERE model = ${toSqlValue(request.model)} AND item_key = ${toSqlValue(request.itemKey)}`
|
|
111
|
+
);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
await this.execute(
|
|
115
|
+
`DELETE FROM content_leases WHERE model = ${toSqlValue(request.model)} AND item_key = ${toSqlValue(request.itemKey)}`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
async update(request) {
|
|
119
|
+
const model = String(request.data.model ?? request.id ?? "");
|
|
120
|
+
const itemKey = String(request.data.itemKey ?? request.slug ?? request.key ?? "");
|
|
121
|
+
const claimedBy = String(request.data.claimedBy ?? request.actor);
|
|
122
|
+
const leaseSeconds = Number(request.data.leaseSeconds ?? 300);
|
|
123
|
+
return this.create({ model, itemKey, claimedBy, leaseSeconds });
|
|
124
|
+
}
|
|
125
|
+
async releaseAll() {
|
|
126
|
+
if (await this.usesEnvelopeTable()) {
|
|
127
|
+
const rows2 = await this.selectAll("SELECT COUNT(*) AS count FROM lease_state");
|
|
128
|
+
await this.execute("DELETE FROM lease_state");
|
|
129
|
+
return Number(rows2[0]?.count ?? 0);
|
|
130
|
+
}
|
|
131
|
+
const rows = await this.selectAll("SELECT COUNT(*) AS count FROM content_leases");
|
|
132
|
+
await this.execute("DELETE FROM content_leases");
|
|
133
|
+
return Number(rows[0]?.count ?? 0);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function buildEnvelopeFilterSql(filters = []) {
|
|
137
|
+
return filters?.length ? `WHERE ${filters.map((filter) => {
|
|
138
|
+
const field = leaseFilterColumn(filter.field);
|
|
139
|
+
switch (filter.op) {
|
|
140
|
+
case "eq":
|
|
141
|
+
return `${field} = ${toSqlValue(filter.value)}`;
|
|
142
|
+
case "in":
|
|
143
|
+
return `${field} IN (${(Array.isArray(filter.value) ? filter.value : [filter.value]).map(toSqlValue).join(", ")})`;
|
|
144
|
+
case "updated_since":
|
|
145
|
+
return `lease_expires_at >= ${toSqlValue(filter.value)}`;
|
|
146
|
+
default:
|
|
147
|
+
return `${field} LIKE ${toSqlValue(`%${String(filter.value ?? "")}%`)}`;
|
|
148
|
+
}
|
|
149
|
+
}).join(" AND ")}` : "";
|
|
150
|
+
}
|
|
151
|
+
function leaseFilterColumn(field) {
|
|
152
|
+
switch (field) {
|
|
153
|
+
case "model":
|
|
154
|
+
return "model";
|
|
155
|
+
case "itemKey":
|
|
156
|
+
case "item_key":
|
|
157
|
+
return "item_key";
|
|
158
|
+
case "claimedBy":
|
|
159
|
+
case "claimed_by":
|
|
160
|
+
return "claimed_by";
|
|
161
|
+
case "leaseExpiresAt":
|
|
162
|
+
case "lease_expires_at":
|
|
163
|
+
return "lease_expires_at";
|
|
164
|
+
default:
|
|
165
|
+
return `json_extract(payload_json, '$.${field}')`;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function leaseSortColumn(field) {
|
|
169
|
+
switch (field) {
|
|
170
|
+
case "model":
|
|
171
|
+
return "model";
|
|
172
|
+
case "itemKey":
|
|
173
|
+
case "item_key":
|
|
174
|
+
return "item_key";
|
|
175
|
+
case "leaseExpiresAt":
|
|
176
|
+
case "lease_expires_at":
|
|
177
|
+
default:
|
|
178
|
+
return "lease_expires_at";
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
export {
|
|
182
|
+
LeaseStore
|
|
183
|
+
};
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { SqliteStoreBase, nowIso, toSqlValue } from "./helpers.js";
|
|
2
|
+
import { createMessageEnvelope, messageEntityFromEnvelope, TRESEED_ENVELOPE_SCHEMA_VERSION } from "./envelopes.js";
|
|
3
|
+
function messageFromRow(row) {
|
|
4
|
+
return {
|
|
5
|
+
id: Number(row.id),
|
|
6
|
+
type: String(row.type ?? ""),
|
|
7
|
+
status: String(row.status ?? "pending"),
|
|
8
|
+
payloadJson: String(row.payload_json ?? row.payloadJson ?? "{}"),
|
|
9
|
+
relatedModel: row.related_model ? String(row.related_model) : row.relatedModel ? String(row.relatedModel) : null,
|
|
10
|
+
relatedId: row.related_id ? String(row.related_id) : row.relatedId ? String(row.relatedId) : null,
|
|
11
|
+
priority: Number(row.priority ?? 0),
|
|
12
|
+
availableAt: String(row.available_at ?? row.availableAt ?? nowIso()),
|
|
13
|
+
claimedBy: row.claimed_by ? String(row.claimed_by) : row.claimedBy ? String(row.claimedBy) : null,
|
|
14
|
+
claimedAt: row.claimed_at ? String(row.claimed_at) : row.claimedAt ? String(row.claimedAt) : null,
|
|
15
|
+
leaseExpiresAt: row.lease_expires_at ? String(row.lease_expires_at) : row.leaseExpiresAt ? String(row.leaseExpiresAt) : null,
|
|
16
|
+
attempts: Number(row.attempts ?? 0),
|
|
17
|
+
maxAttempts: Number(row.max_attempts ?? row.maxAttempts ?? 3),
|
|
18
|
+
createdAt: String(row.created_at ?? row.createdAt ?? nowIso()),
|
|
19
|
+
updatedAt: String(row.updated_at ?? row.updatedAt ?? nowIso())
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function buildFilterSql(filters = []) {
|
|
23
|
+
return filters?.length ? `WHERE ${filters.map((filter) => {
|
|
24
|
+
switch (filter.op) {
|
|
25
|
+
case "eq":
|
|
26
|
+
return `${filter.field} = ${toSqlValue(filter.value)}`;
|
|
27
|
+
case "in":
|
|
28
|
+
return `${filter.field} IN (${(Array.isArray(filter.value) ? filter.value : [filter.value]).map(toSqlValue).join(", ")})`;
|
|
29
|
+
case "updated_since":
|
|
30
|
+
return `${filter.field} >= ${toSqlValue(filter.value)}`;
|
|
31
|
+
default:
|
|
32
|
+
return `${filter.field} LIKE ${toSqlValue(`%${String(filter.value ?? "")}%`)}`;
|
|
33
|
+
}
|
|
34
|
+
}).join(" AND ")}` : "";
|
|
35
|
+
}
|
|
36
|
+
function buildOrderSql(sort = []) {
|
|
37
|
+
return sort?.length ? `ORDER BY ${sort.map((entry) => `${entry.field} ${entry.direction === "asc" ? "ASC" : "DESC"}`).join(", ")}` : "";
|
|
38
|
+
}
|
|
39
|
+
class MessageStore extends SqliteStoreBase {
|
|
40
|
+
async usesEnvelopeTable() {
|
|
41
|
+
return this.tableExists("message_queue");
|
|
42
|
+
}
|
|
43
|
+
async getById(id) {
|
|
44
|
+
if (await this.usesEnvelopeTable()) {
|
|
45
|
+
const row2 = await this.selectFirst(`SELECT * FROM message_queue WHERE id = ${id} LIMIT 1`);
|
|
46
|
+
return row2 ? messageEntityFromEnvelope(row2) : null;
|
|
47
|
+
}
|
|
48
|
+
const row = await this.selectFirst(`SELECT * FROM messages WHERE id = ${id} LIMIT 1`);
|
|
49
|
+
return row ? messageFromRow(row) : null;
|
|
50
|
+
}
|
|
51
|
+
async search(request) {
|
|
52
|
+
if (await this.usesEnvelopeTable()) {
|
|
53
|
+
const sql2 = [
|
|
54
|
+
"SELECT * FROM message_queue",
|
|
55
|
+
buildEnvelopeFilterSql(request.filters),
|
|
56
|
+
buildEnvelopeOrderSql(request.sort),
|
|
57
|
+
request.limit ? `LIMIT ${request.limit}` : ""
|
|
58
|
+
].filter(Boolean).join(" ");
|
|
59
|
+
const rows2 = await this.selectAll(sql2);
|
|
60
|
+
return rows2.map(messageEntityFromEnvelope);
|
|
61
|
+
}
|
|
62
|
+
const sql = [
|
|
63
|
+
"SELECT * FROM messages",
|
|
64
|
+
buildFilterSql(request.filters),
|
|
65
|
+
buildOrderSql(request.sort),
|
|
66
|
+
request.limit ? `LIMIT ${request.limit}` : ""
|
|
67
|
+
].filter(Boolean).join(" ");
|
|
68
|
+
const rows = await this.selectAll(sql);
|
|
69
|
+
return rows.map(messageFromRow);
|
|
70
|
+
}
|
|
71
|
+
async claim(request) {
|
|
72
|
+
if (await this.usesEnvelopeTable()) {
|
|
73
|
+
const typeClause2 = request.messageTypes?.length ? ` AND message_type IN (${request.messageTypes.map(toSqlValue).join(", ")})` : "";
|
|
74
|
+
const row2 = await this.selectFirst(
|
|
75
|
+
`SELECT * FROM message_queue WHERE status IN ('pending', 'failed') AND available_at <= ${toSqlValue(nowIso())}${typeClause2} ORDER BY priority DESC, available_at ASC LIMIT 1`
|
|
76
|
+
);
|
|
77
|
+
if (!row2) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
const id2 = Number(row2.id);
|
|
81
|
+
const claimedAt2 = nowIso();
|
|
82
|
+
await this.execute(
|
|
83
|
+
`UPDATE message_queue SET status = 'claimed', claimed_by = ${toSqlValue(request.workerId)}, claimed_at = ${toSqlValue(claimedAt2)}, lease_expires_at = ${toSqlValue(new Date(Date.now() + request.leaseSeconds * 1e3).toISOString())}, attempts = attempts + 1, updated_at = ${toSqlValue(claimedAt2)} WHERE id = ${id2} AND status IN ('pending', 'failed')`
|
|
84
|
+
);
|
|
85
|
+
return this.getById(id2);
|
|
86
|
+
}
|
|
87
|
+
const typeClause = request.messageTypes?.length ? ` AND type IN (${request.messageTypes.map(toSqlValue).join(", ")})` : "";
|
|
88
|
+
const row = await this.selectFirst(
|
|
89
|
+
`SELECT * FROM messages WHERE status IN ('pending', 'failed') AND available_at <= ${toSqlValue(nowIso())}${typeClause} ORDER BY priority DESC, available_at ASC LIMIT 1`
|
|
90
|
+
);
|
|
91
|
+
if (!row) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
const id = Number(row.id);
|
|
95
|
+
const claimedAt = nowIso();
|
|
96
|
+
await this.execute(
|
|
97
|
+
`UPDATE messages SET status = 'claimed', claimed_by = ${toSqlValue(request.workerId)}, claimed_at = ${toSqlValue(claimedAt)}, lease_expires_at = ${toSqlValue(new Date(Date.now() + request.leaseSeconds * 1e3).toISOString())}, attempts = attempts + 1, updated_at = ${toSqlValue(claimedAt)} WHERE id = ${id} AND status IN ('pending', 'failed')`
|
|
98
|
+
);
|
|
99
|
+
return this.getById(id);
|
|
100
|
+
}
|
|
101
|
+
async ack(request) {
|
|
102
|
+
if (await this.usesEnvelopeTable()) {
|
|
103
|
+
await this.execute(`UPDATE message_queue SET status = ${toSqlValue(request.status)}, updated_at = ${toSqlValue(nowIso())} WHERE id = ${request.id}`);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
await this.execute(`UPDATE messages SET status = ${toSqlValue(request.status)}, updated_at = ${toSqlValue(nowIso())} WHERE id = ${request.id}`);
|
|
107
|
+
}
|
|
108
|
+
async create(request) {
|
|
109
|
+
const timestamp = nowIso();
|
|
110
|
+
if (await this.usesEnvelopeTable()) {
|
|
111
|
+
const envelope = createMessageEnvelope({
|
|
112
|
+
type: request.type,
|
|
113
|
+
payload: request.payload,
|
|
114
|
+
meta: { actor: request.actor }
|
|
115
|
+
});
|
|
116
|
+
await this.execute(
|
|
117
|
+
`INSERT INTO message_queue (message_type, status, schema_version, related_model, related_id, priority, available_at, attempts, max_attempts, created_at, updated_at, payload_json, meta_json) VALUES (${toSqlValue(request.type)}, ${toSqlValue(envelope.status)}, ${TRESEED_ENVELOPE_SCHEMA_VERSION}, ${toSqlValue(request.relatedModel ?? null)}, ${toSqlValue(request.relatedId ?? null)}, ${request.priority ?? 0}, ${toSqlValue(timestamp)}, 0, ${request.maxAttempts ?? 3}, ${toSqlValue(timestamp)}, ${toSqlValue(timestamp)}, ${toSqlValue(JSON.stringify(envelope.payload))}, ${toSqlValue(JSON.stringify(envelope.meta))})`
|
|
118
|
+
);
|
|
119
|
+
const row2 = await this.selectFirst("SELECT * FROM message_queue ORDER BY id DESC LIMIT 1");
|
|
120
|
+
if (!row2) {
|
|
121
|
+
throw new Error("Failed to create message record.");
|
|
122
|
+
}
|
|
123
|
+
return messageEntityFromEnvelope(row2);
|
|
124
|
+
}
|
|
125
|
+
await this.execute(
|
|
126
|
+
`INSERT INTO messages (type, status, payload_json, related_model, related_id, priority, available_at, attempts, max_attempts, created_at, updated_at) VALUES (${toSqlValue(request.type)}, 'pending', ${toSqlValue(JSON.stringify(request.payload))}, ${toSqlValue(request.relatedModel ?? null)}, ${toSqlValue(request.relatedId ?? null)}, ${request.priority ?? 0}, ${toSqlValue(timestamp)}, 0, ${request.maxAttempts ?? 3}, ${toSqlValue(timestamp)}, ${toSqlValue(timestamp)})`
|
|
127
|
+
);
|
|
128
|
+
const row = await this.selectFirst("SELECT * FROM messages ORDER BY id DESC LIMIT 1");
|
|
129
|
+
if (!row) {
|
|
130
|
+
throw new Error("Failed to create message record.");
|
|
131
|
+
}
|
|
132
|
+
return messageFromRow(row);
|
|
133
|
+
}
|
|
134
|
+
async update(request) {
|
|
135
|
+
const id = Number(request.id ?? request.key ?? request.data.id ?? 0);
|
|
136
|
+
if (!id) {
|
|
137
|
+
throw new Error("Message update requires an id.");
|
|
138
|
+
}
|
|
139
|
+
if (await this.usesEnvelopeTable()) {
|
|
140
|
+
const fields2 = [];
|
|
141
|
+
for (const [key, value] of Object.entries(request.data)) {
|
|
142
|
+
if (key === "payload") {
|
|
143
|
+
fields2.push(`payload_json = ${toSqlValue(JSON.stringify({ body: value }))}`);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (key === "meta") {
|
|
147
|
+
fields2.push(`meta_json = ${toSqlValue(JSON.stringify(value))}`);
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
const column = messageEnvelopeColumn(key);
|
|
151
|
+
fields2.push(`${column} = ${toSqlValue(value)}`);
|
|
152
|
+
}
|
|
153
|
+
fields2.push(`updated_at = ${toSqlValue(nowIso())}`);
|
|
154
|
+
await this.execute(`UPDATE message_queue SET ${fields2.join(", ")} WHERE id = ${id}`);
|
|
155
|
+
return this.getById(id);
|
|
156
|
+
}
|
|
157
|
+
const fields = [];
|
|
158
|
+
for (const [key, value] of Object.entries(request.data)) {
|
|
159
|
+
if (key === "payload") {
|
|
160
|
+
fields.push(`payload_json = ${toSqlValue(JSON.stringify(value))}`);
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
const column = key.replace(/[A-Z]/g, (match) => `_${match.toLowerCase()}`);
|
|
164
|
+
fields.push(`${column} = ${toSqlValue(value)}`);
|
|
165
|
+
}
|
|
166
|
+
fields.push(`updated_at = ${toSqlValue(nowIso())}`);
|
|
167
|
+
await this.execute(`UPDATE messages SET ${fields.join(", ")} WHERE id = ${id}`);
|
|
168
|
+
return this.getById(id);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function buildEnvelopeFilterSql(filters = []) {
|
|
172
|
+
return filters?.length ? `WHERE ${filters.map((filter) => {
|
|
173
|
+
const field = messageFilterColumn(filter.field);
|
|
174
|
+
switch (filter.op) {
|
|
175
|
+
case "eq":
|
|
176
|
+
return `${field} = ${toSqlValue(filter.value)}`;
|
|
177
|
+
case "in":
|
|
178
|
+
return `${field} IN (${(Array.isArray(filter.value) ? filter.value : [filter.value]).map(toSqlValue).join(", ")})`;
|
|
179
|
+
case "updated_since":
|
|
180
|
+
return `updated_at >= ${toSqlValue(filter.value)}`;
|
|
181
|
+
default:
|
|
182
|
+
return `${field} LIKE ${toSqlValue(`%${String(filter.value ?? "")}%`)}`;
|
|
183
|
+
}
|
|
184
|
+
}).join(" AND ")}` : "";
|
|
185
|
+
}
|
|
186
|
+
function buildEnvelopeOrderSql(sort = []) {
|
|
187
|
+
return sort?.length ? `ORDER BY ${sort.map((entry) => `${messageSortColumn(entry.field)} ${entry.direction === "asc" ? "ASC" : "DESC"}`).join(", ")}` : "";
|
|
188
|
+
}
|
|
189
|
+
function messageFilterColumn(field) {
|
|
190
|
+
switch (field) {
|
|
191
|
+
case "type":
|
|
192
|
+
return "message_type";
|
|
193
|
+
case "status":
|
|
194
|
+
case "priority":
|
|
195
|
+
case "available_at":
|
|
196
|
+
case "availableAt":
|
|
197
|
+
case "created_at":
|
|
198
|
+
case "createdAt":
|
|
199
|
+
case "updated_at":
|
|
200
|
+
case "updatedAt":
|
|
201
|
+
return field.replace(/[A-Z]/g, (match) => `_${match.toLowerCase()}`);
|
|
202
|
+
default:
|
|
203
|
+
return `json_extract(payload_json, '$.body.${field}')`;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function messageSortColumn(field) {
|
|
207
|
+
switch (field) {
|
|
208
|
+
case "type":
|
|
209
|
+
return "message_type";
|
|
210
|
+
case "priority":
|
|
211
|
+
return "priority";
|
|
212
|
+
case "available_at":
|
|
213
|
+
case "availableAt":
|
|
214
|
+
return "available_at";
|
|
215
|
+
case "created_at":
|
|
216
|
+
case "createdAt":
|
|
217
|
+
return "created_at";
|
|
218
|
+
case "updated_at":
|
|
219
|
+
case "updatedAt":
|
|
220
|
+
default:
|
|
221
|
+
return "updated_at";
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
function messageEnvelopeColumn(field) {
|
|
225
|
+
switch (field) {
|
|
226
|
+
case "type":
|
|
227
|
+
return "message_type";
|
|
228
|
+
case "relatedModel":
|
|
229
|
+
return "related_model";
|
|
230
|
+
case "relatedId":
|
|
231
|
+
return "related_id";
|
|
232
|
+
case "maxAttempts":
|
|
233
|
+
return "max_attempts";
|
|
234
|
+
case "availableAt":
|
|
235
|
+
return "available_at";
|
|
236
|
+
case "claimedBy":
|
|
237
|
+
return "claimed_by";
|
|
238
|
+
case "claimedAt":
|
|
239
|
+
return "claimed_at";
|
|
240
|
+
case "leaseExpiresAt":
|
|
241
|
+
return "lease_expires_at";
|
|
242
|
+
default:
|
|
243
|
+
return field.replace(/[A-Z]/g, (match) => `_${match.toLowerCase()}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
export {
|
|
247
|
+
MessageStore,
|
|
248
|
+
messageFromRow
|
|
249
|
+
};
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { SqliteStoreBase, nowIso, toSqlValue } from "./helpers.js";
|
|
2
|
+
import { createRunEnvelope, runEntityFromEnvelope, TRESEED_ENVELOPE_SCHEMA_VERSION } from "./envelopes.js";
|
|
3
|
+
function runFromRecord(row) {
|
|
4
|
+
return {
|
|
5
|
+
runId: String(row.runId ?? row.run_id ?? ""),
|
|
6
|
+
agentSlug: String(row.agentSlug ?? row.agent_slug ?? ""),
|
|
7
|
+
triggerSource: String(row.triggerSource ?? row.trigger_source ?? ""),
|
|
8
|
+
status: String(row.status ?? ""),
|
|
9
|
+
selectedItemKey: row.selectedItemKey !== void 0 && row.selectedItemKey !== null ? String(row.selectedItemKey) : row.selected_item_key !== void 0 && row.selected_item_key !== null ? String(row.selected_item_key) : null,
|
|
10
|
+
selectedMessageId: row.selectedMessageId !== void 0 && row.selectedMessageId !== null ? Number(row.selectedMessageId) : row.selected_message_id !== void 0 && row.selected_message_id !== null ? Number(row.selected_message_id) : null,
|
|
11
|
+
branchName: row.branchName !== void 0 && row.branchName !== null ? String(row.branchName) : row.branch_name !== void 0 && row.branch_name !== null ? String(row.branch_name) : null,
|
|
12
|
+
prUrl: row.prUrl !== void 0 && row.prUrl !== null ? String(row.prUrl) : row.pr_url !== void 0 && row.pr_url !== null ? String(row.pr_url) : null,
|
|
13
|
+
summary: row.summary !== void 0 && row.summary !== null ? String(row.summary) : null,
|
|
14
|
+
error: row.error !== void 0 && row.error !== null ? String(row.error) : null,
|
|
15
|
+
startedAt: String(row.startedAt ?? row.started_at ?? nowIso()),
|
|
16
|
+
finishedAt: row.finishedAt !== void 0 && row.finishedAt !== null ? String(row.finishedAt) : row.finished_at !== void 0 && row.finished_at !== null ? String(row.finished_at) : null,
|
|
17
|
+
errorCategory: row.errorCategory !== void 0 && row.errorCategory !== null ? String(row.errorCategory) : row.error_category !== void 0 && row.error_category !== null ? String(row.error_category) : null,
|
|
18
|
+
handlerKind: row.handlerKind !== void 0 && row.handlerKind !== null ? String(row.handlerKind) : row.handler_kind !== void 0 && row.handler_kind !== null ? String(row.handler_kind) : null,
|
|
19
|
+
triggerKind: row.triggerKind !== void 0 && row.triggerKind !== null ? String(row.triggerKind) : row.trigger_kind !== void 0 && row.trigger_kind !== null ? String(row.trigger_kind) : null,
|
|
20
|
+
claimedMessageId: row.claimedMessageId !== void 0 && row.claimedMessageId !== null ? Number(row.claimedMessageId) : row.claimed_message_id !== void 0 && row.claimed_message_id !== null ? Number(row.claimed_message_id) : null,
|
|
21
|
+
commitSha: row.commitSha !== void 0 && row.commitSha !== null ? String(row.commitSha) : row.commit_sha !== void 0 && row.commit_sha !== null ? String(row.commit_sha) : null,
|
|
22
|
+
changedPaths: Array.isArray(row.changedPaths) ? row.changedPaths.map(String) : row.changed_paths ? JSON.parse(String(row.changed_paths)) : []
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
class RunStore extends SqliteStoreBase {
|
|
26
|
+
async usesEnvelopeTable() {
|
|
27
|
+
return this.tableExists("runtime_records");
|
|
28
|
+
}
|
|
29
|
+
async getByKey(key) {
|
|
30
|
+
if (await this.usesEnvelopeTable()) {
|
|
31
|
+
const row2 = await this.selectFirst(
|
|
32
|
+
`SELECT * FROM runtime_records WHERE record_type = 'agent_run' AND record_key = ${toSqlValue(key)} LIMIT 1`
|
|
33
|
+
);
|
|
34
|
+
return row2 ? runEntityFromEnvelope(row2) : null;
|
|
35
|
+
}
|
|
36
|
+
const row = await this.selectFirst(
|
|
37
|
+
`SELECT * FROM agent_runs WHERE run_id = ${toSqlValue(key)} LIMIT 1`
|
|
38
|
+
);
|
|
39
|
+
return row ? runFromRecord(row) : null;
|
|
40
|
+
}
|
|
41
|
+
async search(request) {
|
|
42
|
+
if (await this.usesEnvelopeTable()) {
|
|
43
|
+
const where2 = request.filters?.length ? `AND ${request.filters.map((filter) => {
|
|
44
|
+
const field = runFilterColumn(filter.field);
|
|
45
|
+
switch (filter.op) {
|
|
46
|
+
case "eq":
|
|
47
|
+
return `${field} = ${toSqlValue(filter.value)}`;
|
|
48
|
+
case "in":
|
|
49
|
+
return `${field} IN (${(Array.isArray(filter.value) ? filter.value : [filter.value]).map(toSqlValue).join(", ")})`;
|
|
50
|
+
case "updated_since":
|
|
51
|
+
return `created_at >= ${toSqlValue(filter.value)}`;
|
|
52
|
+
default:
|
|
53
|
+
return `${field} LIKE ${toSqlValue(`%${String(filter.value ?? "")}%`)}`;
|
|
54
|
+
}
|
|
55
|
+
}).join(" AND ")}` : "";
|
|
56
|
+
const order2 = request.sort?.length ? `ORDER BY ${request.sort.map((entry) => `${runSortColumn(entry.field)} ${entry.direction === "asc" ? "ASC" : "DESC"}`).join(", ")}` : "";
|
|
57
|
+
const limit2 = request.limit ? `LIMIT ${request.limit}` : "";
|
|
58
|
+
const rows2 = await this.selectAll(["SELECT * FROM runtime_records WHERE record_type = 'agent_run'", where2, order2, limit2].filter(Boolean).join(" "));
|
|
59
|
+
return rows2.map(runEntityFromEnvelope);
|
|
60
|
+
}
|
|
61
|
+
const where = request.filters?.length ? `WHERE ${request.filters.map((filter) => {
|
|
62
|
+
switch (filter.op) {
|
|
63
|
+
case "eq":
|
|
64
|
+
return `${filter.field} = ${toSqlValue(filter.value)}`;
|
|
65
|
+
case "in":
|
|
66
|
+
return `${filter.field} IN (${(Array.isArray(filter.value) ? filter.value : [filter.value]).map(toSqlValue).join(", ")})`;
|
|
67
|
+
case "updated_since":
|
|
68
|
+
return `started_at >= ${toSqlValue(filter.value)}`;
|
|
69
|
+
default:
|
|
70
|
+
return `${filter.field} LIKE ${toSqlValue(`%${String(filter.value ?? "")}%`)}`;
|
|
71
|
+
}
|
|
72
|
+
}).join(" AND ")}` : "";
|
|
73
|
+
const order = request.sort?.length ? `ORDER BY ${request.sort.map((entry) => `${entry.field} ${entry.direction === "asc" ? "ASC" : "DESC"}`).join(", ")}` : "";
|
|
74
|
+
const limit = request.limit ? `LIMIT ${request.limit}` : "";
|
|
75
|
+
const rows = await this.selectAll(["SELECT * FROM agent_runs", where, order, limit].filter(Boolean).join(" "));
|
|
76
|
+
return rows.map(runFromRecord);
|
|
77
|
+
}
|
|
78
|
+
async record(request) {
|
|
79
|
+
const run = runFromRecord(request.run);
|
|
80
|
+
if (await this.usesEnvelopeTable()) {
|
|
81
|
+
const envelope = createRunEnvelope({
|
|
82
|
+
runId: run.runId,
|
|
83
|
+
agentSlug: run.agentSlug,
|
|
84
|
+
status: run.status,
|
|
85
|
+
triggerSource: run.triggerSource,
|
|
86
|
+
startedAt: run.startedAt,
|
|
87
|
+
handlerKind: run.handlerKind ?? null,
|
|
88
|
+
triggerKind: run.triggerKind ?? null,
|
|
89
|
+
selectedItemKey: run.selectedItemKey ?? null,
|
|
90
|
+
selectedMessageId: run.selectedMessageId ?? null,
|
|
91
|
+
claimedMessageId: run.claimedMessageId ?? null,
|
|
92
|
+
branchName: run.branchName ?? null,
|
|
93
|
+
prUrl: run.prUrl ?? null,
|
|
94
|
+
summary: run.summary ?? null,
|
|
95
|
+
error: run.error ?? null,
|
|
96
|
+
errorCategory: run.errorCategory ?? null,
|
|
97
|
+
commitSha: run.commitSha ?? null,
|
|
98
|
+
changedPaths: run.changedPaths ?? [],
|
|
99
|
+
finishedAt: run.finishedAt ?? null
|
|
100
|
+
});
|
|
101
|
+
await this.execute(
|
|
102
|
+
`INSERT OR REPLACE INTO runtime_records (record_type, record_key, lookup_key, secondary_key, status, schema_version, created_at, updated_at, payload_json, meta_json) VALUES ('agent_run', ${toSqlValue(run.runId)}, ${toSqlValue(run.agentSlug)}, ${toSqlValue(run.commitSha ?? null)}, ${toSqlValue(run.status)}, ${TRESEED_ENVELOPE_SCHEMA_VERSION}, ${toSqlValue(run.startedAt)}, ${toSqlValue(run.finishedAt ?? run.startedAt)}, ${toSqlValue(JSON.stringify(envelope.payload))}, ${toSqlValue(JSON.stringify(envelope.meta))})`
|
|
103
|
+
);
|
|
104
|
+
return this.getByKey(run.runId);
|
|
105
|
+
}
|
|
106
|
+
await this.execute(
|
|
107
|
+
`INSERT OR REPLACE INTO agent_runs (run_id, agent_slug, handler_kind, trigger_kind, trigger_source, claimed_message_id, status, selected_item_key, selected_message_id, branch_name, pr_url, summary, error, error_category, commit_sha, changed_paths, started_at, finished_at) VALUES (${toSqlValue(run.runId)}, ${toSqlValue(run.agentSlug)}, ${toSqlValue(run.handlerKind ?? null)}, ${toSqlValue(run.triggerKind ?? null)}, ${toSqlValue(run.triggerSource)}, ${toSqlValue(run.claimedMessageId ?? null)}, ${toSqlValue(run.status)}, ${toSqlValue(run.selectedItemKey)}, ${toSqlValue(run.selectedMessageId)}, ${toSqlValue(run.branchName)}, ${toSqlValue(run.prUrl)}, ${toSqlValue(run.summary)}, ${toSqlValue(run.error)}, ${toSqlValue(run.errorCategory ?? null)}, ${toSqlValue(run.commitSha ?? null)}, ${toSqlValue(JSON.stringify(run.changedPaths ?? []))}, ${toSqlValue(run.startedAt)}, ${toSqlValue(run.finishedAt)})`
|
|
108
|
+
);
|
|
109
|
+
return run;
|
|
110
|
+
}
|
|
111
|
+
async update(request) {
|
|
112
|
+
return this.record({
|
|
113
|
+
run: {
|
|
114
|
+
...request.data,
|
|
115
|
+
runId: request.data.runId ?? request.id ?? request.key
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function runFilterColumn(field) {
|
|
121
|
+
switch (field) {
|
|
122
|
+
case "runId":
|
|
123
|
+
case "run_id":
|
|
124
|
+
return "record_key";
|
|
125
|
+
case "agentSlug":
|
|
126
|
+
case "agent_slug":
|
|
127
|
+
return "lookup_key";
|
|
128
|
+
case "status":
|
|
129
|
+
return "status";
|
|
130
|
+
case "commitSha":
|
|
131
|
+
case "commit_sha":
|
|
132
|
+
return "secondary_key";
|
|
133
|
+
case "startedAt":
|
|
134
|
+
case "started_at":
|
|
135
|
+
return "created_at";
|
|
136
|
+
case "finishedAt":
|
|
137
|
+
case "finished_at":
|
|
138
|
+
return "json_extract(payload_json, '$.finishedAt')";
|
|
139
|
+
default:
|
|
140
|
+
return `json_extract(payload_json, '$.${field}')`;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function runSortColumn(field) {
|
|
144
|
+
switch (field) {
|
|
145
|
+
case "runId":
|
|
146
|
+
case "run_id":
|
|
147
|
+
return "record_key";
|
|
148
|
+
case "agentSlug":
|
|
149
|
+
case "agent_slug":
|
|
150
|
+
return "lookup_key";
|
|
151
|
+
case "startedAt":
|
|
152
|
+
case "started_at":
|
|
153
|
+
return "created_at";
|
|
154
|
+
case "commitSha":
|
|
155
|
+
case "commit_sha":
|
|
156
|
+
return "secondary_key";
|
|
157
|
+
case "status":
|
|
158
|
+
return "status";
|
|
159
|
+
default:
|
|
160
|
+
return "created_at";
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
export {
|
|
164
|
+
RunStore,
|
|
165
|
+
runFromRecord
|
|
166
|
+
};
|