@tangle-network/agent-app 0.1.2 → 0.1.4

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.
Files changed (39) hide show
  1. package/dist/billing/index.d.ts +94 -1
  2. package/dist/billing/index.js +3 -1
  3. package/dist/chunk-5RV6CAHZ.js +137 -0
  4. package/dist/chunk-5RV6CAHZ.js.map +1 -0
  5. package/dist/{chunk-7P6VIHI4.js → chunk-AQ2BOPOQ.js} +8 -2
  6. package/dist/{chunk-7P6VIHI4.js.map → chunk-AQ2BOPOQ.js.map} +1 -1
  7. package/dist/{chunk-45MYQ3GD.js → chunk-EAJSWUU5.js} +56 -1
  8. package/dist/chunk-EAJSWUU5.js.map +1 -0
  9. package/dist/chunk-EEPJGZJW.js +102 -0
  10. package/dist/chunk-EEPJGZJW.js.map +1 -0
  11. package/dist/chunk-EZXN67KE.js +318 -0
  12. package/dist/chunk-EZXN67KE.js.map +1 -0
  13. package/dist/{chunk-SIDR6BH3.js → chunk-ZJGY7OMZ.js} +47 -2
  14. package/dist/chunk-ZJGY7OMZ.js.map +1 -0
  15. package/dist/chunk-ZXNXAQAH.js +69 -0
  16. package/dist/chunk-ZXNXAQAH.js.map +1 -0
  17. package/dist/config/index.d.ts +367 -0
  18. package/dist/config/index.js +9 -0
  19. package/dist/config/index.js.map +1 -0
  20. package/dist/crypto/index.d.ts +36 -1
  21. package/dist/crypto/index.js +13 -3
  22. package/dist/delegation/index.d.ts +16 -1
  23. package/dist/delegation/index.js +5 -3
  24. package/dist/index.d.ts +10 -4
  25. package/dist/index.js +61 -9
  26. package/dist/knowledge/index.d.ts +90 -0
  27. package/dist/knowledge/index.js +9 -0
  28. package/dist/knowledge/index.js.map +1 -0
  29. package/dist/knowledge-loop/index.d.ts +208 -0
  30. package/dist/knowledge-loop/index.js +11 -0
  31. package/dist/knowledge-loop/index.js.map +1 -0
  32. package/dist/model-BOP69mVu.d.ts +35 -0
  33. package/dist/preset-cloudflare/index.d.ts +244 -0
  34. package/dist/preset-cloudflare/index.js +23 -0
  35. package/dist/preset-cloudflare/index.js.map +1 -0
  36. package/dist/runtime/index.d.ts +2 -35
  37. package/package.json +36 -5
  38. package/dist/chunk-45MYQ3GD.js.map +0 -1
  39. package/dist/chunk-SIDR6BH3.js.map +0 -1
@@ -0,0 +1,318 @@
1
+ import {
2
+ createWorkspaceKeyManager
3
+ } from "./chunk-EAJSWUU5.js";
4
+ import {
5
+ createFieldCrypto
6
+ } from "./chunk-ZJGY7OMZ.js";
7
+
8
+ // src/preset-cloudflare/index.ts
9
+ var PRESET_TABLES = {
10
+ proposals: {
11
+ name: "proposals",
12
+ columns: {
13
+ id: "id",
14
+ workspaceId: "workspace_id",
15
+ threadId: "thread_id",
16
+ type: "type",
17
+ title: "title",
18
+ description: "description",
19
+ status: "status",
20
+ createdBy: "created_by",
21
+ createdAt: "created_at"
22
+ }
23
+ },
24
+ threads: {
25
+ name: "threads",
26
+ columns: {
27
+ id: "id",
28
+ workspaceId: "workspace_id",
29
+ title: "title",
30
+ createdAt: "created_at"
31
+ }
32
+ },
33
+ knowledge: {
34
+ name: "knowledge",
35
+ columns: {
36
+ id: "id",
37
+ workspaceId: "workspace_id",
38
+ path: "path",
39
+ kind: "kind",
40
+ label: "label",
41
+ content: "content",
42
+ createdAt: "created_at"
43
+ }
44
+ },
45
+ deadlines: {
46
+ name: "deadlines",
47
+ columns: {
48
+ id: "id",
49
+ workspaceId: "workspace_id",
50
+ threadId: "thread_id",
51
+ title: "title",
52
+ dueDate: "due_date",
53
+ priority: "priority",
54
+ status: "status",
55
+ createdAt: "created_at"
56
+ }
57
+ },
58
+ workspaceKeys: {
59
+ name: "workspace_keys",
60
+ columns: {
61
+ id: "id",
62
+ workspaceId: "workspace_id",
63
+ keyId: "key_id",
64
+ keyEncrypted: "key_encrypted",
65
+ budgetUsd: "budget_usd",
66
+ expiresAt: "expires_at",
67
+ revokedAt: "revoked_at",
68
+ createdAt: "created_at"
69
+ }
70
+ }
71
+ };
72
+ var PRESET_MIGRATION_SQL = [
73
+ `CREATE TABLE IF NOT EXISTS threads (
74
+ id TEXT PRIMARY KEY,
75
+ workspace_id TEXT NOT NULL,
76
+ title TEXT,
77
+ created_at INTEGER NOT NULL
78
+ )`,
79
+ `CREATE TABLE IF NOT EXISTS proposals (
80
+ id TEXT PRIMARY KEY,
81
+ workspace_id TEXT NOT NULL,
82
+ thread_id TEXT,
83
+ type TEXT NOT NULL,
84
+ title TEXT NOT NULL,
85
+ description TEXT,
86
+ status TEXT NOT NULL DEFAULT 'pending',
87
+ created_by TEXT,
88
+ created_at INTEGER NOT NULL
89
+ )`,
90
+ `CREATE TABLE IF NOT EXISTS knowledge (
91
+ id TEXT PRIMARY KEY,
92
+ workspace_id TEXT NOT NULL,
93
+ path TEXT NOT NULL,
94
+ kind TEXT NOT NULL,
95
+ label TEXT,
96
+ content TEXT,
97
+ created_at INTEGER NOT NULL
98
+ )`,
99
+ `CREATE TABLE IF NOT EXISTS deadlines (
100
+ id TEXT PRIMARY KEY,
101
+ workspace_id TEXT NOT NULL,
102
+ thread_id TEXT,
103
+ title TEXT NOT NULL,
104
+ due_date TEXT NOT NULL,
105
+ priority TEXT,
106
+ status TEXT NOT NULL DEFAULT 'scheduled',
107
+ created_at INTEGER NOT NULL
108
+ )`,
109
+ `CREATE TABLE IF NOT EXISTS workspace_keys (
110
+ id TEXT PRIMARY KEY,
111
+ workspace_id TEXT NOT NULL,
112
+ key_id TEXT NOT NULL,
113
+ key_encrypted TEXT NOT NULL,
114
+ budget_usd REAL NOT NULL,
115
+ expires_at INTEGER NOT NULL,
116
+ revoked_at INTEGER,
117
+ created_at INTEGER NOT NULL
118
+ )`,
119
+ `CREATE INDEX IF NOT EXISTS idx_proposals_ws ON proposals (workspace_id, status)`,
120
+ `CREATE INDEX IF NOT EXISTS idx_deadlines_ws ON deadlines (workspace_id, status)`,
121
+ `CREATE INDEX IF NOT EXISTS idx_knowledge_ws ON knowledge (workspace_id)`,
122
+ `CREATE INDEX IF NOT EXISTS idx_workspace_keys_ws ON workspace_keys (workspace_id, revoked_at)`
123
+ ];
124
+ function createPresetDrizzleSchema(d) {
125
+ const { sqliteTable, text, integer, real } = d;
126
+ const C = PRESET_TABLES;
127
+ return {
128
+ threads: sqliteTable(C.threads.name, {
129
+ id: text(C.threads.columns.id).primaryKey(),
130
+ workspaceId: text(C.threads.columns.workspaceId).notNull(),
131
+ title: text(C.threads.columns.title),
132
+ createdAt: integer(C.threads.columns.createdAt).notNull()
133
+ }),
134
+ proposals: sqliteTable(C.proposals.name, {
135
+ id: text(C.proposals.columns.id).primaryKey(),
136
+ workspaceId: text(C.proposals.columns.workspaceId).notNull(),
137
+ threadId: text(C.proposals.columns.threadId),
138
+ type: text(C.proposals.columns.type).notNull(),
139
+ title: text(C.proposals.columns.title).notNull(),
140
+ description: text(C.proposals.columns.description),
141
+ status: text(C.proposals.columns.status).notNull().default("pending"),
142
+ createdBy: text(C.proposals.columns.createdBy),
143
+ createdAt: integer(C.proposals.columns.createdAt).notNull()
144
+ }),
145
+ knowledge: sqliteTable(C.knowledge.name, {
146
+ id: text(C.knowledge.columns.id).primaryKey(),
147
+ workspaceId: text(C.knowledge.columns.workspaceId).notNull(),
148
+ path: text(C.knowledge.columns.path).notNull(),
149
+ kind: text(C.knowledge.columns.kind).notNull(),
150
+ label: text(C.knowledge.columns.label),
151
+ content: text(C.knowledge.columns.content),
152
+ createdAt: integer(C.knowledge.columns.createdAt).notNull()
153
+ }),
154
+ deadlines: sqliteTable(C.deadlines.name, {
155
+ id: text(C.deadlines.columns.id).primaryKey(),
156
+ workspaceId: text(C.deadlines.columns.workspaceId).notNull(),
157
+ threadId: text(C.deadlines.columns.threadId),
158
+ title: text(C.deadlines.columns.title).notNull(),
159
+ dueDate: text(C.deadlines.columns.dueDate).notNull(),
160
+ priority: text(C.deadlines.columns.priority),
161
+ status: text(C.deadlines.columns.status).notNull().default("scheduled"),
162
+ createdAt: integer(C.deadlines.columns.createdAt).notNull()
163
+ }),
164
+ workspaceKeys: sqliteTable(C.workspaceKeys.name, {
165
+ id: text(C.workspaceKeys.columns.id).primaryKey(),
166
+ workspaceId: text(C.workspaceKeys.columns.workspaceId).notNull(),
167
+ keyId: text(C.workspaceKeys.columns.keyId).notNull(),
168
+ keyEncrypted: text(C.workspaceKeys.columns.keyEncrypted).notNull(),
169
+ budgetUsd: real(C.workspaceKeys.columns.budgetUsd).notNull(),
170
+ expiresAt: integer(C.workspaceKeys.columns.expiresAt).notNull(),
171
+ revokedAt: integer(C.workspaceKeys.columns.revokedAt),
172
+ createdAt: integer(C.workspaceKeys.columns.createdAt).notNull()
173
+ })
174
+ };
175
+ }
176
+ function slug(value) {
177
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64) || "item";
178
+ }
179
+ function createPresetToolHandlers(opts) {
180
+ const { db, vault } = opts;
181
+ const newId = opts.newId ?? (() => crypto.randomUUID());
182
+ const now = opts.now ?? (() => Date.now());
183
+ const uiPrefix = opts.uiPathPrefix ?? "ui";
184
+ const citationPrefix = opts.citationPathPrefix ?? "citations";
185
+ const P = PRESET_TABLES.proposals;
186
+ const D = PRESET_TABLES.deadlines;
187
+ const K = PRESET_TABLES.knowledge;
188
+ async function persistArtifact(path, body) {
189
+ await vault.put(path, body);
190
+ }
191
+ async function insertKnowledge(workspaceId, path, kind, label, content) {
192
+ await db.prepare(
193
+ `INSERT INTO ${K.name} (${K.columns.id}, ${K.columns.workspaceId}, ${K.columns.path}, ${K.columns.kind}, ${K.columns.label}, ${K.columns.content}, ${K.columns.createdAt}) VALUES (?, ?, ?, ?, ?, ?, ?)`
194
+ ).bind(newId(), workspaceId, path, kind, label, content, now()).run();
195
+ }
196
+ return {
197
+ async submitProposal(args, ctx) {
198
+ const existing = await db.prepare(`SELECT ${P.columns.id} AS id FROM ${P.name} WHERE ${P.columns.workspaceId} = ? AND ${P.columns.title} = ? LIMIT 1`).bind(ctx.workspaceId, args.title).first();
199
+ if (existing) return { proposalId: existing.id, deduped: true };
200
+ const id = newId();
201
+ await db.prepare(
202
+ `INSERT INTO ${P.name} (${P.columns.id}, ${P.columns.workspaceId}, ${P.columns.threadId}, ${P.columns.type}, ${P.columns.title}, ${P.columns.description}, ${P.columns.status}, ${P.columns.createdBy}, ${P.columns.createdAt}) VALUES (?, ?, ?, ?, ?, ?, 'pending', ?, ?)`
203
+ ).bind(id, ctx.workspaceId, ctx.threadId, args.type, args.title, args.description ?? null, ctx.userId, now()).run();
204
+ return { proposalId: id, deduped: false };
205
+ },
206
+ async scheduleFollowup(args, ctx) {
207
+ const existing = await db.prepare(
208
+ `SELECT ${D.columns.id} AS id, ${D.columns.dueDate} AS dueDate FROM ${D.name} WHERE ${D.columns.workspaceId} = ? AND ${D.columns.title} = ? AND ${D.columns.dueDate} = ? LIMIT 1`
209
+ ).bind(ctx.workspaceId, args.title, args.dueDate).first();
210
+ if (existing) return { id: existing.id, dueDate: existing.dueDate, deduped: true };
211
+ const id = newId();
212
+ await db.prepare(
213
+ `INSERT INTO ${D.name} (${D.columns.id}, ${D.columns.workspaceId}, ${D.columns.threadId}, ${D.columns.title}, ${D.columns.dueDate}, ${D.columns.priority}, ${D.columns.status}, ${D.columns.createdAt}) VALUES (?, ?, ?, ?, ?, ?, 'scheduled', ?)`
214
+ ).bind(id, ctx.workspaceId, ctx.threadId, args.title, args.dueDate, args.priority ?? null, now()).run();
215
+ return { id, dueDate: args.dueDate, deduped: false };
216
+ },
217
+ async renderUi(args, ctx) {
218
+ const content = JSON.stringify(args.schema);
219
+ const path = `${uiPrefix}/${ctx.threadId ?? "global"}/${slug(args.title)}.json`;
220
+ await persistArtifact(path, content);
221
+ await insertKnowledge(ctx.workspaceId, path, "ui", args.title, content);
222
+ return { path, content };
223
+ },
224
+ async addCitation(args, ctx) {
225
+ const citationId = newId();
226
+ const path = `${citationPrefix}/${slug(args.label ?? args.path)}-${citationId.slice(0, 8)}.json`;
227
+ const body = JSON.stringify({ sourcePath: args.path, quote: args.quote, label: args.label ?? null });
228
+ await persistArtifact(path, body);
229
+ await insertKnowledge(ctx.workspaceId, path, "citation", args.label ?? null, body);
230
+ return { citationId, path };
231
+ }
232
+ };
233
+ }
234
+ function readDotPath(obj, path) {
235
+ let cur = obj;
236
+ for (const part of path.split(".")) {
237
+ if (cur == null || typeof cur !== "object") return void 0;
238
+ cur = cur[part];
239
+ }
240
+ return cur;
241
+ }
242
+ function createD1KnowledgeStateAccessor(opts) {
243
+ const { db, workspaceId } = opts;
244
+ const defaultWhere = opts.defaultWhereColumn ?? "workspace_id";
245
+ const configFn = typeof opts.config === "function" ? opts.config : (path) => readDotPath(opts.config, path);
246
+ const isIdentifier = (s) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(s);
247
+ return {
248
+ config: configFn,
249
+ async count(query) {
250
+ if (!isIdentifier(query.table)) throw new Error(`unsafe table identifier: ${query.table}`);
251
+ const whereCol = query.where ?? defaultWhere;
252
+ if (!isIdentifier(whereCol)) throw new Error(`unsafe where identifier: ${whereCol}`);
253
+ let sql = `SELECT count(*) AS n FROM ${query.table} WHERE ${whereCol} = ?`;
254
+ const binds = [workspaceId];
255
+ if (query.statusIn && query.statusIn.length > 0) {
256
+ sql += ` AND status IN (${query.statusIn.map(() => "?").join(", ")})`;
257
+ binds.push(...query.statusIn);
258
+ }
259
+ const row = await db.prepare(sql).bind(...binds).first();
260
+ return row?.n ?? 0;
261
+ }
262
+ };
263
+ }
264
+ function createPresetFieldCrypto(key) {
265
+ return createFieldCrypto(key);
266
+ }
267
+ function createPresetWorkspaceKeyStore(db) {
268
+ const W = PRESET_TABLES.workspaceKeys;
269
+ return {
270
+ async getActive(workspaceId) {
271
+ const row = await db.prepare(
272
+ `SELECT ${W.columns.id} AS id, ${W.columns.keyId} AS keyId, ${W.columns.keyEncrypted} AS keyEncrypted, ${W.columns.budgetUsd} AS budgetUsd, ${W.columns.expiresAt} AS expiresAt FROM ${W.name} WHERE ${W.columns.workspaceId} = ? AND ${W.columns.revokedAt} IS NULL ORDER BY ${W.columns.createdAt} DESC LIMIT 1`
273
+ ).bind(workspaceId).first();
274
+ if (!row) return null;
275
+ return {
276
+ id: row.id,
277
+ keyId: row.keyId,
278
+ keyEncrypted: row.keyEncrypted,
279
+ budgetUsd: row.budgetUsd,
280
+ expiresAt: row.expiresAt == null ? null : new Date(row.expiresAt)
281
+ };
282
+ },
283
+ async listActive(workspaceId) {
284
+ const res = await db.prepare(`SELECT ${W.columns.id} AS id, ${W.columns.keyId} AS keyId FROM ${W.name} WHERE ${W.columns.workspaceId} = ? AND ${W.columns.revokedAt} IS NULL`).bind(workspaceId).all();
285
+ return res.results;
286
+ },
287
+ async insert(record) {
288
+ await db.prepare(
289
+ `INSERT INTO ${W.name} (${W.columns.id}, ${W.columns.workspaceId}, ${W.columns.keyId}, ${W.columns.keyEncrypted}, ${W.columns.budgetUsd}, ${W.columns.expiresAt}, ${W.columns.revokedAt}, ${W.columns.createdAt}) VALUES (?, ?, ?, ?, ?, ?, NULL, ?)`
290
+ ).bind(crypto.randomUUID(), record.workspaceId, record.keyId, record.keyEncrypted, record.budgetUsd, record.expiresAt.getTime(), Date.now()).run();
291
+ },
292
+ async markRevoked(id, now) {
293
+ await db.prepare(`UPDATE ${W.name} SET ${W.columns.revokedAt} = ? WHERE ${W.columns.id} = ?`).bind(now.getTime(), id).run();
294
+ }
295
+ };
296
+ }
297
+ function createPresetWorkspaceKeyManager(opts) {
298
+ return createWorkspaceKeyManager({
299
+ provisioner: opts.provisioner,
300
+ store: createPresetWorkspaceKeyStore(opts.db),
301
+ crypto: createPresetFieldCrypto(opts.encryptionKey),
302
+ defaultBudgetUsd: opts.defaultBudgetUsd,
303
+ now: opts.now,
304
+ product: opts.product
305
+ });
306
+ }
307
+
308
+ export {
309
+ PRESET_TABLES,
310
+ PRESET_MIGRATION_SQL,
311
+ createPresetDrizzleSchema,
312
+ createPresetToolHandlers,
313
+ createD1KnowledgeStateAccessor,
314
+ createPresetFieldCrypto,
315
+ createPresetWorkspaceKeyStore,
316
+ createPresetWorkspaceKeyManager
317
+ };
318
+ //# sourceMappingURL=chunk-EZXN67KE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/preset-cloudflare/index.ts"],"sourcesContent":["/**\n * `@tangle-network/agent-app/preset-cloudflare` — the batteries-included default\n * stack.\n *\n * Every fleet agent runs the SAME backend: Cloudflare D1 (SQLite) through\n * Drizzle for state, a KV namespace as the artifact vault, AES-GCM field crypto\n * for PII, and per-workspace budget-capped model keys. The other agent-app\n * modules are pure SEAMS — `./tools` needs an `AppToolHandlers`, `./knowledge`\n * needs a `KnowledgeStateAccessor`, `./billing` needs a `WorkspaceKeyStore` +\n * `KeyCrypto`. This module is the ONE implementation of those seams against the\n * house stack, so a consumer that runs D1 + KV stands the whole shell up with\n * config + bindings and ZERO handler code.\n *\n * Layering:\n * - Drizzle is a PEER (the consumer installs `drizzle-orm`, never bundled). The\n * schema is therefore expressed two ways that need no import here: the plain\n * DDL ({@link PRESET_MIGRATION_SQL}) a consumer runs to create the tables, and\n * a {@link createPresetDrizzleSchema} factory that takes the consumer's\n * `drizzle-orm/sqlite-core` builder module and returns the typed tables. The\n * column names in {@link PRESET_TABLES} are the contract the handlers,\n * accessor, and DDL all agree on.\n * - D1 + KV are STRUCTURAL: {@link D1Like} (Cloudflare `D1Database` satisfies it)\n * and `KvLike` from `../web` (Cloudflare `KVNamespace` satisfies it). No\n * `@cloudflare/workers-types` dependency.\n * - Crypto/billing reuse `../crypto` + `../billing` exactly — this only wires\n * them to the D1 key table.\n */\n\nimport { createFieldCrypto } from '../crypto/index'\nimport {\n createWorkspaceKeyManager,\n type KeyCrypto,\n type KeyProvisioner,\n type WorkspaceKeyManager,\n type WorkspaceKeyRecord,\n type WorkspaceKeyStore,\n} from '../billing/index'\nimport type { KnowledgeStateAccessor } from '../knowledge/index'\nimport type {\n AddCitationArgs,\n AddCitationResult,\n AppToolContext,\n AppToolHandlers,\n RenderUiArgs,\n RenderUiResult,\n ScheduleFollowupArgs,\n ScheduleFollowupResult,\n SubmitProposalArgs,\n SubmitProposalResult,\n} from '../tools/index'\nimport type { KvLike } from '../web/index'\n\n// ---------------------------------------------------------------------------\n// D1 structural seam\n//\n// The minimal surface of Cloudflare's `D1Database` the handlers + accessor use.\n// `D1Database` satisfies it structurally, so the consumer passes `env.DB`\n// directly and tests pass an in-memory fake. We keep it to the\n// prepare/bind/first/run/all shape D1 already exposes — no Drizzle here, so the\n// default handlers run on a fresh Worker with only the D1 binding.\n// ---------------------------------------------------------------------------\n\n/** A prepared, bound D1 statement. */\nexport interface D1PreparedLike {\n bind(...values: unknown[]): D1PreparedLike\n first<T = Record<string, unknown>>(colName?: string): Promise<T | null>\n run(): Promise<unknown>\n all<T = Record<string, unknown>>(): Promise<{ results: T[] }>\n}\n\n/** The D1 surface the preset needs. Cloudflare `D1Database` satisfies it. */\nexport interface D1Like {\n prepare(query: string): D1PreparedLike\n}\n\n// ---------------------------------------------------------------------------\n// Default schema\n//\n// The four tables the default handlers + accessor read/write. Column names are\n// the single source of truth shared by the DDL, the Drizzle factory, the\n// handlers, and the accessor. Every table is workspace-scoped on `workspace_id`\n// (the accessor's default `where` column) so the knowledge `count` rule and the\n// tool writes agree without per-consumer wiring.\n// ---------------------------------------------------------------------------\n\n/** The preset table + column names — the contract the DDL, Drizzle schema,\n * handlers, and accessor share. Exposed so a consumer can reference a column\n * without a string literal. */\nexport const PRESET_TABLES = {\n proposals: {\n name: 'proposals',\n columns: {\n id: 'id',\n workspaceId: 'workspace_id',\n threadId: 'thread_id',\n type: 'type',\n title: 'title',\n description: 'description',\n status: 'status',\n createdBy: 'created_by',\n createdAt: 'created_at',\n },\n },\n threads: {\n name: 'threads',\n columns: {\n id: 'id',\n workspaceId: 'workspace_id',\n title: 'title',\n createdAt: 'created_at',\n },\n },\n knowledge: {\n name: 'knowledge',\n columns: {\n id: 'id',\n workspaceId: 'workspace_id',\n path: 'path',\n kind: 'kind',\n label: 'label',\n content: 'content',\n createdAt: 'created_at',\n },\n },\n deadlines: {\n name: 'deadlines',\n columns: {\n id: 'id',\n workspaceId: 'workspace_id',\n threadId: 'thread_id',\n title: 'title',\n dueDate: 'due_date',\n priority: 'priority',\n status: 'status',\n createdAt: 'created_at',\n },\n },\n workspaceKeys: {\n name: 'workspace_keys',\n columns: {\n id: 'id',\n workspaceId: 'workspace_id',\n keyId: 'key_id',\n keyEncrypted: 'key_encrypted',\n budgetUsd: 'budget_usd',\n expiresAt: 'expires_at',\n revokedAt: 'revoked_at',\n createdAt: 'created_at',\n },\n },\n} as const\n\n/**\n * Plain DDL for the preset schema — run by a consumer to create the tables with\n * ZERO drizzle (`for (const sql of PRESET_MIGRATION_SQL) await db.prepare(sql).run()`,\n * or paste into a `.sql` migration). One statement per table so D1's\n * single-statement `prepare` accepts each. Matches {@link PRESET_TABLES} exactly.\n */\nexport const PRESET_MIGRATION_SQL: readonly string[] = [\n `CREATE TABLE IF NOT EXISTS threads (\n id TEXT PRIMARY KEY,\n workspace_id TEXT NOT NULL,\n title TEXT,\n created_at INTEGER NOT NULL\n )`,\n `CREATE TABLE IF NOT EXISTS proposals (\n id TEXT PRIMARY KEY,\n workspace_id TEXT NOT NULL,\n thread_id TEXT,\n type TEXT NOT NULL,\n title TEXT NOT NULL,\n description TEXT,\n status TEXT NOT NULL DEFAULT 'pending',\n created_by TEXT,\n created_at INTEGER NOT NULL\n )`,\n `CREATE TABLE IF NOT EXISTS knowledge (\n id TEXT PRIMARY KEY,\n workspace_id TEXT NOT NULL,\n path TEXT NOT NULL,\n kind TEXT NOT NULL,\n label TEXT,\n content TEXT,\n created_at INTEGER NOT NULL\n )`,\n `CREATE TABLE IF NOT EXISTS deadlines (\n id TEXT PRIMARY KEY,\n workspace_id TEXT NOT NULL,\n thread_id TEXT,\n title TEXT NOT NULL,\n due_date TEXT NOT NULL,\n priority TEXT,\n status TEXT NOT NULL DEFAULT 'scheduled',\n created_at INTEGER NOT NULL\n )`,\n `CREATE TABLE IF NOT EXISTS workspace_keys (\n id TEXT PRIMARY KEY,\n workspace_id TEXT NOT NULL,\n key_id TEXT NOT NULL,\n key_encrypted TEXT NOT NULL,\n budget_usd REAL NOT NULL,\n expires_at INTEGER NOT NULL,\n revoked_at INTEGER,\n created_at INTEGER NOT NULL\n )`,\n `CREATE INDEX IF NOT EXISTS idx_proposals_ws ON proposals (workspace_id, status)`,\n `CREATE INDEX IF NOT EXISTS idx_deadlines_ws ON deadlines (workspace_id, status)`,\n `CREATE INDEX IF NOT EXISTS idx_knowledge_ws ON knowledge (workspace_id)`,\n `CREATE INDEX IF NOT EXISTS idx_workspace_keys_ws ON workspace_keys (workspace_id, revoked_at)`,\n]\n\n/** A chainable column builder — every modifier returns the builder so calls\n * like `.notNull().default('pending')` typecheck. The concrete drizzle builders\n * satisfy this structurally. */\nexport interface DrizzleColumnLike {\n primaryKey: () => DrizzleColumnLike\n notNull: () => DrizzleColumnLike\n default: (v: unknown) => DrizzleColumnLike\n}\n\n/** The shape of a `drizzle-orm/sqlite-core` module — the few builders the\n * preset schema uses. The consumer passes the real module; agent-app never\n * imports it (it stays a peer). */\nexport interface DrizzleSqliteCoreLike {\n sqliteTable: (name: string, columns: Record<string, DrizzleColumnLike>) => unknown\n text: (name?: string) => DrizzleColumnLike\n integer: (name?: string, config?: unknown) => DrizzleColumnLike\n real: (name?: string) => DrizzleColumnLike\n}\n\n/**\n * Build the typed Drizzle schema for the preset, given the consumer's\n * `drizzle-orm/sqlite-core` module. Returns one table object per\n * {@link PRESET_TABLES} entry — pass to `drizzle(db, { schema })` for typed\n * queries, or to drizzle-kit for migration generation. agent-app never imports\n * drizzle; the builder module is the seam.\n *\n * ```ts\n * import * as d from 'drizzle-orm/sqlite-core'\n * const schema = createPresetDrizzleSchema(d)\n * ```\n */\nexport function createPresetDrizzleSchema(d: DrizzleSqliteCoreLike) {\n const { sqliteTable, text, integer, real } = d\n const C = PRESET_TABLES\n return {\n threads: sqliteTable(C.threads.name, {\n id: text(C.threads.columns.id).primaryKey(),\n workspaceId: text(C.threads.columns.workspaceId).notNull(),\n title: text(C.threads.columns.title),\n createdAt: integer(C.threads.columns.createdAt).notNull(),\n }),\n proposals: sqliteTable(C.proposals.name, {\n id: text(C.proposals.columns.id).primaryKey(),\n workspaceId: text(C.proposals.columns.workspaceId).notNull(),\n threadId: text(C.proposals.columns.threadId),\n type: text(C.proposals.columns.type).notNull(),\n title: text(C.proposals.columns.title).notNull(),\n description: text(C.proposals.columns.description),\n status: text(C.proposals.columns.status).notNull().default('pending'),\n createdBy: text(C.proposals.columns.createdBy),\n createdAt: integer(C.proposals.columns.createdAt).notNull(),\n }),\n knowledge: sqliteTable(C.knowledge.name, {\n id: text(C.knowledge.columns.id).primaryKey(),\n workspaceId: text(C.knowledge.columns.workspaceId).notNull(),\n path: text(C.knowledge.columns.path).notNull(),\n kind: text(C.knowledge.columns.kind).notNull(),\n label: text(C.knowledge.columns.label),\n content: text(C.knowledge.columns.content),\n createdAt: integer(C.knowledge.columns.createdAt).notNull(),\n }),\n deadlines: sqliteTable(C.deadlines.name, {\n id: text(C.deadlines.columns.id).primaryKey(),\n workspaceId: text(C.deadlines.columns.workspaceId).notNull(),\n threadId: text(C.deadlines.columns.threadId),\n title: text(C.deadlines.columns.title).notNull(),\n dueDate: text(C.deadlines.columns.dueDate).notNull(),\n priority: text(C.deadlines.columns.priority),\n status: text(C.deadlines.columns.status).notNull().default('scheduled'),\n createdAt: integer(C.deadlines.columns.createdAt).notNull(),\n }),\n workspaceKeys: sqliteTable(C.workspaceKeys.name, {\n id: text(C.workspaceKeys.columns.id).primaryKey(),\n workspaceId: text(C.workspaceKeys.columns.workspaceId).notNull(),\n keyId: text(C.workspaceKeys.columns.keyId).notNull(),\n keyEncrypted: text(C.workspaceKeys.columns.keyEncrypted).notNull(),\n budgetUsd: real(C.workspaceKeys.columns.budgetUsd).notNull(),\n expiresAt: integer(C.workspaceKeys.columns.expiresAt).notNull(),\n revokedAt: integer(C.workspaceKeys.columns.revokedAt),\n createdAt: integer(C.workspaceKeys.columns.createdAt).notNull(),\n }),\n }\n}\n\n// ---------------------------------------------------------------------------\n// Default AppToolHandlers over D1 + KV\n// ---------------------------------------------------------------------------\n\n/** The KV-backed vault. `KvLike` (from `../web`) is the structural KV contract;\n * Cloudflare `KVNamespace` satisfies it. Artifacts are stored under their path. */\nexport type VaultKv = KvLike\n\nexport interface PresetToolHandlerOptions {\n /** The D1 database (Cloudflare `D1Database` satisfies {@link D1Like}). */\n db: D1Like\n /** The KV namespace used as the artifact vault. */\n vault: VaultKv\n /** Id generator. Default `crypto.randomUUID`. Injectable for deterministic tests. */\n newId?: () => string\n /** Clock (epoch ms). Default `Date.now`. Injectable for deterministic tests. */\n now?: () => number\n /** Vault path prefix for `render_ui` artifacts. Default `'ui'`. */\n uiPathPrefix?: string\n /** Vault path prefix for `add_citation` artifacts. Default `'citations'`. */\n citationPathPrefix?: string\n}\n\nfunction slug(value: string): string {\n return value\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 64) || 'item'\n}\n\n/**\n * The default {@link AppToolHandlers} for the house stack:\n * - `submit_proposal` → insert a `proposals` row (`status='pending'`), deduped\n * on (workspace, title) so a retried turn doesn't double-queue.\n * - `schedule_followup` → insert a `deadlines` row, deduped on (workspace, title, due_date).\n * - `render_ui` → write the schema JSON as a `ui/<thread>/<slug>.json`\n * vault artifact AND a `knowledge` row pointing at it.\n * - `add_citation` → write the quote as a `citations/<slug>.json` artifact AND\n * a `knowledge` row.\n *\n * Returns the EXACT persisted content from `render_ui` (per the seam contract) so\n * a completion oracle sees real bytes. Pure seam wiring: a consumer that runs\n * D1 + KV gets all four tools with no handler code.\n */\nexport function createPresetToolHandlers(opts: PresetToolHandlerOptions): AppToolHandlers {\n const { db, vault } = opts\n const newId = opts.newId ?? (() => crypto.randomUUID())\n const now = opts.now ?? (() => Date.now())\n const uiPrefix = opts.uiPathPrefix ?? 'ui'\n const citationPrefix = opts.citationPathPrefix ?? 'citations'\n const P = PRESET_TABLES.proposals\n const D = PRESET_TABLES.deadlines\n const K = PRESET_TABLES.knowledge\n\n async function persistArtifact(path: string, body: string): Promise<void> {\n await vault.put(path, body)\n }\n\n async function insertKnowledge(workspaceId: string, path: string, kind: string, label: string | null, content: string): Promise<void> {\n await db\n .prepare(\n `INSERT INTO ${K.name} (${K.columns.id}, ${K.columns.workspaceId}, ${K.columns.path}, ${K.columns.kind}, ${K.columns.label}, ${K.columns.content}, ${K.columns.createdAt}) VALUES (?, ?, ?, ?, ?, ?, ?)`,\n )\n .bind(newId(), workspaceId, path, kind, label, content, now())\n .run()\n }\n\n return {\n async submitProposal(args: SubmitProposalArgs, ctx: AppToolContext): Promise<SubmitProposalResult> {\n const existing = await db\n .prepare(`SELECT ${P.columns.id} AS id FROM ${P.name} WHERE ${P.columns.workspaceId} = ? AND ${P.columns.title} = ? LIMIT 1`)\n .bind(ctx.workspaceId, args.title)\n .first<{ id: string }>()\n if (existing) return { proposalId: existing.id, deduped: true }\n\n const id = newId()\n await db\n .prepare(\n `INSERT INTO ${P.name} (${P.columns.id}, ${P.columns.workspaceId}, ${P.columns.threadId}, ${P.columns.type}, ${P.columns.title}, ${P.columns.description}, ${P.columns.status}, ${P.columns.createdBy}, ${P.columns.createdAt}) VALUES (?, ?, ?, ?, ?, ?, 'pending', ?, ?)`,\n )\n .bind(id, ctx.workspaceId, ctx.threadId, args.type, args.title, args.description ?? null, ctx.userId, now())\n .run()\n return { proposalId: id, deduped: false }\n },\n\n async scheduleFollowup(args: ScheduleFollowupArgs, ctx: AppToolContext): Promise<ScheduleFollowupResult> {\n const existing = await db\n .prepare(\n `SELECT ${D.columns.id} AS id, ${D.columns.dueDate} AS dueDate FROM ${D.name} WHERE ${D.columns.workspaceId} = ? AND ${D.columns.title} = ? AND ${D.columns.dueDate} = ? LIMIT 1`,\n )\n .bind(ctx.workspaceId, args.title, args.dueDate)\n .first<{ id: string; dueDate: string }>()\n if (existing) return { id: existing.id, dueDate: existing.dueDate, deduped: true }\n\n const id = newId()\n await db\n .prepare(\n `INSERT INTO ${D.name} (${D.columns.id}, ${D.columns.workspaceId}, ${D.columns.threadId}, ${D.columns.title}, ${D.columns.dueDate}, ${D.columns.priority}, ${D.columns.status}, ${D.columns.createdAt}) VALUES (?, ?, ?, ?, ?, ?, 'scheduled', ?)`,\n )\n .bind(id, ctx.workspaceId, ctx.threadId, args.title, args.dueDate, args.priority ?? null, now())\n .run()\n return { id, dueDate: args.dueDate, deduped: false }\n },\n\n async renderUi(args: RenderUiArgs, ctx: AppToolContext): Promise<RenderUiResult> {\n const content = JSON.stringify(args.schema)\n const path = `${uiPrefix}/${ctx.threadId ?? 'global'}/${slug(args.title)}.json`\n await persistArtifact(path, content)\n await insertKnowledge(ctx.workspaceId, path, 'ui', args.title, content)\n return { path, content }\n },\n\n async addCitation(args: AddCitationArgs, ctx: AppToolContext): Promise<AddCitationResult> {\n const citationId = newId()\n const path = `${citationPrefix}/${slug(args.label ?? args.path)}-${citationId.slice(0, 8)}.json`\n const body = JSON.stringify({ sourcePath: args.path, quote: args.quote, label: args.label ?? null })\n await persistArtifact(path, body)\n await insertKnowledge(ctx.workspaceId, path, 'citation', args.label ?? null, body)\n return { citationId, path }\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// D1-backed KnowledgeStateAccessor\n// ---------------------------------------------------------------------------\n\nexport interface PresetKnowledgeAccessorOptions {\n db: D1Like\n /** The active workspace — every `count` is scoped to it. */\n workspaceId: string\n /** Workspace config the `satisfiedBy: { config }` rules read. A resolved\n * object (dot-path lookup), or a function the accessor calls per path. */\n config: Record<string, unknown> | ((path: string) => unknown)\n /** The default workspace fk column a `count` rule scopes on when its rule\n * omits `where`. Default `'workspace_id'` (the preset schema convention). */\n defaultWhereColumn?: string\n}\n\nfunction readDotPath(obj: Record<string, unknown>, path: string): unknown {\n let cur: unknown = obj\n for (const part of path.split('.')) {\n if (cur == null || typeof cur !== 'object') return undefined\n cur = (cur as Record<string, unknown>)[part]\n }\n return cur\n}\n\n/**\n * The {@link KnowledgeStateAccessor} over the preset D1 schema — the seam that\n * lets the declarative `satisfiedBy` rules resolve with ZERO consumer code:\n * - `config(path)` reads the supplied workspace config by dot-path.\n * - `count({ table, where, statusIn })` runs `SELECT count(*)` scoped to the\n * active workspace (the rule's `where` column, default `workspace_id`),\n * optionally filtered to `statusIn` via a parameterized `IN (...)`.\n *\n * Identifiers (table/column) are validated against a safe pattern before\n * interpolation — they originate from the product's own config, never model\n * input, but we fail loud rather than build a malformed/injectable query.\n */\nexport function createD1KnowledgeStateAccessor(opts: PresetKnowledgeAccessorOptions): KnowledgeStateAccessor {\n const { db, workspaceId } = opts\n const defaultWhere = opts.defaultWhereColumn ?? 'workspace_id'\n const configFn = typeof opts.config === 'function' ? opts.config : (path: string) => readDotPath(opts.config as Record<string, unknown>, path)\n\n const isIdentifier = (s: string) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(s)\n\n return {\n config: configFn,\n async count(query) {\n if (!isIdentifier(query.table)) throw new Error(`unsafe table identifier: ${query.table}`)\n const whereCol = query.where ?? defaultWhere\n if (!isIdentifier(whereCol)) throw new Error(`unsafe where identifier: ${whereCol}`)\n\n let sql = `SELECT count(*) AS n FROM ${query.table} WHERE ${whereCol} = ?`\n const binds: unknown[] = [workspaceId]\n if (query.statusIn && query.statusIn.length > 0) {\n sql += ` AND status IN (${query.statusIn.map(() => '?').join(', ')})`\n binds.push(...query.statusIn)\n }\n const row = await db.prepare(sql).bind(...binds).first<{ n: number }>()\n return row?.n ?? 0\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Crypto + per-workspace key wiring\n// ---------------------------------------------------------------------------\n\n/** Build the {@link KeyCrypto} the billing key store uses — AES-256-GCM field\n * crypto bound to the product's 64-char-hex `ENCRYPTION_KEY` (or a resolver).\n * This is the concrete impl behind the `../billing` `KeyCrypto` seam. */\nexport function createPresetFieldCrypto(key: string | (() => string)): KeyCrypto {\n return createFieldCrypto(key)\n}\n\n/**\n * The {@link WorkspaceKeyStore} over the preset `workspace_keys` table — the\n * persistence seam the per-workspace key manager needs. \"Active\" = a row with a\n * null `revoked_at`. Pure D1 wiring; no key minting (that's the provisioner).\n */\nexport function createPresetWorkspaceKeyStore(db: D1Like): WorkspaceKeyStore {\n const W = PRESET_TABLES.workspaceKeys\n return {\n async getActive(workspaceId: string): Promise<WorkspaceKeyRecord | null> {\n const row = await db\n .prepare(\n `SELECT ${W.columns.id} AS id, ${W.columns.keyId} AS keyId, ${W.columns.keyEncrypted} AS keyEncrypted, ${W.columns.budgetUsd} AS budgetUsd, ${W.columns.expiresAt} AS expiresAt FROM ${W.name} WHERE ${W.columns.workspaceId} = ? AND ${W.columns.revokedAt} IS NULL ORDER BY ${W.columns.createdAt} DESC LIMIT 1`,\n )\n .bind(workspaceId)\n .first<{ id: string; keyId: string; keyEncrypted: string; budgetUsd: number; expiresAt: number | null }>()\n if (!row) return null\n return {\n id: row.id,\n keyId: row.keyId,\n keyEncrypted: row.keyEncrypted,\n budgetUsd: row.budgetUsd,\n expiresAt: row.expiresAt == null ? null : new Date(row.expiresAt),\n }\n },\n async listActive(workspaceId: string): Promise<Array<{ id: string; keyId: string }>> {\n const res = await db\n .prepare(`SELECT ${W.columns.id} AS id, ${W.columns.keyId} AS keyId FROM ${W.name} WHERE ${W.columns.workspaceId} = ? AND ${W.columns.revokedAt} IS NULL`)\n .bind(workspaceId)\n .all<{ id: string; keyId: string }>()\n return res.results\n },\n async insert(record): Promise<void> {\n await db\n .prepare(\n `INSERT INTO ${W.name} (${W.columns.id}, ${W.columns.workspaceId}, ${W.columns.keyId}, ${W.columns.keyEncrypted}, ${W.columns.budgetUsd}, ${W.columns.expiresAt}, ${W.columns.revokedAt}, ${W.columns.createdAt}) VALUES (?, ?, ?, ?, ?, ?, NULL, ?)`,\n )\n .bind(crypto.randomUUID(), record.workspaceId, record.keyId, record.keyEncrypted, record.budgetUsd, record.expiresAt.getTime(), Date.now())\n .run()\n },\n async markRevoked(id: string, now: Date): Promise<void> {\n await db\n .prepare(`UPDATE ${W.name} SET ${W.columns.revokedAt} = ? WHERE ${W.columns.id} = ?`)\n .bind(now.getTime(), id)\n .run()\n },\n }\n}\n\nexport interface PresetBillingOptions {\n db: D1Like\n /** The key provisioner (`@tangle-network/tcloud`'s client satisfies it structurally). */\n provisioner: KeyProvisioner\n /** Field-crypto key (64-char hex) or resolver — encrypts the minted key at rest. */\n encryptionKey: string | (() => string)\n /** Default monthly USD allowance when a call doesn't specify one. */\n defaultBudgetUsd: number\n /** Injectable clock. */\n now?: () => Date\n /** tcloud product the key is scoped to. Default `'router'`. */\n product?: string\n}\n\n/**\n * Stand up the per-workspace budget-capped {@link WorkspaceKeyManager} on the\n * house stack: the preset `workspace_keys` D1 store + AES-GCM field crypto +\n * the consumer's tcloud provisioner. The mint/rotate/rollover/usage LOGIC lives\n * in `../billing`; this only binds it to the preset table + crypto.\n */\nexport function createPresetWorkspaceKeyManager(opts: PresetBillingOptions): WorkspaceKeyManager {\n return createWorkspaceKeyManager({\n provisioner: opts.provisioner,\n store: createPresetWorkspaceKeyStore(opts.db),\n crypto: createPresetFieldCrypto(opts.encryptionKey),\n defaultBudgetUsd: opts.defaultBudgetUsd,\n now: opts.now,\n product: opts.product,\n })\n}\n"],"mappings":"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","names":[]}
@@ -47,11 +47,56 @@ function createFieldCrypto(key) {
47
47
  decrypt: (s) => decryptAesGcm(s, resolve())
48
48
  };
49
49
  }
50
+ async function deriveKey(secret, opts) {
51
+ const salt = typeof opts.salt === "string" ? new TextEncoder().encode(opts.salt) : opts.salt;
52
+ const keyMaterial = await crypto.subtle.importKey("raw", new TextEncoder().encode(secret), "PBKDF2", false, ["deriveKey"]);
53
+ return crypto.subtle.deriveKey(
54
+ { name: "PBKDF2", salt, iterations: opts.iterations, hash: opts.hash ?? "SHA-256" },
55
+ keyMaterial,
56
+ { name: ALGORITHM, length: 256 },
57
+ false,
58
+ ["encrypt", "decrypt"]
59
+ );
60
+ }
61
+ async function encryptWithKey(plaintext, key) {
62
+ const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH));
63
+ const ciphertext = await crypto.subtle.encrypt({ name: ALGORITHM, iv }, key, new TextEncoder().encode(plaintext));
64
+ const out = new Uint8Array(IV_LENGTH + ciphertext.byteLength);
65
+ out.set(iv, 0);
66
+ out.set(new Uint8Array(ciphertext), IV_LENGTH);
67
+ return toBase64(out);
68
+ }
69
+ async function decryptWithKey(encoded, key) {
70
+ const raw = fromBase64(encoded);
71
+ const iv = raw.slice(0, IV_LENGTH);
72
+ const ciphertext = raw.slice(IV_LENGTH);
73
+ const plain = await crypto.subtle.decrypt({ name: ALGORITHM, iv }, key, ciphertext);
74
+ return new TextDecoder().decode(plain);
75
+ }
76
+ async function encryptBytes(data, key) {
77
+ const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH));
78
+ const ciphertext = await crypto.subtle.encrypt({ name: ALGORITHM, iv }, key, data);
79
+ const out = new Uint8Array(IV_LENGTH + ciphertext.byteLength);
80
+ out.set(iv, 0);
81
+ out.set(new Uint8Array(ciphertext), IV_LENGTH);
82
+ return out.buffer;
83
+ }
84
+ async function decryptBytes(data, key) {
85
+ const raw = new Uint8Array(data);
86
+ const iv = raw.slice(0, IV_LENGTH);
87
+ const ciphertext = raw.slice(IV_LENGTH);
88
+ return crypto.subtle.decrypt({ name: ALGORITHM, iv }, key, ciphertext);
89
+ }
50
90
 
51
91
  export {
52
92
  decodeHexKey,
53
93
  encryptAesGcm,
54
94
  decryptAesGcm,
55
- createFieldCrypto
95
+ createFieldCrypto,
96
+ deriveKey,
97
+ encryptWithKey,
98
+ decryptWithKey,
99
+ encryptBytes,
100
+ decryptBytes
56
101
  };
57
- //# sourceMappingURL=chunk-SIDR6BH3.js.map
102
+ //# sourceMappingURL=chunk-ZJGY7OMZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/crypto/index.ts"],"sourcesContent":["/**\n * AES-256-GCM field encryption (for PII at rest — SSN/EIN/ID numbers, secrets).\n * WebCrypto only — runs on Cloudflare Workers, Node, and the browser with no\n * Node `crypto` dependency. The 32-byte key is a PARAMETER (64-char hex); the\n * framework never reads env — the product binds its own `ENCRYPTION_KEY` (this\n * is the concrete impl behind the `KeyCrypto` seam in `../billing`).\n *\n * Wire format: base64(iv ‖ ciphertext ‖ tag) — the 12-byte IV is prepended; the\n * GCM auth tag is appended by WebCrypto inside the ciphertext.\n */\n\nconst IV_LENGTH = 12\nconst TAG_LENGTH = 16\nconst ALGORITHM = 'AES-GCM'\n\n/** Validate + decode a 64-char hex key to 32 bytes. Throws on the wrong shape so\n * a misconfigured key fails loud, never silently weakens encryption. */\nexport function decodeHexKey(keyHex: string): Uint8Array {\n if (keyHex.length !== 64) throw new Error('encryption key must be a 64-char hex string (32 bytes)')\n const bytes = new Uint8Array(32)\n for (let i = 0; i < 64; i += 2) bytes[i / 2] = parseInt(keyHex.substring(i, i + 2), 16)\n return bytes\n}\n\nasync function importKey(keyHex: string): Promise<CryptoKey> {\n const raw = decodeHexKey(keyHex)\n return crypto.subtle.importKey('raw', raw.buffer as ArrayBuffer, { name: ALGORITHM } as Algorithm, false, ['encrypt', 'decrypt'])\n}\n\nfunction toBase64(data: Uint8Array): string {\n let binary = ''\n for (let i = 0; i < data.length; i++) binary += String.fromCharCode(data[i]!)\n return btoa(binary)\n}\n\nfunction fromBase64(b64: string): Uint8Array {\n const binary = atob(b64)\n const bytes = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i)\n return bytes\n}\n\n/** Encrypt `plaintext` with AES-256-GCM under `keyHex`. Returns\n * base64(iv ‖ ciphertext ‖ tag). A fresh random IV per call. */\nexport async function encryptAesGcm(plaintext: string, keyHex: string): Promise<string> {\n const key = await importKey(keyHex)\n const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH))\n const ciphertext = await crypto.subtle.encrypt({ name: ALGORITHM, iv, tagLength: TAG_LENGTH * 8 }, key, new TextEncoder().encode(plaintext))\n const result = new Uint8Array(IV_LENGTH + ciphertext.byteLength)\n result.set(iv, 0)\n result.set(new Uint8Array(ciphertext), IV_LENGTH)\n return toBase64(result)\n}\n\n/** Decrypt a base64(iv ‖ ciphertext ‖ tag) string under `keyHex`. Throws if the\n * tag fails (tamper/wrong key). */\nexport async function decryptAesGcm(encrypted: string, keyHex: string): Promise<string> {\n const key = await importKey(keyHex)\n const data = fromBase64(encrypted)\n const iv = data.slice(0, IV_LENGTH)\n const ciphertext = data.slice(IV_LENGTH)\n const plain = await crypto.subtle.decrypt({ name: ALGORITHM, iv, tagLength: TAG_LENGTH * 8 }, key, ciphertext)\n return new TextDecoder().decode(plain)\n}\n\n/** Build a {@link import('../billing').KeyCrypto}-compatible pair bound to a key\n * (or a key-resolver, for env-backed keys resolved per call). */\nexport function createFieldCrypto(key: string | (() => string)): { encrypt(s: string): Promise<string>; decrypt(s: string): Promise<string> } {\n const resolve = typeof key === 'function' ? key : () => key\n return {\n encrypt: (s) => encryptAesGcm(s, resolve()),\n decrypt: (s) => decryptAesGcm(s, resolve()),\n }\n}\n\n/**\n * --- Passphrase-derived key path (PBKDF2 → AES-256-GCM CryptoKey) ---\n *\n * The `encryptAesGcm`/`decryptAesGcm` path takes a raw 64-char-hex key. Some\n * products instead bind a SECRET STRING (not a hex key) and derive the AES key\n * with PBKDF2 — and need a BINARY path (encrypting document bytes, not just\n * strings). Both are exposed here so a product never hand-rolls WebCrypto.\n *\n * The derivation parameters (salt, iterations) are PARAMETERS — a product pins\n * its own so the derived key bytes stay stable for data already at rest. The\n * default salt/iterations match the historical tax-agent contract, but any\n * product supplies its own via {@link DeriveKeyOptions}.\n */\n\nexport interface DeriveKeyOptions {\n /** PBKDF2 salt. A product MUST pin this — changing it changes the derived key\n * bytes and orphans every value already encrypted at rest. */\n salt: Uint8Array | string\n /** PBKDF2 iteration count. Pin it for the same reason as `salt`. */\n iterations: number\n /** PBKDF2 hash. Default `'SHA-256'`. */\n hash?: 'SHA-256' | 'SHA-384' | 'SHA-512'\n}\n\n/** Derive an AES-256-GCM `CryptoKey` from a secret string via PBKDF2. The key is\n * non-extractable and usable only for encrypt/decrypt. */\nexport async function deriveKey(secret: string, opts: DeriveKeyOptions): Promise<CryptoKey> {\n const salt = typeof opts.salt === 'string' ? new TextEncoder().encode(opts.salt) : opts.salt\n const keyMaterial = await crypto.subtle.importKey('raw', new TextEncoder().encode(secret), 'PBKDF2', false, ['deriveKey'])\n return crypto.subtle.deriveKey(\n { name: 'PBKDF2', salt: salt as BufferSource, iterations: opts.iterations, hash: opts.hash ?? 'SHA-256' },\n keyMaterial,\n { name: ALGORITHM, length: 256 },\n false,\n ['encrypt', 'decrypt'],\n )\n}\n\n/** Encrypt `plaintext` under a derived `CryptoKey`. Returns base64(iv ‖ ct ‖ tag). */\nexport async function encryptWithKey(plaintext: string, key: CryptoKey): Promise<string> {\n const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH))\n const ciphertext = await crypto.subtle.encrypt({ name: ALGORITHM, iv }, key, new TextEncoder().encode(plaintext))\n const out = new Uint8Array(IV_LENGTH + ciphertext.byteLength)\n out.set(iv, 0)\n out.set(new Uint8Array(ciphertext), IV_LENGTH)\n return toBase64(out)\n}\n\n/** Decrypt a base64(iv ‖ ct ‖ tag) string under a derived `CryptoKey`. */\nexport async function decryptWithKey(encoded: string, key: CryptoKey): Promise<string> {\n const raw = fromBase64(encoded)\n const iv = raw.slice(0, IV_LENGTH)\n const ciphertext = raw.slice(IV_LENGTH)\n const plain = await crypto.subtle.decrypt({ name: ALGORITHM, iv }, key, ciphertext)\n return new TextDecoder().decode(plain)\n}\n\n/** Encrypt binary data under a derived `CryptoKey`. Returns an ArrayBuffer:\n * 12-byte IV ‖ ciphertext ‖ 16-byte GCM tag (same wire layout as the string\n * path, raw bytes instead of base64). */\nexport async function encryptBytes(data: ArrayBuffer, key: CryptoKey): Promise<ArrayBuffer> {\n const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH))\n const ciphertext = await crypto.subtle.encrypt({ name: ALGORITHM, iv }, key, data)\n const out = new Uint8Array(IV_LENGTH + ciphertext.byteLength)\n out.set(iv, 0)\n out.set(new Uint8Array(ciphertext), IV_LENGTH)\n return out.buffer\n}\n\n/** Decrypt binary data (IV ‖ ciphertext ‖ tag) under a derived `CryptoKey`. */\nexport async function decryptBytes(data: ArrayBuffer, key: CryptoKey): Promise<ArrayBuffer> {\n const raw = new Uint8Array(data)\n const iv = raw.slice(0, IV_LENGTH)\n const ciphertext = raw.slice(IV_LENGTH)\n return crypto.subtle.decrypt({ name: ALGORITHM, iv }, key, ciphertext)\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,69 @@
1
+ // src/knowledge/index.ts
2
+ function clamp(value) {
3
+ if (!Number.isFinite(value)) return 0;
4
+ if (value < 0) return 0;
5
+ if (value > 1) return 1;
6
+ return value;
7
+ }
8
+ function buildKnowledgeRequirements(specs, signals = {}) {
9
+ return specs.map((spec) => {
10
+ const signal = signals[spec.id];
11
+ return {
12
+ id: spec.id,
13
+ description: spec.description,
14
+ requiredFor: spec.requiredFor ?? [],
15
+ category: spec.category,
16
+ acquisitionMode: spec.acquisitionMode,
17
+ importance: spec.importance ?? "blocking",
18
+ freshness: spec.freshness ?? "static",
19
+ sensitivity: spec.sensitivity ?? "private",
20
+ confidenceNeeded: spec.confidenceNeeded ?? 1,
21
+ currentConfidence: clamp(signal?.confidence ?? 0),
22
+ evidenceIds: signal?.evidence ? [signal.evidence] : [],
23
+ fallbackPolicy: spec.acquisitionMode === "ask_user" ? "ask" : "block"
24
+ };
25
+ });
26
+ }
27
+ async function deriveSignals(specs, ctx) {
28
+ const out = {};
29
+ for (const spec of specs) {
30
+ if (spec.derive) {
31
+ out[spec.id] = { confidence: clamp(await spec.derive(ctx)), evidence: spec.evidence };
32
+ } else if (spec.satisfiedBy) {
33
+ const ok = await evalRule(spec.satisfiedBy, ctx);
34
+ out[spec.id] = ok ? { confidence: 1, evidence: spec.evidence ?? describeRule(spec.satisfiedBy) } : { confidence: 0 };
35
+ } else {
36
+ out[spec.id] = { confidence: 0 };
37
+ }
38
+ }
39
+ return out;
40
+ }
41
+ async function evalRule(rule, ctx) {
42
+ if ("anyOf" in rule) {
43
+ for (const sub of rule.anyOf) if (await evalRule(sub, ctx)) return true;
44
+ return false;
45
+ }
46
+ if ("allOf" in rule) {
47
+ for (const sub of rule.allOf) if (!await evalRule(sub, ctx)) return false;
48
+ return true;
49
+ }
50
+ if ("config" in rule) {
51
+ const value = ctx.config(rule.config);
52
+ if (rule.nonEmpty) return Array.isArray(value) ? value.length > 0 : value != null && value !== "";
53
+ return value != null && value !== "" && value !== false;
54
+ }
55
+ const rows = await ctx.count({ table: rule.table, where: rule.where, statusIn: rule.statusIn });
56
+ return rows >= (rule.minRows ?? 1);
57
+ }
58
+ function describeRule(rule) {
59
+ if ("anyOf" in rule) return `anyOf(${rule.anyOf.map(describeRule).join(",")})`;
60
+ if ("allOf" in rule) return `allOf(${rule.allOf.map(describeRule).join(",")})`;
61
+ if ("config" in rule) return `config:${rule.config}`;
62
+ return `${rule.table}${rule.statusIn ? `[${rule.statusIn.join("|")}]` : ""}>=${rule.minRows ?? 1}`;
63
+ }
64
+
65
+ export {
66
+ buildKnowledgeRequirements,
67
+ deriveSignals
68
+ };
69
+ //# sourceMappingURL=chunk-ZXNXAQAH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/knowledge/index.ts"],"sourcesContent":["/**\n * Declarative knowledge-requirement gate.\n *\n * Every agent product hand-rolls the same pair: a `buildXKnowledgeRequirements`\n * declaring the requirements that gate its control loop, and a\n * `deriveXRuntimeKnowledge` that scores each one from workspace state. Across\n * the fleet those derives are uniformly \"is a config field set\" / \"are there\n * >= N rows in table T (optionally with a status filter)\" / \"any/all of the\n * above\" — data, not logic. This module makes both DATA: a spec list with\n * declarative `satisfiedBy` rules, plus a per-spec `derive` escape hatch for\n * the rare rule a declarative form can't express (e.g. an aggregate over a\n * JSON column).\n *\n * Substrate-free: the only seam is `KnowledgeStateAccessor` (a config lookup +\n * a row count), which the consumer's backend — or `agent-app/preset-cloudflare`\n * — implements. Emits `@tangle-network/agent-eval`'s `KnowledgeRequirement[]`,\n * exactly what the agent-runtime control loop consumes.\n */\n\nimport type {\n KnowledgeAcquisitionMode,\n KnowledgeFreshness,\n KnowledgeImportance,\n KnowledgeRequirement,\n KnowledgeRequirementCategory,\n KnowledgeSensitivity,\n} from '@tangle-network/agent-eval'\n\n/** A declarative rule for satisfying a requirement from workspace state. */\nexport type SatisfiedByRule =\n /** A workspace-config field (dot-path) is set. `nonEmpty` requires a\n * non-empty array/string rather than mere presence. */\n | { config: string; nonEmpty?: boolean }\n /** At least `minRows` (default 1) rows exist in `table` for the workspace,\n * optionally filtered to `statusIn`. `where` names the workspace fk column\n * the accessor scopes on (default: the accessor's convention). */\n | { table: string; where?: string; statusIn?: string[]; minRows?: number }\n | { anyOf: SatisfiedByRule[] }\n | { allOf: SatisfiedByRule[] }\n\nexport interface KnowledgeRequirementSpec {\n id: string\n description: string\n category: KnowledgeRequirementCategory\n acquisitionMode: KnowledgeAcquisitionMode\n importance?: KnowledgeImportance\n freshness?: KnowledgeFreshness\n sensitivity?: KnowledgeSensitivity\n confidenceNeeded?: number\n requiredFor?: string[]\n /** The data path — evaluated against the `KnowledgeStateAccessor`. */\n satisfiedBy?: SatisfiedByRule\n /** The escape hatch — a code derive for what a rule can't express. Wins\n * over `satisfiedBy` when both are present. Returns confidence in [0, 1]. */\n derive?: (ctx: KnowledgeStateAccessor) => number | Promise<number>\n /** Evidence id attached when satisfied (default: a description of the rule). */\n evidence?: string\n}\n\n/** The single seam a backend implements. `preset-cloudflare` provides a D1\n * implementation; a custom stack supplies its own. */\nexport interface KnowledgeStateAccessor {\n /** Resolve a workspace-config field value (dot-path), or undefined. */\n config: (path: string) => unknown\n /** Count rows in `table` for the active workspace, optionally status-filtered. */\n count: (query: { table: string; where?: string; statusIn?: string[] }) => number | Promise<number>\n}\n\nexport interface KnowledgeSignal {\n confidence: number\n evidence?: string\n}\n\nfunction clamp(value: number): number {\n if (!Number.isFinite(value)) return 0\n if (value < 0) return 0\n if (value > 1) return 1\n return value\n}\n\n/**\n * Map specs -> the runtime's `KnowledgeRequirement[]`, folding in per-spec\n * confidence from `signals` (default 0). Pure + sync: an eval harness can pass\n * hand-authored signals; production passes the output of {@link deriveSignals}.\n */\nexport function buildKnowledgeRequirements(\n specs: KnowledgeRequirementSpec[],\n signals: Record<string, KnowledgeSignal> = {},\n): KnowledgeRequirement[] {\n return specs.map((spec) => {\n const signal = signals[spec.id]\n return {\n id: spec.id,\n description: spec.description,\n requiredFor: spec.requiredFor ?? [],\n category: spec.category,\n acquisitionMode: spec.acquisitionMode,\n importance: spec.importance ?? 'blocking',\n freshness: spec.freshness ?? 'static',\n sensitivity: spec.sensitivity ?? 'private',\n confidenceNeeded: spec.confidenceNeeded ?? 1,\n currentConfidence: clamp(signal?.confidence ?? 0),\n evidenceIds: signal?.evidence ? [signal.evidence] : [],\n fallbackPolicy: spec.acquisitionMode === 'ask_user' ? 'ask' : 'block',\n }\n })\n}\n\n/**\n * Score every spec from workspace state. `derive` (code) wins; otherwise the\n * declarative `satisfiedBy` rule is evaluated through the accessor; a spec with\n * neither scores 0 (an acquisition gate, e.g. `search_web`).\n */\nexport async function deriveSignals(\n specs: KnowledgeRequirementSpec[],\n ctx: KnowledgeStateAccessor,\n): Promise<Record<string, KnowledgeSignal>> {\n const out: Record<string, KnowledgeSignal> = {}\n for (const spec of specs) {\n if (spec.derive) {\n out[spec.id] = { confidence: clamp(await spec.derive(ctx)), evidence: spec.evidence }\n } else if (spec.satisfiedBy) {\n const ok = await evalRule(spec.satisfiedBy, ctx)\n out[spec.id] = ok\n ? { confidence: 1, evidence: spec.evidence ?? describeRule(spec.satisfiedBy) }\n : { confidence: 0 }\n } else {\n out[spec.id] = { confidence: 0 }\n }\n }\n return out\n}\n\nasync function evalRule(rule: SatisfiedByRule, ctx: KnowledgeStateAccessor): Promise<boolean> {\n if ('anyOf' in rule) {\n for (const sub of rule.anyOf) if (await evalRule(sub, ctx)) return true\n return false\n }\n if ('allOf' in rule) {\n for (const sub of rule.allOf) if (!(await evalRule(sub, ctx))) return false\n return true\n }\n if ('config' in rule) {\n const value = ctx.config(rule.config)\n if (rule.nonEmpty) return Array.isArray(value) ? value.length > 0 : value != null && value !== ''\n return value != null && value !== '' && value !== false\n }\n const rows = await ctx.count({ table: rule.table, where: rule.where, statusIn: rule.statusIn })\n return rows >= (rule.minRows ?? 1)\n}\n\nfunction describeRule(rule: SatisfiedByRule): string {\n if ('anyOf' in rule) return `anyOf(${rule.anyOf.map(describeRule).join(',')})`\n if ('allOf' in rule) return `allOf(${rule.allOf.map(describeRule).join(',')})`\n if ('config' in rule) return `config:${rule.config}`\n return `${rule.table}${rule.statusIn ? `[${rule.statusIn.join('|')}]` : ''}>=${rule.minRows ?? 1}`\n}\n"],"mappings":";AAyEA,SAAS,MAAM,OAAuB;AACpC,MAAI,CAAC,OAAO,SAAS,KAAK,EAAG,QAAO;AACpC,MAAI,QAAQ,EAAG,QAAO;AACtB,MAAI,QAAQ,EAAG,QAAO;AACtB,SAAO;AACT;AAOO,SAAS,2BACd,OACA,UAA2C,CAAC,GACpB;AACxB,SAAO,MAAM,IAAI,CAAC,SAAS;AACzB,UAAM,SAAS,QAAQ,KAAK,EAAE;AAC9B,WAAO;AAAA,MACL,IAAI,KAAK;AAAA,MACT,aAAa,KAAK;AAAA,MAClB,aAAa,KAAK,eAAe,CAAC;AAAA,MAClC,UAAU,KAAK;AAAA,MACf,iBAAiB,KAAK;AAAA,MACtB,YAAY,KAAK,cAAc;AAAA,MAC/B,WAAW,KAAK,aAAa;AAAA,MAC7B,aAAa,KAAK,eAAe;AAAA,MACjC,kBAAkB,KAAK,oBAAoB;AAAA,MAC3C,mBAAmB,MAAM,QAAQ,cAAc,CAAC;AAAA,MAChD,aAAa,QAAQ,WAAW,CAAC,OAAO,QAAQ,IAAI,CAAC;AAAA,MACrD,gBAAgB,KAAK,oBAAoB,aAAa,QAAQ;AAAA,IAChE;AAAA,EACF,CAAC;AACH;AAOA,eAAsB,cACpB,OACA,KAC0C;AAC1C,QAAM,MAAuC,CAAC;AAC9C,aAAW,QAAQ,OAAO;AACxB,QAAI,KAAK,QAAQ;AACf,UAAI,KAAK,EAAE,IAAI,EAAE,YAAY,MAAM,MAAM,KAAK,OAAO,GAAG,CAAC,GAAG,UAAU,KAAK,SAAS;AAAA,IACtF,WAAW,KAAK,aAAa;AAC3B,YAAM,KAAK,MAAM,SAAS,KAAK,aAAa,GAAG;AAC/C,UAAI,KAAK,EAAE,IAAI,KACX,EAAE,YAAY,GAAG,UAAU,KAAK,YAAY,aAAa,KAAK,WAAW,EAAE,IAC3E,EAAE,YAAY,EAAE;AAAA,IACtB,OAAO;AACL,UAAI,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE;AAAA,IACjC;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAe,SAAS,MAAuB,KAA+C;AAC5F,MAAI,WAAW,MAAM;AACnB,eAAW,OAAO,KAAK,MAAO,KAAI,MAAM,SAAS,KAAK,GAAG,EAAG,QAAO;AACnE,WAAO;AAAA,EACT;AACA,MAAI,WAAW,MAAM;AACnB,eAAW,OAAO,KAAK,MAAO,KAAI,CAAE,MAAM,SAAS,KAAK,GAAG,EAAI,QAAO;AACtE,WAAO;AAAA,EACT;AACA,MAAI,YAAY,MAAM;AACpB,UAAM,QAAQ,IAAI,OAAO,KAAK,MAAM;AACpC,QAAI,KAAK,SAAU,QAAO,MAAM,QAAQ,KAAK,IAAI,MAAM,SAAS,IAAI,SAAS,QAAQ,UAAU;AAC/F,WAAO,SAAS,QAAQ,UAAU,MAAM,UAAU;AAAA,EACpD;AACA,QAAM,OAAO,MAAM,IAAI,MAAM,EAAE,OAAO,KAAK,OAAO,OAAO,KAAK,OAAO,UAAU,KAAK,SAAS,CAAC;AAC9F,SAAO,SAAS,KAAK,WAAW;AAClC;AAEA,SAAS,aAAa,MAA+B;AACnD,MAAI,WAAW,KAAM,QAAO,SAAS,KAAK,MAAM,IAAI,YAAY,EAAE,KAAK,GAAG,CAAC;AAC3E,MAAI,WAAW,KAAM,QAAO,SAAS,KAAK,MAAM,IAAI,YAAY,EAAE,KAAK,GAAG,CAAC;AAC3E,MAAI,YAAY,KAAM,QAAO,UAAU,KAAK,MAAM;AAClD,SAAO,GAAG,KAAK,KAAK,GAAG,KAAK,WAAW,IAAI,KAAK,SAAS,KAAK,GAAG,CAAC,MAAM,EAAE,KAAK,KAAK,WAAW,CAAC;AAClG;","names":[]}