@autono/pinbox-core 0.1.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/README.md +17 -0
- package/dist/auth/verify.d.ts +15 -0
- package/dist/auth/verify.js +2 -0
- package/dist/connectors/github.d.ts +5 -0
- package/dist/connectors/github.js +49 -0
- package/dist/connectors/index.d.ts +37 -0
- package/dist/connectors/index.js +103 -0
- package/dist/context-BHcEpzVb.js +30 -0
- package/dist/delivery/openclaw.d.ts +10 -0
- package/dist/delivery/openclaw.js +50 -0
- package/dist/delivery/resume.d.ts +23 -0
- package/dist/delivery/resume.js +102 -0
- package/dist/delivery/router.d.ts +2 -0
- package/dist/delivery/router.js +262 -0
- package/dist/delivery/webhook.d.ts +9 -0
- package/dist/delivery/webhook.js +52 -0
- package/dist/do.d.ts +67 -0
- package/dist/do.js +713 -0
- package/dist/hub-QYz6OqYQ.js +328 -0
- package/dist/hub-server.d.ts +44 -0
- package/dist/hub-server.js +251 -0
- package/dist/hub.d.ts +125 -0
- package/dist/hub.js +2 -0
- package/dist/markdown.d.ts +14 -0
- package/dist/markdown.js +87 -0
- package/dist/payload-m1DbRWDD.d.ts +6 -0
- package/dist/poll-BrcAuaAz.js +252 -0
- package/dist/proc-BMp_pbPS.js +42 -0
- package/dist/router-D3dDjIaD.d.ts +62 -0
- package/dist/schema-BOTmn5SM.d.ts +297 -0
- package/dist/schema.d.ts +2 -0
- package/dist/schema.js +121 -0
- package/dist/schema.json +403 -0
- package/dist/sessions-CJdMBH3C.d.ts +47 -0
- package/dist/sessions-DrCVTMfI.js +129 -0
- package/dist/sessions.d.ts +2 -0
- package/dist/sessions.js +2 -0
- package/dist/store-DHbwWu93.d.ts +105 -0
- package/dist/store-DM8MjB8M.js +479 -0
- package/dist/store.d.ts +2 -0
- package/dist/store.js +3 -0
- package/dist/types-BLNQb7MH.d.ts +28 -0
- package/dist/verify-BDt9d9Np.js +55 -0
- package/dist/ws-protocol.d.ts +61 -0
- package/dist/ws-protocol.js +51 -0
- package/dist/ws.d.ts +7 -0
- package/dist/ws.js +1 -0
- package/package.json +104 -0
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
import { LinkSchema, PinInputSchema, PinSchema, SessionRefSchema, ThreadMessageSchema } from "./schema.js";
|
|
2
|
+
import { a as NotFoundError, i as ConflictError, n as SqliteSessionStore, o as newId } from "./sessions-DrCVTMfI.js";
|
|
3
|
+
import { Database } from "bun:sqlite";
|
|
4
|
+
//#region src/store-deliveries.ts
|
|
5
|
+
var Row = class {
|
|
6
|
+
id;
|
|
7
|
+
event_seq;
|
|
8
|
+
session_id;
|
|
9
|
+
adapter;
|
|
10
|
+
status;
|
|
11
|
+
attempts;
|
|
12
|
+
due_at;
|
|
13
|
+
last_error;
|
|
14
|
+
updated_at;
|
|
15
|
+
};
|
|
16
|
+
var MaxSeqRow = class {
|
|
17
|
+
last_event_seq;
|
|
18
|
+
};
|
|
19
|
+
function toDeliveryRow(row) {
|
|
20
|
+
return {
|
|
21
|
+
id: row.id,
|
|
22
|
+
eventSeq: row.event_seq,
|
|
23
|
+
sessionId: row.session_id,
|
|
24
|
+
adapter: row.adapter,
|
|
25
|
+
status: row.status,
|
|
26
|
+
attempts: row.attempts,
|
|
27
|
+
dueAt: row.due_at,
|
|
28
|
+
lastError: row.last_error,
|
|
29
|
+
updatedAt: row.updated_at
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const COLUMNS = "id, event_seq, session_id, adapter, status, attempts, due_at, last_error, updated_at";
|
|
33
|
+
var SqliteDeliveryStore = class {
|
|
34
|
+
insert;
|
|
35
|
+
selectById;
|
|
36
|
+
selectDue;
|
|
37
|
+
selectForSession;
|
|
38
|
+
selectUnassigned;
|
|
39
|
+
updateAssign;
|
|
40
|
+
updateDelivered;
|
|
41
|
+
updateFailed;
|
|
42
|
+
selectMaxSeq;
|
|
43
|
+
constructor(db) {
|
|
44
|
+
this.insert = db.query(`INSERT INTO deliveries (event_seq, session_id, adapter, status, attempts, due_at, last_error, updated_at)
|
|
45
|
+
VALUES (?, ?, ?, ?, 0, ?, NULL, ?)`);
|
|
46
|
+
this.selectById = db.query(`SELECT ${COLUMNS} FROM deliveries WHERE id = ?`).as(Row);
|
|
47
|
+
this.selectDue = db.query(`SELECT ${COLUMNS} FROM deliveries
|
|
48
|
+
WHERE status = 'pending' AND (due_at IS NULL OR due_at <= ?) ORDER BY id ASC`).as(Row);
|
|
49
|
+
this.selectForSession = db.query(`SELECT ${COLUMNS} FROM deliveries
|
|
50
|
+
WHERE status = 'pending' AND session_id = ? ORDER BY id ASC`).as(Row);
|
|
51
|
+
this.selectUnassigned = db.query(`SELECT ${COLUMNS} FROM deliveries
|
|
52
|
+
WHERE status = 'pending' AND session_id IS NULL ORDER BY id ASC`).as(Row);
|
|
53
|
+
this.updateAssign = db.query("UPDATE deliveries SET session_id = ?, updated_at = ? WHERE id = ?");
|
|
54
|
+
this.updateDelivered = db.query("UPDATE deliveries SET status = 'delivered', updated_at = ? WHERE id = ?");
|
|
55
|
+
this.updateFailed = db.query(`UPDATE deliveries SET status = ?, attempts = attempts + 1, due_at = ?, last_error = ?, updated_at = ?
|
|
56
|
+
WHERE id = ?`);
|
|
57
|
+
this.selectMaxSeq = db.query("SELECT COALESCE(MAX(event_seq), 0) AS last_event_seq FROM deliveries").as(MaxSeqRow);
|
|
58
|
+
}
|
|
59
|
+
enqueue(row) {
|
|
60
|
+
const result = this.insert.run(row.eventSeq, row.sessionId, row.adapter, row.status ?? "pending", row.dueAt, (/* @__PURE__ */ new Date()).toISOString());
|
|
61
|
+
return this.mustGet(Number(result.lastInsertRowid));
|
|
62
|
+
}
|
|
63
|
+
due(now) {
|
|
64
|
+
return this.selectDue.all(now).map(toDeliveryRow);
|
|
65
|
+
}
|
|
66
|
+
pendingForSession(sessionId) {
|
|
67
|
+
return this.selectForSession.all(sessionId).map(toDeliveryRow);
|
|
68
|
+
}
|
|
69
|
+
unassigned() {
|
|
70
|
+
return this.selectUnassigned.all().map(toDeliveryRow);
|
|
71
|
+
}
|
|
72
|
+
assign(id, sessionId) {
|
|
73
|
+
this.updateAssign.run(sessionId, (/* @__PURE__ */ new Date()).toISOString(), id);
|
|
74
|
+
}
|
|
75
|
+
markDelivered(id) {
|
|
76
|
+
this.updateDelivered.run((/* @__PURE__ */ new Date()).toISOString(), id);
|
|
77
|
+
}
|
|
78
|
+
markFailed(id, error, retryAt) {
|
|
79
|
+
this.updateFailed.run(retryAt === null ? "failed" : "pending", retryAt, error, (/* @__PURE__ */ new Date()).toISOString(), id);
|
|
80
|
+
}
|
|
81
|
+
lastEventSeq() {
|
|
82
|
+
return this.selectMaxSeq.get()?.last_event_seq ?? 0;
|
|
83
|
+
}
|
|
84
|
+
mustGet(id) {
|
|
85
|
+
const row = this.selectById.get(id);
|
|
86
|
+
if (!row) throw new Error(`delivery row not found after insert: ${id}`);
|
|
87
|
+
return toDeliveryRow(row);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
const MIGRATIONS = [
|
|
91
|
+
{
|
|
92
|
+
version: 1,
|
|
93
|
+
ddl: `
|
|
94
|
+
CREATE TABLE IF NOT EXISTS events (
|
|
95
|
+
seq INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
96
|
+
type TEXT NOT NULL,
|
|
97
|
+
at TEXT NOT NULL,
|
|
98
|
+
payload TEXT NOT NULL
|
|
99
|
+
);
|
|
100
|
+
CREATE TABLE IF NOT EXISTS pins (
|
|
101
|
+
id TEXT PRIMARY KEY,
|
|
102
|
+
status TEXT NOT NULL,
|
|
103
|
+
created_at TEXT NOT NULL,
|
|
104
|
+
due_at TEXT,
|
|
105
|
+
json TEXT NOT NULL
|
|
106
|
+
);
|
|
107
|
+
CREATE TABLE IF NOT EXISTS thread_messages (
|
|
108
|
+
id TEXT PRIMARY KEY,
|
|
109
|
+
pin_id TEXT NOT NULL,
|
|
110
|
+
at TEXT NOT NULL,
|
|
111
|
+
json TEXT NOT NULL
|
|
112
|
+
);
|
|
113
|
+
CREATE INDEX IF NOT EXISTS pins_due_at ON pins(due_at) WHERE due_at IS NOT NULL;
|
|
114
|
+
`
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
version: 2,
|
|
118
|
+
ddl: `
|
|
119
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
120
|
+
id TEXT PRIMARY KEY, agent TEXT NOT NULL, key TEXT NOT NULL, cwd TEXT,
|
|
121
|
+
registered_at TEXT NOT NULL, last_seen_at TEXT NOT NULL, ended_at TEXT, json TEXT NOT NULL
|
|
122
|
+
);
|
|
123
|
+
CREATE UNIQUE INDEX IF NOT EXISTS sessions_agent_key ON sessions(agent, key);
|
|
124
|
+
CREATE TABLE IF NOT EXISTS deliveries (
|
|
125
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT, event_seq INTEGER NOT NULL, session_id TEXT,
|
|
126
|
+
adapter TEXT NOT NULL, status TEXT NOT NULL, attempts INTEGER NOT NULL DEFAULT 0,
|
|
127
|
+
due_at TEXT, last_error TEXT, updated_at TEXT NOT NULL
|
|
128
|
+
);
|
|
129
|
+
CREATE INDEX IF NOT EXISTS deliveries_due ON deliveries(due_at) WHERE status = 'pending';
|
|
130
|
+
`
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
version: 3,
|
|
134
|
+
ddl: `
|
|
135
|
+
CREATE TABLE IF NOT EXISTS cursors (
|
|
136
|
+
consumer_id TEXT PRIMARY KEY, last_seq INTEGER NOT NULL, updated_at TEXT NOT NULL
|
|
137
|
+
);
|
|
138
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS pins_fts USING fts5(pin_id UNINDEXED, kind UNINDEXED, body, tokenize='unicode61');
|
|
139
|
+
`
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
version: 4,
|
|
143
|
+
ddl: `
|
|
144
|
+
CREATE TABLE IF NOT EXISTS links (
|
|
145
|
+
pin_id TEXT NOT NULL, connector TEXT NOT NULL, ref TEXT NOT NULL, url TEXT NOT NULL,
|
|
146
|
+
created_at TEXT NOT NULL, last_synced_at TEXT,
|
|
147
|
+
PRIMARY KEY (pin_id, connector, ref)
|
|
148
|
+
);
|
|
149
|
+
`
|
|
150
|
+
}
|
|
151
|
+
];
|
|
152
|
+
var UserVersionRow = class {
|
|
153
|
+
user_version;
|
|
154
|
+
};
|
|
155
|
+
function migrate(db) {
|
|
156
|
+
db.transaction(() => {
|
|
157
|
+
const current = db.query("PRAGMA user_version").as(UserVersionRow).get()?.user_version ?? 0;
|
|
158
|
+
for (const migration of MIGRATIONS) {
|
|
159
|
+
if (migration.version <= current) continue;
|
|
160
|
+
db.exec(migration.ddl);
|
|
161
|
+
db.exec(`PRAGMA user_version = ${migration.version}`);
|
|
162
|
+
}
|
|
163
|
+
}).immediate();
|
|
164
|
+
}
|
|
165
|
+
function backfillFts(db) {
|
|
166
|
+
const fts = db.query("SELECT COUNT(*) AS n FROM pins_fts").get();
|
|
167
|
+
const pins = db.query("SELECT COUNT(*) AS n FROM pins").get();
|
|
168
|
+
if (fts.n > 0 || pins.n === 0) return;
|
|
169
|
+
db.transaction(() => {
|
|
170
|
+
db.exec("INSERT INTO pins_fts (pin_id, kind, body) SELECT id, 'pin', json_extract(json, '$.text') FROM pins");
|
|
171
|
+
db.exec("INSERT INTO pins_fts (pin_id, kind, body) SELECT pin_id, 'msg', json_extract(json, '$.text') FROM thread_messages");
|
|
172
|
+
})();
|
|
173
|
+
}
|
|
174
|
+
var JsonRow = class {
|
|
175
|
+
json;
|
|
176
|
+
};
|
|
177
|
+
var EventRow = class {
|
|
178
|
+
seq;
|
|
179
|
+
type;
|
|
180
|
+
at;
|
|
181
|
+
payload;
|
|
182
|
+
};
|
|
183
|
+
var SummaryRow = class {
|
|
184
|
+
open;
|
|
185
|
+
resolved;
|
|
186
|
+
lastEventSeq;
|
|
187
|
+
};
|
|
188
|
+
var CursorRow = class {
|
|
189
|
+
last_seq;
|
|
190
|
+
};
|
|
191
|
+
var LinkRow = class {
|
|
192
|
+
pin_id;
|
|
193
|
+
connector;
|
|
194
|
+
ref;
|
|
195
|
+
url;
|
|
196
|
+
last_synced_at;
|
|
197
|
+
};
|
|
198
|
+
function rowToLink(row) {
|
|
199
|
+
return LinkSchema.parse({
|
|
200
|
+
connector: row.connector,
|
|
201
|
+
ref: row.ref,
|
|
202
|
+
url: row.url
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
function parsePin(json) {
|
|
206
|
+
return PinSchema.parse(JSON.parse(json));
|
|
207
|
+
}
|
|
208
|
+
function openStore(path) {
|
|
209
|
+
const db = new Database(path);
|
|
210
|
+
if (path !== ":memory:") db.exec("PRAGMA journal_mode=WAL;");
|
|
211
|
+
migrate(db);
|
|
212
|
+
backfillFts(db);
|
|
213
|
+
return new SqlitePinStore(db);
|
|
214
|
+
}
|
|
215
|
+
var SqlitePinStore = class {
|
|
216
|
+
db;
|
|
217
|
+
sessions;
|
|
218
|
+
deliveries;
|
|
219
|
+
insertEvent;
|
|
220
|
+
insertPin;
|
|
221
|
+
selectPin;
|
|
222
|
+
selectAllPins;
|
|
223
|
+
selectPinsByStatus;
|
|
224
|
+
updatePin;
|
|
225
|
+
updateDueAt;
|
|
226
|
+
selectDuePins;
|
|
227
|
+
insertMessage;
|
|
228
|
+
selectThread;
|
|
229
|
+
selectEventsAfter;
|
|
230
|
+
selectSummary;
|
|
231
|
+
insertFts;
|
|
232
|
+
selectSearch;
|
|
233
|
+
insertLink;
|
|
234
|
+
selectLinksForPin;
|
|
235
|
+
selectAllLinks;
|
|
236
|
+
updateLinkSynced;
|
|
237
|
+
cursors;
|
|
238
|
+
links;
|
|
239
|
+
listeners = /* @__PURE__ */ new Set();
|
|
240
|
+
pendingEvents = [];
|
|
241
|
+
constructor(db) {
|
|
242
|
+
this.db = db;
|
|
243
|
+
this.sessions = new SqliteSessionStore(db);
|
|
244
|
+
this.deliveries = new SqliteDeliveryStore(db);
|
|
245
|
+
this.insertEvent = db.query("INSERT INTO events (type, at, payload) VALUES (?, ?, ?)");
|
|
246
|
+
this.insertPin = db.query("INSERT INTO pins (id, status, created_at, json) VALUES (?, ?, ?, ?)");
|
|
247
|
+
this.selectPin = db.query("SELECT json FROM pins WHERE id = ?").as(JsonRow);
|
|
248
|
+
this.selectAllPins = db.query("SELECT json FROM pins ORDER BY created_at DESC, rowid DESC").as(JsonRow);
|
|
249
|
+
this.selectPinsByStatus = db.query("SELECT json FROM pins WHERE status = ? ORDER BY created_at DESC, rowid DESC").as(JsonRow);
|
|
250
|
+
this.updatePin = db.query("UPDATE pins SET status = ?, json = ? WHERE id = ?");
|
|
251
|
+
this.updateDueAt = db.query("UPDATE pins SET due_at = ? WHERE id = ?");
|
|
252
|
+
this.selectDuePins = db.query("SELECT json FROM pins WHERE due_at IS NOT NULL AND due_at <= ? ORDER BY due_at ASC").as(JsonRow);
|
|
253
|
+
this.insertMessage = db.query("INSERT INTO thread_messages (id, pin_id, at, json) VALUES (?, ?, ?, ?)");
|
|
254
|
+
this.selectThread = db.query("SELECT json FROM thread_messages WHERE pin_id = ? ORDER BY rowid ASC").as(JsonRow);
|
|
255
|
+
this.selectEventsAfter = db.query("SELECT seq, type, at, payload FROM events WHERE seq > ? ORDER BY seq ASC").as(EventRow);
|
|
256
|
+
this.selectSummary = db.query(`SELECT
|
|
257
|
+
(SELECT COUNT(*) FROM pins WHERE status = 'open') AS open,
|
|
258
|
+
(SELECT COUNT(*) FROM pins WHERE status = 'resolved') AS resolved,
|
|
259
|
+
(SELECT COALESCE(MAX(seq), 0) FROM events) AS lastEventSeq`).as(SummaryRow);
|
|
260
|
+
this.insertFts = db.query("INSERT INTO pins_fts (pin_id, kind, body) VALUES (?, ?, ?)");
|
|
261
|
+
this.selectSearch = db.query(`SELECT json FROM pins
|
|
262
|
+
WHERE id IN (SELECT DISTINCT pin_id FROM pins_fts WHERE pins_fts MATCH ?)
|
|
263
|
+
ORDER BY created_at DESC, rowid DESC`).as(JsonRow);
|
|
264
|
+
const selectCursor = db.query("SELECT last_seq FROM cursors WHERE consumer_id = ?").as(CursorRow);
|
|
265
|
+
const upsertCursor = db.query(`INSERT INTO cursors (consumer_id, last_seq, updated_at) VALUES (?, ?, ?)
|
|
266
|
+
ON CONFLICT(consumer_id) DO UPDATE SET last_seq = excluded.last_seq, updated_at = excluded.updated_at`);
|
|
267
|
+
this.cursors = {
|
|
268
|
+
get: (consumerId) => selectCursor.get(consumerId)?.last_seq ?? 0,
|
|
269
|
+
set: (consumerId, lastSeq) => {
|
|
270
|
+
upsertCursor.run(consumerId, lastSeq, (/* @__PURE__ */ new Date()).toISOString());
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
this.insertLink = db.query("INSERT INTO links (pin_id, connector, ref, url, created_at) VALUES (?, ?, ?, ?, ?)");
|
|
274
|
+
this.selectLinksForPin = db.query("SELECT pin_id, connector, ref, url, last_synced_at FROM links WHERE pin_id = ? ORDER BY rowid ASC").as(LinkRow);
|
|
275
|
+
this.selectAllLinks = db.query("SELECT pin_id, connector, ref, url, last_synced_at FROM links ORDER BY rowid ASC").as(LinkRow);
|
|
276
|
+
this.updateLinkSynced = db.query("UPDATE links SET last_synced_at = ? WHERE pin_id = ? AND connector = ? AND ref = ?");
|
|
277
|
+
this.links = {
|
|
278
|
+
forPin: (pinId) => this.selectLinksForPin.all(pinId).map(rowToLink),
|
|
279
|
+
all: () => this.selectAllLinks.all().map((row) => ({
|
|
280
|
+
pinId: row.pin_id,
|
|
281
|
+
link: rowToLink(row),
|
|
282
|
+
lastSyncedAt: row.last_synced_at
|
|
283
|
+
})),
|
|
284
|
+
markSynced: (pinId, link, at) => {
|
|
285
|
+
this.updateLinkSynced.run(at, pinId, link.connector, link.ref);
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
subscribe(listener) {
|
|
290
|
+
this.listeners.add(listener);
|
|
291
|
+
return () => {
|
|
292
|
+
this.listeners.delete(listener);
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
createPin(input, env) {
|
|
296
|
+
const parsed = PinInputSchema.parse(input);
|
|
297
|
+
const mergedEnv = { ...parsed.env };
|
|
298
|
+
if (env.branch !== void 0) mergedEnv.branch = env.branch;
|
|
299
|
+
if (env.commit !== void 0) mergedEnv.commit = env.commit;
|
|
300
|
+
const pin = PinSchema.parse({
|
|
301
|
+
...parsed,
|
|
302
|
+
env: mergedEnv,
|
|
303
|
+
id: newId("pin"),
|
|
304
|
+
schemaVersion: 1,
|
|
305
|
+
status: "open",
|
|
306
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
307
|
+
});
|
|
308
|
+
this.mutate(() => {
|
|
309
|
+
this.insertPin.run(pin.id, pin.status, pin.createdAt, JSON.stringify(pin));
|
|
310
|
+
this.insertFts.run(pin.id, "pin", pin.text);
|
|
311
|
+
this.logEvent("pin.created", pin.createdAt, pin);
|
|
312
|
+
});
|
|
313
|
+
return pin;
|
|
314
|
+
}
|
|
315
|
+
getPin(id) {
|
|
316
|
+
const row = this.selectPin.get(id);
|
|
317
|
+
return row ? parsePin(row.json) : null;
|
|
318
|
+
}
|
|
319
|
+
listPins(filter) {
|
|
320
|
+
return (filter?.status ? this.selectPinsByStatus.all(filter.status) : this.selectAllPins.all()).map((r) => parsePin(r.json));
|
|
321
|
+
}
|
|
322
|
+
addThreadMessage(pinId, role, text, opts) {
|
|
323
|
+
const message = ThreadMessageSchema.parse({
|
|
324
|
+
id: newId("msg"),
|
|
325
|
+
pinId,
|
|
326
|
+
role,
|
|
327
|
+
...opts?.origin !== void 0 ? { origin: opts.origin } : {},
|
|
328
|
+
text,
|
|
329
|
+
...opts?.attachments !== void 0 ? { attachments: opts.attachments } : {},
|
|
330
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
331
|
+
});
|
|
332
|
+
this.mutate(() => {
|
|
333
|
+
this.mustGetPin(pinId);
|
|
334
|
+
this.insertMessage.run(message.id, message.pinId, message.at, JSON.stringify(message));
|
|
335
|
+
this.insertFts.run(message.pinId, "msg", message.text);
|
|
336
|
+
this.logEvent("thread.message", message.at, message);
|
|
337
|
+
});
|
|
338
|
+
return message;
|
|
339
|
+
}
|
|
340
|
+
getThread(pinId) {
|
|
341
|
+
return this.selectThread.all(pinId).map((r) => ThreadMessageSchema.parse(JSON.parse(r.json)));
|
|
342
|
+
}
|
|
343
|
+
resolvePin(id, by, note, commit) {
|
|
344
|
+
let resolved;
|
|
345
|
+
this.mutate(() => {
|
|
346
|
+
const pin = this.mustGetPin(id);
|
|
347
|
+
if (pin.status === "resolved") throw new ConflictError(`pin already resolved: ${id}`);
|
|
348
|
+
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
349
|
+
resolved = PinSchema.parse({
|
|
350
|
+
...pin,
|
|
351
|
+
status: "resolved",
|
|
352
|
+
resolution: {
|
|
353
|
+
by,
|
|
354
|
+
...note !== void 0 ? { note } : {},
|
|
355
|
+
...commit !== void 0 ? { commit } : {},
|
|
356
|
+
at
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
this.updatePin.run(resolved.status, JSON.stringify(resolved), id);
|
|
360
|
+
this.logEvent("pin.resolved", at, resolved);
|
|
361
|
+
});
|
|
362
|
+
return resolved;
|
|
363
|
+
}
|
|
364
|
+
bindSession(pinId, ref) {
|
|
365
|
+
const parsedRef = SessionRefSchema.parse(ref);
|
|
366
|
+
let bound;
|
|
367
|
+
this.mutate(() => {
|
|
368
|
+
const pin = this.mustGetPin(pinId);
|
|
369
|
+
bound = PinSchema.parse({
|
|
370
|
+
...pin,
|
|
371
|
+
agentSession: parsedRef
|
|
372
|
+
});
|
|
373
|
+
this.updatePin.run(bound.status, JSON.stringify(bound), pinId);
|
|
374
|
+
});
|
|
375
|
+
return bound;
|
|
376
|
+
}
|
|
377
|
+
verifyPin(id, outcome) {
|
|
378
|
+
let verified;
|
|
379
|
+
this.mutate(() => {
|
|
380
|
+
const pin = this.mustGetPin(id);
|
|
381
|
+
if (pin.status !== "resolved") throw new ConflictError(`pin not resolved: ${id}`);
|
|
382
|
+
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
383
|
+
verified = PinSchema.parse({
|
|
384
|
+
...pin,
|
|
385
|
+
status: outcome === "reopened" ? "open" : "resolved",
|
|
386
|
+
verification: {
|
|
387
|
+
outcome,
|
|
388
|
+
at
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
this.updatePin.run(verified.status, JSON.stringify(verified), id);
|
|
392
|
+
this.logEvent("pin.verified", at, verified);
|
|
393
|
+
});
|
|
394
|
+
return verified;
|
|
395
|
+
}
|
|
396
|
+
searchPins(query) {
|
|
397
|
+
const phrase = `"${query.replaceAll("\"", "\"\"")}"`;
|
|
398
|
+
return this.selectSearch.all(phrase).map((r) => parsePin(r.json));
|
|
399
|
+
}
|
|
400
|
+
addLink(pinId, link) {
|
|
401
|
+
const parsed = LinkSchema.parse(link);
|
|
402
|
+
let updated;
|
|
403
|
+
this.mutate(() => {
|
|
404
|
+
const pin = this.mustGetPin(pinId);
|
|
405
|
+
const existing = pin.links ?? [];
|
|
406
|
+
if (existing.some((l) => l.connector === parsed.connector && l.ref === parsed.ref)) throw new ConflictError(`pin already linked: ${pinId} → ${parsed.connector}#${parsed.ref}`);
|
|
407
|
+
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
408
|
+
updated = PinSchema.parse({
|
|
409
|
+
...pin,
|
|
410
|
+
links: [...existing, parsed]
|
|
411
|
+
});
|
|
412
|
+
this.insertLink.run(pinId, parsed.connector, parsed.ref, parsed.url, at);
|
|
413
|
+
this.updatePin.run(updated.status, JSON.stringify(updated), pinId);
|
|
414
|
+
this.logEvent("pin.linked", at, updated);
|
|
415
|
+
});
|
|
416
|
+
return updated;
|
|
417
|
+
}
|
|
418
|
+
setDueAt(id, at) {
|
|
419
|
+
if (this.updateDueAt.run(at, id).changes === 0) throw new NotFoundError(`pin not found: ${id}`);
|
|
420
|
+
}
|
|
421
|
+
pinsDueBefore(at) {
|
|
422
|
+
return this.selectDuePins.all(at).map((r) => parsePin(r.json));
|
|
423
|
+
}
|
|
424
|
+
eventsAfter(seq) {
|
|
425
|
+
return this.selectEventsAfter.all(seq).map((row) => ({
|
|
426
|
+
seq: row.seq,
|
|
427
|
+
type: row.type,
|
|
428
|
+
at: row.at,
|
|
429
|
+
payload: JSON.parse(row.payload)
|
|
430
|
+
}));
|
|
431
|
+
}
|
|
432
|
+
summary() {
|
|
433
|
+
const row = this.selectSummary.get();
|
|
434
|
+
return {
|
|
435
|
+
open: row?.open ?? 0,
|
|
436
|
+
resolved: row?.resolved ?? 0,
|
|
437
|
+
lastEventSeq: row?.lastEventSeq ?? 0
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
close() {
|
|
441
|
+
this.db.close();
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Runs a mutation inside one immediate transaction (two-writer SQLITE_BUSY case:
|
|
445
|
+
* daemon + stray CLI call, deep-dive §1.11), then flushes events collected by
|
|
446
|
+
* logEvent to subscribers — post-commit, in seq order, on the mutating call's stack.
|
|
447
|
+
*/
|
|
448
|
+
mutate(mutation) {
|
|
449
|
+
let result;
|
|
450
|
+
try {
|
|
451
|
+
result = this.db.transaction(mutation).immediate();
|
|
452
|
+
} catch (cause) {
|
|
453
|
+
this.pendingEvents = [];
|
|
454
|
+
throw cause;
|
|
455
|
+
}
|
|
456
|
+
const events = this.pendingEvents;
|
|
457
|
+
this.pendingEvents = [];
|
|
458
|
+
for (const event of events) for (const listener of this.listeners) try {
|
|
459
|
+
listener(event);
|
|
460
|
+
} catch {}
|
|
461
|
+
return result;
|
|
462
|
+
}
|
|
463
|
+
logEvent(type, at, payload) {
|
|
464
|
+
const result = this.insertEvent.run(type, at, JSON.stringify(payload));
|
|
465
|
+
this.pendingEvents.push({
|
|
466
|
+
seq: Number(result.lastInsertRowid),
|
|
467
|
+
type,
|
|
468
|
+
at,
|
|
469
|
+
payload
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
mustGetPin(id) {
|
|
473
|
+
const pin = this.getPin(id);
|
|
474
|
+
if (!pin) throw new NotFoundError(`pin not found: ${id}`);
|
|
475
|
+
return pin;
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
//#endregion
|
|
479
|
+
export { openStore as n, MIGRATIONS as t };
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as LinkStore, c as PinStore, d as ConflictError, f as NotFoundError, i as DeliveryStore, l as StoredEvent, n as DeliveryRow, o as MIGRATIONS, r as DeliveryStatus, s as Migration, t as CursorStore, u as openStore } from "./store-DHbwWu93.js";
|
|
2
|
+
export { ConflictError, CursorStore, DeliveryRow, DeliveryStatus, DeliveryStore, LinkStore, MIGRATIONS, Migration, NotFoundError, PinStore, StoredEvent, openStore };
|
package/dist/store.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { a as Pin, m as ThreadMessage, r as Link } from "./schema-BOTmn5SM.js";
|
|
2
|
+
//#region src/connectors/types.d.ts
|
|
3
|
+
/** Host-injected transport: local = `gh` CLI shell-out (impl in packages/cli, Bun.$); Worker = App token fetch. */
|
|
4
|
+
interface ConnectorTransport {
|
|
5
|
+
request(op: string, params: Record<string, unknown>): Promise<unknown>;
|
|
6
|
+
}
|
|
7
|
+
type RemoteComment = {
|
|
8
|
+
origin: string;
|
|
9
|
+
text: string;
|
|
10
|
+
at: string;
|
|
11
|
+
};
|
|
12
|
+
type RemoteStatus = "open" | "closed";
|
|
13
|
+
/** Hub-supplied sinks — the connector reports inbound activity, core writes it. */
|
|
14
|
+
type ConnectorEvents = {
|
|
15
|
+
onRemoteComment(link: Link, comment: RemoteComment): Promise<void>;
|
|
16
|
+
onRemoteStatus(link: Link, status: RemoteStatus): Promise<void>;
|
|
17
|
+
};
|
|
18
|
+
interface Connector {
|
|
19
|
+
readonly name: string;
|
|
20
|
+
createItem(pin: Pin, thread: ThreadMessage[]): Promise<Link>;
|
|
21
|
+
postComment(link: Link, message: ThreadMessage): Promise<void>;
|
|
22
|
+
/** Inbound: poll (local, scheduled via pins.due_at + drainDue) or webhook-parse (Worker route); reports through the sinks; caller marks links.markSynced. */
|
|
23
|
+
sync(link: Link, events: ConnectorEvents): Promise<void>;
|
|
24
|
+
/** The outbound half of the status rule; the Slack connector implements it too. */
|
|
25
|
+
setRemoteStatus(link: Link, status: RemoteStatus): Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
//#endregion
|
|
28
|
+
export { RemoteStatus as a, RemoteComment as i, ConnectorEvents as n, ConnectorTransport as r, Connector as t };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { createRemoteJWKSet, jwtVerify } from "jose";
|
|
2
|
+
//#region src/auth/jwt.ts
|
|
3
|
+
function verifyJwt(opts) {
|
|
4
|
+
const jwks = createRemoteJWKSet(new URL(opts.jwksUrl));
|
|
5
|
+
return async (req) => {
|
|
6
|
+
const token = bearerToken(req);
|
|
7
|
+
if (token === null) return null;
|
|
8
|
+
try {
|
|
9
|
+
const { payload } = await jwtVerify(token, jwks, {
|
|
10
|
+
issuer: opts.issuer,
|
|
11
|
+
audience: opts.audience,
|
|
12
|
+
algorithms: ["EdDSA", "RS256"]
|
|
13
|
+
});
|
|
14
|
+
if (typeof payload["sub"] !== "string") return null;
|
|
15
|
+
const identity = { userId: payload["sub"] };
|
|
16
|
+
if (typeof payload["tenant"] === "string") identity.tenantId = payload["tenant"];
|
|
17
|
+
if (typeof payload["name"] === "string") identity.name = payload["name"];
|
|
18
|
+
if (typeof payload["email"] === "string") identity.email = payload["email"];
|
|
19
|
+
return identity;
|
|
20
|
+
} catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function bearerToken(req) {
|
|
26
|
+
const header = req.headers.get("authorization");
|
|
27
|
+
if (header === null || !header.startsWith("Bearer ")) return null;
|
|
28
|
+
return header.slice(7);
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region src/auth/verify.ts
|
|
32
|
+
function verifyNone() {
|
|
33
|
+
return async () => ({ userId: "anonymous" });
|
|
34
|
+
}
|
|
35
|
+
function verifyToken(expected) {
|
|
36
|
+
return async (req) => {
|
|
37
|
+
const token = bearerToken(req);
|
|
38
|
+
if (token === null) return null;
|
|
39
|
+
return await digestsEqual(token, expected) ? { userId: "token" } : null;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function verifyCustom(fn) {
|
|
43
|
+
return fn;
|
|
44
|
+
}
|
|
45
|
+
async function digestsEqual(a, b) {
|
|
46
|
+
const encoder = new TextEncoder();
|
|
47
|
+
const [da, db] = await Promise.all([crypto.subtle.digest("SHA-256", encoder.encode(a)), crypto.subtle.digest("SHA-256", encoder.encode(b))]);
|
|
48
|
+
const ua = new Uint8Array(da);
|
|
49
|
+
const ub = new Uint8Array(db);
|
|
50
|
+
let diff = 0;
|
|
51
|
+
for (let i = 0; i < ua.length; i++) diff |= (ua[i] ?? 0) ^ (ub[i] ?? 0);
|
|
52
|
+
return diff === 0;
|
|
53
|
+
}
|
|
54
|
+
//#endregion
|
|
55
|
+
export { verifyJwt as i, verifyNone as n, verifyToken as r, verifyCustom as t };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { l as StoredEvent } from "./store-DHbwWu93.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
//#region src/ws-protocol.d.ts
|
|
4
|
+
declare const WS_PATH = "/ws";
|
|
5
|
+
declare const WS_PROTOCOL_VERSION = 1;
|
|
6
|
+
declare const WS_MIN_PROTOCOL = 1;
|
|
7
|
+
declare const WS_TOKEN_SUBPROTOCOL_PREFIX = "pinbox.token.";
|
|
8
|
+
declare const WS_CLOSE_PROTOCOL = 4400;
|
|
9
|
+
declare const WS_CLOSE_UNAUTHORIZED = 4401;
|
|
10
|
+
declare const ClientHelloSchema: z.ZodObject<{
|
|
11
|
+
type: z.ZodLiteral<"hello">;
|
|
12
|
+
protocol: z.ZodNumber;
|
|
13
|
+
consumerId: z.ZodString;
|
|
14
|
+
lastSeq: z.ZodNumber;
|
|
15
|
+
}, z.core.$strip>;
|
|
16
|
+
declare const WsEventSchema: z.ZodObject<{
|
|
17
|
+
type: z.ZodLiteral<"event">;
|
|
18
|
+
seq: z.ZodNumber;
|
|
19
|
+
eventType: z.ZodEnum<{
|
|
20
|
+
"pin.created": "pin.created";
|
|
21
|
+
"pin.linked": "pin.linked";
|
|
22
|
+
"pin.resolved": "pin.resolved";
|
|
23
|
+
"pin.verified": "pin.verified";
|
|
24
|
+
"thread.message": "thread.message";
|
|
25
|
+
}>;
|
|
26
|
+
at: z.ZodString;
|
|
27
|
+
payload: z.ZodUnknown;
|
|
28
|
+
}, z.core.$strip>;
|
|
29
|
+
declare const ServerCatchUpSchema: z.ZodObject<{
|
|
30
|
+
type: z.ZodLiteral<"catch-up">;
|
|
31
|
+
protocol: z.ZodLiteral<1>;
|
|
32
|
+
minProtocol: z.ZodLiteral<1>;
|
|
33
|
+
lastSeq: z.ZodNumber;
|
|
34
|
+
events: z.ZodArray<z.ZodObject<{
|
|
35
|
+
type: z.ZodLiteral<"event">;
|
|
36
|
+
seq: z.ZodNumber;
|
|
37
|
+
eventType: z.ZodEnum<{
|
|
38
|
+
"pin.created": "pin.created";
|
|
39
|
+
"pin.linked": "pin.linked";
|
|
40
|
+
"pin.resolved": "pin.resolved";
|
|
41
|
+
"pin.verified": "pin.verified";
|
|
42
|
+
"thread.message": "thread.message";
|
|
43
|
+
}>;
|
|
44
|
+
at: z.ZodString;
|
|
45
|
+
payload: z.ZodUnknown;
|
|
46
|
+
}, z.core.$strip>>;
|
|
47
|
+
}, z.core.$strip>;
|
|
48
|
+
declare const ServerErrorSchema: z.ZodObject<{
|
|
49
|
+
type: z.ZodLiteral<"error">;
|
|
50
|
+
code: z.ZodEnum<{
|
|
51
|
+
E_INVALID_INPUT: "E_INVALID_INPUT";
|
|
52
|
+
E_WS_PROTOCOL: "E_WS_PROTOCOL";
|
|
53
|
+
}>;
|
|
54
|
+
message: z.ZodString;
|
|
55
|
+
}, z.core.$strip>;
|
|
56
|
+
type WsClientMessage = z.infer<typeof ClientHelloSchema>;
|
|
57
|
+
type WsServerMessage = z.infer<typeof ServerCatchUpSchema | typeof WsEventSchema | typeof ServerErrorSchema>;
|
|
58
|
+
/** One WsEvent frame, JSON. */
|
|
59
|
+
declare function encodeWsEvent(event: StoredEvent): string;
|
|
60
|
+
//#endregion
|
|
61
|
+
export { ClientHelloSchema, ServerCatchUpSchema, ServerErrorSchema, WS_CLOSE_PROTOCOL, WS_CLOSE_UNAUTHORIZED, WS_MIN_PROTOCOL, WS_PATH, WS_PROTOCOL_VERSION, WS_TOKEN_SUBPROTOCOL_PREFIX, WsClientMessage, WsEventSchema, WsServerMessage, encodeWsEvent };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
//#region src/ws-protocol.ts
|
|
3
|
+
const WS_PATH = "/ws";
|
|
4
|
+
const WS_PROTOCOL_VERSION = 1;
|
|
5
|
+
const WS_MIN_PROTOCOL = 1;
|
|
6
|
+
const WS_TOKEN_SUBPROTOCOL_PREFIX = "pinbox.token.";
|
|
7
|
+
const WS_CLOSE_PROTOCOL = 4400;
|
|
8
|
+
const WS_CLOSE_UNAUTHORIZED = 4401;
|
|
9
|
+
const ClientHelloSchema = z.object({
|
|
10
|
+
type: z.literal("hello"),
|
|
11
|
+
protocol: z.number().int().positive(),
|
|
12
|
+
consumerId: z.string().min(1),
|
|
13
|
+
lastSeq: z.number().int().nonnegative()
|
|
14
|
+
});
|
|
15
|
+
const WsEventSchema = z.object({
|
|
16
|
+
type: z.literal("event"),
|
|
17
|
+
seq: z.number().int().positive(),
|
|
18
|
+
eventType: z.enum([
|
|
19
|
+
"pin.created",
|
|
20
|
+
"pin.resolved",
|
|
21
|
+
"thread.message",
|
|
22
|
+
"pin.verified",
|
|
23
|
+
"pin.linked"
|
|
24
|
+
]),
|
|
25
|
+
at: z.string(),
|
|
26
|
+
payload: z.unknown()
|
|
27
|
+
});
|
|
28
|
+
const ServerCatchUpSchema = z.object({
|
|
29
|
+
type: z.literal("catch-up"),
|
|
30
|
+
protocol: z.literal(1),
|
|
31
|
+
minProtocol: z.literal(1),
|
|
32
|
+
lastSeq: z.number().int().nonnegative(),
|
|
33
|
+
events: z.array(WsEventSchema)
|
|
34
|
+
});
|
|
35
|
+
const ServerErrorSchema = z.object({
|
|
36
|
+
type: z.literal("error"),
|
|
37
|
+
code: z.enum(["E_WS_PROTOCOL", "E_INVALID_INPUT"]),
|
|
38
|
+
message: z.string()
|
|
39
|
+
});
|
|
40
|
+
/** One WsEvent frame, JSON. */
|
|
41
|
+
function encodeWsEvent(event) {
|
|
42
|
+
return JSON.stringify({
|
|
43
|
+
type: "event",
|
|
44
|
+
seq: event.seq,
|
|
45
|
+
eventType: event.type,
|
|
46
|
+
at: event.at,
|
|
47
|
+
payload: event.payload
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
export { ClientHelloSchema, ServerCatchUpSchema, ServerErrorSchema, WS_CLOSE_PROTOCOL, WS_CLOSE_UNAUTHORIZED, WS_MIN_PROTOCOL, WS_PATH, WS_PROTOCOL_VERSION, WS_TOKEN_SUBPROTOCOL_PREFIX, WsEventSchema, encodeWsEvent };
|
package/dist/ws.d.ts
ADDED
package/dist/ws.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|