@tangle-network/agent-app 0.43.76 → 0.44.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 +1 -1
- package/dist/DesignCanvas-Z362I3R3.js +10 -0
- package/dist/{DesignCanvasEditor-FJIVYOA2.js → DesignCanvasEditor-ZFE2W4OJ.js} +4 -4
- package/dist/assets/index.d.ts +4 -4
- package/dist/assets/index.js +223 -11
- package/dist/assets/index.js.map +1 -1
- package/dist/assistant/index.d.ts +7 -7
- package/dist/assistant/index.js +3 -3
- package/dist/assistant/index.js.map +1 -1
- package/dist/{attachment-validation-Dv1A_Puy.d.ts → attachment-validation-DRuLEjAE.d.ts} +1 -1
- package/dist/{auth-_FU8w01b.d.ts → auth-anc7mv2W.d.ts} +2 -2
- package/dist/chat-routes/index.d.ts +14 -11
- package/dist/chat-routes/index.js +16 -17
- package/dist/chat-routes/index.js.map +1 -1
- package/dist/chat-store/index.d.ts +26 -7
- package/dist/chat-store/index.js +14 -5
- package/dist/chat-store/index.js.map +1 -1
- package/dist/{chunk-ROLFDOMJ.js → chunk-4TI7QWLW.js} +3 -3
- package/dist/{chunk-3JUTOVUH.js → chunk-6KMAM7BL.js} +1 -1
- package/dist/chunk-6KMAM7BL.js.map +1 -0
- package/dist/{chunk-WL7XHLDK.js → chunk-ICOHEZK6.js} +77 -5
- package/dist/chunk-ICOHEZK6.js.map +1 -0
- package/dist/{chunk-22GLESKE.js → chunk-O63WWYET.js} +2 -4
- package/dist/chunk-O63WWYET.js.map +1 -0
- package/dist/{chunk-E7N67FYI.js → chunk-Q3FONVAK.js} +5 -5
- package/dist/{chunk-E7N67FYI.js.map → chunk-Q3FONVAK.js.map} +1 -1
- package/dist/{chunk-RXOTWZ4G.js → chunk-TKRI463Z.js} +4 -4
- package/dist/chunk-TKRI463Z.js.map +1 -0
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.js +123 -4
- package/dist/config/index.js.map +1 -1
- package/dist/{contract-B3h7peV3.d.ts → contract-CEewO6DI.d.ts} +1 -1
- package/dist/design-canvas/index.d.ts +4 -4
- package/dist/design-canvas-react/index.js +7 -7
- package/dist/design-canvas-react/lazy.js +1 -1
- package/dist/eval/index.d.ts +1 -1
- package/dist/eval/index.js +22 -8
- package/dist/eval/index.js.map +1 -1
- package/dist/{flow-types-CqomVAUN.d.ts → flow-types-CJxEmaRy.d.ts} +1 -1
- package/dist/integrations/index.js +67 -5
- package/dist/integrations/index.js.map +1 -1
- package/dist/interactions/index.d.ts +6 -4
- package/dist/interactions/index.js +1 -2
- package/dist/knowledge/index.js +63 -4
- package/dist/knowledge/index.js.map +1 -1
- package/dist/knowledge-loop/index.d.ts +1 -1
- package/dist/knowledge-loop/index.js +94 -4
- package/dist/knowledge-loop/index.js.map +1 -1
- package/dist/{mcp-BjPqQ-3C.d.ts → mcp-D3qVzbE1.d.ts} +2 -2
- package/dist/{mcp-rpc-BfUOsalw.d.ts → mcp-rpc-CzU5LWWT.d.ts} +1 -1
- package/dist/missions/index.js +1143 -23
- package/dist/missions/index.js.map +1 -1
- package/dist/{model-DmdkIteM.d.ts → model-CdCDfBA9.d.ts} +1 -1
- package/dist/object-store/index.js +113 -8
- package/dist/object-store/index.js.map +1 -1
- package/dist/{parts-2ymE5cs-.d.ts → parts-Co_lFgLI.d.ts} +3 -3
- package/dist/platform/index.d.ts +1 -1
- package/dist/preflight/cli.d.ts +13 -0
- package/dist/preflight/cli.js +50 -0
- package/dist/preflight/cli.js.map +1 -0
- package/dist/preset-cloudflare/index.d.ts +1 -1
- package/dist/preset-cloudflare/index.js +284 -11
- package/dist/preset-cloudflare/index.js.map +1 -1
- package/dist/{queue-C24V13h9.d.ts → queue-CD5-_VTX.d.ts} +1 -1
- package/dist/redact/index.js +116 -8
- package/dist/redact/index.js.map +1 -1
- package/dist/runtime/index.d.ts +19 -111
- package/dist/runtime/index.js +234 -14
- package/dist/runtime/index.js.map +1 -1
- package/dist/sandbox/index.d.ts +3 -3
- package/dist/sandbox/index.js +2 -3
- package/dist/sequences/index.d.ts +4 -4
- package/dist/stream/index.d.ts +4 -4
- package/dist/{stream-normalizer-QYUl4vnl.d.ts → stream-normalizer-DnuqkZvw.d.ts} +2 -2
- package/dist/tangle/index.js +42 -4
- package/dist/tangle/index.js.map +1 -1
- package/dist/teams/index.js +5 -5
- package/dist/teams/invitations-api.js +4 -4
- package/dist/teams-react/index.js +3 -3
- package/dist/theme/index.js +0 -1
- package/dist/tools/index.d.ts +6 -6
- package/dist/tools/index.js +5 -7
- package/dist/trace/index.d.ts +1 -1
- package/dist/trace/index.js +179 -10
- package/dist/trace/index.js.map +1 -1
- package/dist/{turn-buffer-BaSgxVmK.d.ts → turn-buffer-BcPfhr1_.d.ts} +1 -1
- package/dist/turn-stream/index.d.ts +1 -1
- package/dist/{types-CBRyqijY.d.ts → types-BCxK0wyS.d.ts} +1 -1
- package/dist/{types-CEchbvgz.d.ts → types-XIZqIdX1.d.ts} +1 -1
- package/dist/web-react/index.d.ts +10 -10
- package/dist/web-react/index.js +5 -5
- package/dist/work-product/index.d.ts +4 -4
- package/dist/work-product/index.js +735 -15
- package/dist/work-product/index.js.map +1 -1
- package/dist/work-product-react/index.d.ts +1 -1
- package/dist/work-product-react/index.js.map +1 -1
- package/package.json +4 -19
- package/dist/DesignCanvas-UNL34LS2.js +0 -12
- package/dist/chunk-22GLESKE.js.map +0 -1
- package/dist/chunk-3JUTOVUH.js.map +0 -1
- package/dist/chunk-5EQCITY3.js +0 -20
- package/dist/chunk-5EQCITY3.js.map +0 -1
- package/dist/chunk-5SD6HT2V.js +0 -236
- package/dist/chunk-5SD6HT2V.js.map +0 -1
- package/dist/chunk-5TESA4SN.js +0 -74
- package/dist/chunk-5TESA4SN.js.map +0 -1
- package/dist/chunk-6GQ5DKFK.js +0 -920
- package/dist/chunk-6GQ5DKFK.js.map +0 -1
- package/dist/chunk-7ESQUSAC.js +0 -343
- package/dist/chunk-7ESQUSAC.js.map +0 -1
- package/dist/chunk-7LNGJDNA.js +0 -1
- package/dist/chunk-7LNGJDNA.js.map +0 -1
- package/dist/chunk-AP2UXKUK.js +0 -69
- package/dist/chunk-AP2UXKUK.js.map +0 -1
- package/dist/chunk-FA4XR66Y.js +0 -32
- package/dist/chunk-FA4XR66Y.js.map +0 -1
- package/dist/chunk-FKCB5FTD.js +0 -126
- package/dist/chunk-FKCB5FTD.js.map +0 -1
- package/dist/chunk-GPBRL2XR.js +0 -102
- package/dist/chunk-GPBRL2XR.js.map +0 -1
- package/dist/chunk-HRH7ASAG.js +0 -759
- package/dist/chunk-HRH7ASAG.js.map +0 -1
- package/dist/chunk-HYTIHYNV.js +0 -297
- package/dist/chunk-HYTIHYNV.js.map +0 -1
- package/dist/chunk-LGBO24FL.js +0 -190
- package/dist/chunk-LGBO24FL.js.map +0 -1
- package/dist/chunk-NLSSFLLS.js +0 -1168
- package/dist/chunk-NLSSFLLS.js.map +0 -1
- package/dist/chunk-OSMNYPOG.js +0 -129
- package/dist/chunk-OSMNYPOG.js.map +0 -1
- package/dist/chunk-QYOD3K56.js +0 -124
- package/dist/chunk-QYOD3K56.js.map +0 -1
- package/dist/chunk-RXOTWZ4G.js.map +0 -1
- package/dist/chunk-UAKFCMWK.js +0 -1
- package/dist/chunk-UAKFCMWK.js.map +0 -1
- package/dist/chunk-UKTYKG57.js +0 -48
- package/dist/chunk-UKTYKG57.js.map +0 -1
- package/dist/chunk-UOAYS72M.js +0 -80
- package/dist/chunk-UOAYS72M.js.map +0 -1
- package/dist/chunk-WL7XHLDK.js.map +0 -1
- package/dist/composer/index.d.ts +0 -2
- package/dist/composer/index.js +0 -22
- package/dist/composer/index.js.map +0 -1
- package/dist/core-7qIM7svy.d.ts +0 -21
- package/dist/durable-chat/index.d.ts +0 -464
- package/dist/durable-chat/index.js +0 -59
- package/dist/durable-chat/index.js.map +0 -1
- package/dist/index.d.ts +0 -51
- package/dist/index.js +0 -827
- package/dist/index.js.map +0 -1
- /package/dist/{DesignCanvas-UNL34LS2.js.map → DesignCanvas-Z362I3R3.js.map} +0 -0
- /package/dist/{DesignCanvasEditor-FJIVYOA2.js.map → DesignCanvasEditor-ZFE2W4OJ.js.map} +0 -0
- /package/dist/{chunk-ROLFDOMJ.js.map → chunk-4TI7QWLW.js.map} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { KeyProvisioner, KeyCrypto, WorkspaceKeyManager, WorkspaceKeyStore } from '../billing/index.js';
|
|
2
2
|
import { KnowledgeStateAccessor } from '../knowledge/index.js';
|
|
3
|
-
import {
|
|
3
|
+
import { d as AppToolHandlers } from '../types-BCxK0wyS.js';
|
|
4
4
|
import { KvLike } from '../web/index.js';
|
|
5
5
|
import '@tangle-network/agent-eval';
|
|
6
6
|
|
|
@@ -1,15 +1,288 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
2
|
+
createWorkspaceKeyManager
|
|
3
|
+
} from "../chunk-GXE6KHTS.js";
|
|
4
|
+
import {
|
|
5
|
+
createFieldCrypto
|
|
6
|
+
} from "../chunk-TA5Q4I2K.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
|
+
knowledge: {
|
|
25
|
+
name: "knowledge",
|
|
26
|
+
columns: {
|
|
27
|
+
id: "id",
|
|
28
|
+
workspaceId: "workspace_id",
|
|
29
|
+
path: "path",
|
|
30
|
+
kind: "kind",
|
|
31
|
+
label: "label",
|
|
32
|
+
content: "content",
|
|
33
|
+
createdAt: "created_at"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
deadlines: {
|
|
37
|
+
name: "deadlines",
|
|
38
|
+
columns: {
|
|
39
|
+
id: "id",
|
|
40
|
+
workspaceId: "workspace_id",
|
|
41
|
+
threadId: "thread_id",
|
|
42
|
+
title: "title",
|
|
43
|
+
dueDate: "due_date",
|
|
44
|
+
priority: "priority",
|
|
45
|
+
status: "status",
|
|
46
|
+
createdAt: "created_at"
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
workspaceKeys: {
|
|
50
|
+
name: "workspace_keys",
|
|
51
|
+
columns: {
|
|
52
|
+
id: "id",
|
|
53
|
+
workspaceId: "workspace_id",
|
|
54
|
+
keyId: "key_id",
|
|
55
|
+
keyEncrypted: "key_encrypted",
|
|
56
|
+
budgetUsd: "budget_usd",
|
|
57
|
+
expiresAt: "expires_at",
|
|
58
|
+
revokedAt: "revoked_at",
|
|
59
|
+
createdAt: "created_at"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
var PRESET_MIGRATION_SQL = [
|
|
64
|
+
`CREATE TABLE IF NOT EXISTS proposals (
|
|
65
|
+
id TEXT PRIMARY KEY,
|
|
66
|
+
workspace_id TEXT NOT NULL,
|
|
67
|
+
thread_id TEXT,
|
|
68
|
+
type TEXT NOT NULL,
|
|
69
|
+
title TEXT NOT NULL,
|
|
70
|
+
description TEXT,
|
|
71
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
72
|
+
created_by TEXT,
|
|
73
|
+
created_at INTEGER NOT NULL
|
|
74
|
+
)`,
|
|
75
|
+
`CREATE TABLE IF NOT EXISTS knowledge (
|
|
76
|
+
id TEXT PRIMARY KEY,
|
|
77
|
+
workspace_id TEXT NOT NULL,
|
|
78
|
+
path TEXT NOT NULL,
|
|
79
|
+
kind TEXT NOT NULL,
|
|
80
|
+
label TEXT,
|
|
81
|
+
content TEXT,
|
|
82
|
+
created_at INTEGER NOT NULL
|
|
83
|
+
)`,
|
|
84
|
+
`CREATE TABLE IF NOT EXISTS deadlines (
|
|
85
|
+
id TEXT PRIMARY KEY,
|
|
86
|
+
workspace_id TEXT NOT NULL,
|
|
87
|
+
thread_id TEXT,
|
|
88
|
+
title TEXT NOT NULL,
|
|
89
|
+
due_date TEXT NOT NULL,
|
|
90
|
+
priority TEXT,
|
|
91
|
+
status TEXT NOT NULL DEFAULT 'scheduled',
|
|
92
|
+
created_at INTEGER NOT NULL
|
|
93
|
+
)`,
|
|
94
|
+
`CREATE TABLE IF NOT EXISTS workspace_keys (
|
|
95
|
+
id TEXT PRIMARY KEY,
|
|
96
|
+
workspace_id TEXT NOT NULL,
|
|
97
|
+
key_id TEXT NOT NULL,
|
|
98
|
+
key_encrypted TEXT NOT NULL,
|
|
99
|
+
budget_usd REAL NOT NULL,
|
|
100
|
+
expires_at INTEGER NOT NULL,
|
|
101
|
+
revoked_at INTEGER,
|
|
102
|
+
created_at INTEGER NOT NULL
|
|
103
|
+
)`,
|
|
104
|
+
`CREATE INDEX IF NOT EXISTS idx_proposals_ws ON proposals (workspace_id, status)`,
|
|
105
|
+
`CREATE INDEX IF NOT EXISTS idx_deadlines_ws ON deadlines (workspace_id, status)`,
|
|
106
|
+
`CREATE INDEX IF NOT EXISTS idx_knowledge_ws ON knowledge (workspace_id)`,
|
|
107
|
+
`CREATE INDEX IF NOT EXISTS idx_workspace_keys_ws ON workspace_keys (workspace_id, revoked_at)`
|
|
108
|
+
];
|
|
109
|
+
function createPresetDrizzleSchema(d) {
|
|
110
|
+
const { sqliteTable, text, integer, real } = d;
|
|
111
|
+
const C = PRESET_TABLES;
|
|
112
|
+
return {
|
|
113
|
+
proposals: sqliteTable(C.proposals.name, {
|
|
114
|
+
id: text(C.proposals.columns.id).primaryKey(),
|
|
115
|
+
workspaceId: text(C.proposals.columns.workspaceId).notNull(),
|
|
116
|
+
threadId: text(C.proposals.columns.threadId),
|
|
117
|
+
type: text(C.proposals.columns.type).notNull(),
|
|
118
|
+
title: text(C.proposals.columns.title).notNull(),
|
|
119
|
+
description: text(C.proposals.columns.description),
|
|
120
|
+
status: text(C.proposals.columns.status).notNull().default("pending"),
|
|
121
|
+
createdBy: text(C.proposals.columns.createdBy),
|
|
122
|
+
createdAt: integer(C.proposals.columns.createdAt).notNull()
|
|
123
|
+
}),
|
|
124
|
+
knowledge: sqliteTable(C.knowledge.name, {
|
|
125
|
+
id: text(C.knowledge.columns.id).primaryKey(),
|
|
126
|
+
workspaceId: text(C.knowledge.columns.workspaceId).notNull(),
|
|
127
|
+
path: text(C.knowledge.columns.path).notNull(),
|
|
128
|
+
kind: text(C.knowledge.columns.kind).notNull(),
|
|
129
|
+
label: text(C.knowledge.columns.label),
|
|
130
|
+
content: text(C.knowledge.columns.content),
|
|
131
|
+
createdAt: integer(C.knowledge.columns.createdAt).notNull()
|
|
132
|
+
}),
|
|
133
|
+
deadlines: sqliteTable(C.deadlines.name, {
|
|
134
|
+
id: text(C.deadlines.columns.id).primaryKey(),
|
|
135
|
+
workspaceId: text(C.deadlines.columns.workspaceId).notNull(),
|
|
136
|
+
threadId: text(C.deadlines.columns.threadId),
|
|
137
|
+
title: text(C.deadlines.columns.title).notNull(),
|
|
138
|
+
dueDate: text(C.deadlines.columns.dueDate).notNull(),
|
|
139
|
+
priority: text(C.deadlines.columns.priority),
|
|
140
|
+
status: text(C.deadlines.columns.status).notNull().default("scheduled"),
|
|
141
|
+
createdAt: integer(C.deadlines.columns.createdAt).notNull()
|
|
142
|
+
}),
|
|
143
|
+
workspaceKeys: sqliteTable(C.workspaceKeys.name, {
|
|
144
|
+
id: text(C.workspaceKeys.columns.id).primaryKey(),
|
|
145
|
+
workspaceId: text(C.workspaceKeys.columns.workspaceId).notNull(),
|
|
146
|
+
keyId: text(C.workspaceKeys.columns.keyId).notNull(),
|
|
147
|
+
keyEncrypted: text(C.workspaceKeys.columns.keyEncrypted).notNull(),
|
|
148
|
+
budgetUsd: real(C.workspaceKeys.columns.budgetUsd).notNull(),
|
|
149
|
+
expiresAt: integer(C.workspaceKeys.columns.expiresAt).notNull(),
|
|
150
|
+
revokedAt: integer(C.workspaceKeys.columns.revokedAt),
|
|
151
|
+
createdAt: integer(C.workspaceKeys.columns.createdAt).notNull()
|
|
152
|
+
})
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function slug(value) {
|
|
156
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64) || "item";
|
|
157
|
+
}
|
|
158
|
+
function createPresetToolHandlers(opts) {
|
|
159
|
+
const { db, vault } = opts;
|
|
160
|
+
const newId = opts.newId ?? (() => crypto.randomUUID());
|
|
161
|
+
const now = opts.now ?? (() => Date.now());
|
|
162
|
+
const uiPrefix = opts.uiPathPrefix ?? "ui";
|
|
163
|
+
const citationPrefix = opts.citationPathPrefix ?? "citations";
|
|
164
|
+
const P = PRESET_TABLES.proposals;
|
|
165
|
+
const D = PRESET_TABLES.deadlines;
|
|
166
|
+
const K = PRESET_TABLES.knowledge;
|
|
167
|
+
async function persistArtifact(path, body) {
|
|
168
|
+
await vault.put(path, body);
|
|
169
|
+
}
|
|
170
|
+
async function insertKnowledge(workspaceId, path, kind, label, content) {
|
|
171
|
+
await db.prepare(
|
|
172
|
+
`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 (?, ?, ?, ?, ?, ?, ?)`
|
|
173
|
+
).bind(newId(), workspaceId, path, kind, label, content, now()).run();
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
async submitProposal(args, ctx) {
|
|
177
|
+
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();
|
|
178
|
+
if (existing) return { proposalId: existing.id, deduped: true };
|
|
179
|
+
const id = newId();
|
|
180
|
+
await db.prepare(
|
|
181
|
+
`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', ?, ?)`
|
|
182
|
+
).bind(id, ctx.workspaceId, ctx.threadId, args.type, args.title, args.description ?? null, ctx.userId, now()).run();
|
|
183
|
+
return { proposalId: id, deduped: false };
|
|
184
|
+
},
|
|
185
|
+
async scheduleFollowup(args, ctx) {
|
|
186
|
+
const existing = await db.prepare(
|
|
187
|
+
`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`
|
|
188
|
+
).bind(ctx.workspaceId, args.title, args.dueDate).first();
|
|
189
|
+
if (existing) return { id: existing.id, dueDate: existing.dueDate, deduped: true };
|
|
190
|
+
const id = newId();
|
|
191
|
+
await db.prepare(
|
|
192
|
+
`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', ?)`
|
|
193
|
+
).bind(id, ctx.workspaceId, ctx.threadId, args.title, args.dueDate, args.priority ?? null, now()).run();
|
|
194
|
+
return { id, dueDate: args.dueDate, deduped: false };
|
|
195
|
+
},
|
|
196
|
+
async renderUi(args, ctx) {
|
|
197
|
+
const content = JSON.stringify(args.schema);
|
|
198
|
+
const path = `${uiPrefix}/${ctx.threadId ?? "global"}/${slug(args.title)}.json`;
|
|
199
|
+
await persistArtifact(path, content);
|
|
200
|
+
await insertKnowledge(ctx.workspaceId, path, "ui", args.title, content);
|
|
201
|
+
return { path, content };
|
|
202
|
+
},
|
|
203
|
+
async addCitation(args, ctx) {
|
|
204
|
+
const citationId = newId();
|
|
205
|
+
const path = `${citationPrefix}/${slug(args.label ?? args.path)}-${citationId.slice(0, 8)}.json`;
|
|
206
|
+
const body = JSON.stringify({ sourcePath: args.path, quote: args.quote, label: args.label ?? null });
|
|
207
|
+
await persistArtifact(path, body);
|
|
208
|
+
await insertKnowledge(ctx.workspaceId, path, "citation", args.label ?? null, body);
|
|
209
|
+
return { citationId, path };
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
function readDotPath(obj, path) {
|
|
214
|
+
let cur = obj;
|
|
215
|
+
for (const part of path.split(".")) {
|
|
216
|
+
if (cur == null || typeof cur !== "object") return void 0;
|
|
217
|
+
cur = cur[part];
|
|
218
|
+
}
|
|
219
|
+
return cur;
|
|
220
|
+
}
|
|
221
|
+
function createD1KnowledgeStateAccessor(opts) {
|
|
222
|
+
const { db, workspaceId } = opts;
|
|
223
|
+
const defaultWhere = opts.defaultWhereColumn ?? "workspace_id";
|
|
224
|
+
const configFn = typeof opts.config === "function" ? opts.config : (path) => readDotPath(opts.config, path);
|
|
225
|
+
const isIdentifier = (s) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(s);
|
|
226
|
+
return {
|
|
227
|
+
config: configFn,
|
|
228
|
+
async count(query) {
|
|
229
|
+
if (!isIdentifier(query.table)) throw new Error(`unsafe table identifier: ${query.table}`);
|
|
230
|
+
const whereCol = query.where ?? defaultWhere;
|
|
231
|
+
if (!isIdentifier(whereCol)) throw new Error(`unsafe where identifier: ${whereCol}`);
|
|
232
|
+
let sql = `SELECT count(*) AS n FROM ${query.table} WHERE ${whereCol} = ?`;
|
|
233
|
+
const binds = [workspaceId];
|
|
234
|
+
if (query.statusIn && query.statusIn.length > 0) {
|
|
235
|
+
sql += ` AND status IN (${query.statusIn.map(() => "?").join(", ")})`;
|
|
236
|
+
binds.push(...query.statusIn);
|
|
237
|
+
}
|
|
238
|
+
const row = await db.prepare(sql).bind(...binds).first();
|
|
239
|
+
return row?.n ?? 0;
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
function createPresetFieldCrypto(key) {
|
|
244
|
+
return createFieldCrypto(key);
|
|
245
|
+
}
|
|
246
|
+
function createPresetWorkspaceKeyStore(db) {
|
|
247
|
+
const W = PRESET_TABLES.workspaceKeys;
|
|
248
|
+
return {
|
|
249
|
+
async getActive(workspaceId) {
|
|
250
|
+
const row = await db.prepare(
|
|
251
|
+
`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`
|
|
252
|
+
).bind(workspaceId).first();
|
|
253
|
+
if (!row) return null;
|
|
254
|
+
return {
|
|
255
|
+
id: row.id,
|
|
256
|
+
keyId: row.keyId,
|
|
257
|
+
keyEncrypted: row.keyEncrypted,
|
|
258
|
+
budgetUsd: row.budgetUsd,
|
|
259
|
+
expiresAt: row.expiresAt == null ? null : new Date(row.expiresAt)
|
|
260
|
+
};
|
|
261
|
+
},
|
|
262
|
+
async listActive(workspaceId) {
|
|
263
|
+
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();
|
|
264
|
+
return res.results;
|
|
265
|
+
},
|
|
266
|
+
async insert(record) {
|
|
267
|
+
await db.prepare(
|
|
268
|
+
`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, ?)`
|
|
269
|
+
).bind(crypto.randomUUID(), record.workspaceId, record.keyId, record.keyEncrypted, record.budgetUsd, record.expiresAt.getTime(), Date.now()).run();
|
|
270
|
+
},
|
|
271
|
+
async markRevoked(id, now) {
|
|
272
|
+
await db.prepare(`UPDATE ${W.name} SET ${W.columns.revokedAt} = ? WHERE ${W.columns.id} = ?`).bind(now.getTime(), id).run();
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
function createPresetWorkspaceKeyManager(opts) {
|
|
277
|
+
return createWorkspaceKeyManager({
|
|
278
|
+
provisioner: opts.provisioner,
|
|
279
|
+
store: createPresetWorkspaceKeyStore(opts.db),
|
|
280
|
+
crypto: createPresetFieldCrypto(opts.encryptionKey),
|
|
281
|
+
defaultBudgetUsd: opts.defaultBudgetUsd,
|
|
282
|
+
now: opts.now,
|
|
283
|
+
product: opts.product
|
|
284
|
+
});
|
|
285
|
+
}
|
|
13
286
|
export {
|
|
14
287
|
PRESET_MIGRATION_SQL,
|
|
15
288
|
PRESET_TABLES,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
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// Deliberately NOT here: chat thread/message tables. `/chat-store` is the\n// single thread-schema owner (`createChatTables` + `createChatStore`); the\n// preset once declared an unconsumed `threads` DDL, removed so two schemas\n// can't drift.\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 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 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 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\n/** Define configuration options for handling preset tools including database, vault, and optional utilities */\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\n/** Define options for accessing preset knowledge scoped to a specific workspace and configuration */\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\n/** Define preset billing options including database, provisioner, encryption key, budget, and optional settings */\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":[]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { W as WorkProductRef, a as WorkProductProvenance, b as WorkProductRecord } from './types-XIZqIdX1.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* The review queue is a PROJECTION, not a store — a client-safe pure fold of
|
package/dist/redact/index.js
CHANGED
|
@@ -1,11 +1,119 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
// src/redact/index.ts
|
|
2
|
+
var DEFAULT_REDACTION_PATTERNS = [
|
|
3
|
+
{ kind: "ssn", pattern: /\d{3}-\d{2}-\d{4}/ },
|
|
4
|
+
{ kind: "ein", pattern: /\d{2}-\d{7}/ }
|
|
5
|
+
];
|
|
6
|
+
var SENSITIVE_KEYS = /* @__PURE__ */ new Set([
|
|
7
|
+
"ssn",
|
|
8
|
+
"ein",
|
|
9
|
+
"password",
|
|
10
|
+
"apikey",
|
|
11
|
+
"token",
|
|
12
|
+
"secret",
|
|
13
|
+
"authorization",
|
|
14
|
+
"email",
|
|
15
|
+
"phone"
|
|
16
|
+
]);
|
|
17
|
+
function redactString(value, patterns) {
|
|
18
|
+
for (const { kind, pattern, validate } of patterns) {
|
|
19
|
+
if (!validate) {
|
|
20
|
+
if (pattern.test(value)) return `[REDACTED:${kind}]`;
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
const g = new RegExp(pattern.source, pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`);
|
|
24
|
+
for (const m of value.matchAll(g)) {
|
|
25
|
+
if (m[0].length > 0 && validate(m[0])) return `[REDACTED:${kind}]`;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
function maskSpans(text, patterns = DEFAULT_REDACTION_PATTERNS) {
|
|
31
|
+
const spans = detectSpans(text, patterns);
|
|
32
|
+
if (spans.length === 0) return text;
|
|
33
|
+
let out = "";
|
|
34
|
+
let pos = 0;
|
|
35
|
+
for (const s of spans) {
|
|
36
|
+
if (s.start > pos) out += text.slice(pos, s.start);
|
|
37
|
+
out += `[REDACTED:${s.kind}]`;
|
|
38
|
+
pos = s.end;
|
|
39
|
+
}
|
|
40
|
+
if (pos < text.length) out += text.slice(pos);
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
function isPlainObject(value) {
|
|
44
|
+
if (value === null || typeof value !== "object") return false;
|
|
45
|
+
const proto = Object.getPrototypeOf(value);
|
|
46
|
+
return proto === Object.prototype || proto === null;
|
|
47
|
+
}
|
|
48
|
+
function redactForIngestion(value, options = {}) {
|
|
49
|
+
const patterns = options.extraPatterns ? [...DEFAULT_REDACTION_PATTERNS, ...options.extraPatterns] : DEFAULT_REDACTION_PATTERNS;
|
|
50
|
+
const sensitiveKeys = options.extraSensitiveKeys ? /* @__PURE__ */ new Set([...SENSITIVE_KEYS, ...options.extraSensitiveKeys.map((k) => k.toLowerCase())]) : SENSITIVE_KEYS;
|
|
51
|
+
const maskString = options.stringMode === "mask-spans" ? (s) => maskSpans(s, patterns) : (s) => redactString(s, patterns);
|
|
52
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
53
|
+
const walk = (v) => {
|
|
54
|
+
if (typeof v === "string") return maskString(v);
|
|
55
|
+
if (Array.isArray(v)) {
|
|
56
|
+
if (seen.has(v)) return v;
|
|
57
|
+
seen.add(v);
|
|
58
|
+
return v.map(walk);
|
|
59
|
+
}
|
|
60
|
+
if (isPlainObject(v)) {
|
|
61
|
+
if (seen.has(v)) return v;
|
|
62
|
+
seen.add(v);
|
|
63
|
+
const out = {};
|
|
64
|
+
for (const [k, val] of Object.entries(v)) {
|
|
65
|
+
out[k] = sensitiveKeys.has(k.toLowerCase()) ? "[REDACTED:field]" : walk(val);
|
|
66
|
+
}
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
69
|
+
return v;
|
|
70
|
+
};
|
|
71
|
+
return walk(value);
|
|
72
|
+
}
|
|
73
|
+
function detectSpans(text, patterns = DEFAULT_REDACTION_PATTERNS) {
|
|
74
|
+
const raw = [];
|
|
75
|
+
for (const { kind, pattern, validate } of patterns) {
|
|
76
|
+
const g = new RegExp(pattern.source, pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`);
|
|
77
|
+
for (const m of text.matchAll(g)) {
|
|
78
|
+
if (m.index === void 0 || m[0].length === 0) continue;
|
|
79
|
+
if (validate && !validate(m[0])) continue;
|
|
80
|
+
raw.push({ kind, start: m.index, end: m.index + m[0].length, text: m[0] });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
raw.sort((a, b) => a.start - b.start || b.end - a.end);
|
|
84
|
+
const spans = [];
|
|
85
|
+
let cursor = -1;
|
|
86
|
+
let i = 0;
|
|
87
|
+
for (const s of raw) {
|
|
88
|
+
if (s.start < cursor) continue;
|
|
89
|
+
spans.push({ id: `span-${i++}`, ...s });
|
|
90
|
+
cursor = s.end;
|
|
91
|
+
}
|
|
92
|
+
return spans;
|
|
93
|
+
}
|
|
94
|
+
async function buildRedactedDocument(text, options) {
|
|
95
|
+
const spans = detectSpans(text, options.patterns);
|
|
96
|
+
const segments = [];
|
|
97
|
+
let pos = 0;
|
|
98
|
+
for (const span of spans) {
|
|
99
|
+
if (span.start > pos) segments.push({ type: "text", text: text.slice(pos, span.start) });
|
|
100
|
+
segments.push({ type: "redacted", id: span.id, kind: span.kind, cipher: await options.encrypt(span.text) });
|
|
101
|
+
pos = span.end;
|
|
102
|
+
}
|
|
103
|
+
if (pos < text.length) segments.push({ type: "text", text: text.slice(pos) });
|
|
104
|
+
return { segments };
|
|
105
|
+
}
|
|
106
|
+
async function revealSpan(doc, spanId, options) {
|
|
107
|
+
const seg = doc.segments.find(
|
|
108
|
+
(s) => s.type === "redacted" && s.id === spanId
|
|
109
|
+
);
|
|
110
|
+
if (!seg) return { ok: false, reason: "not_found" };
|
|
111
|
+
const allowed = await options.canReveal({ id: seg.id, kind: seg.kind });
|
|
112
|
+
if (!allowed) return { ok: false, reason: "forbidden" };
|
|
113
|
+
const value = await options.decrypt(seg.cipher);
|
|
114
|
+
if (options.onReveal) await options.onReveal({ id: seg.id, kind: seg.kind });
|
|
115
|
+
return { ok: true, value };
|
|
116
|
+
}
|
|
9
117
|
export {
|
|
10
118
|
DEFAULT_REDACTION_PATTERNS,
|
|
11
119
|
buildRedactedDocument,
|
package/dist/redact/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/redact/index.ts"],"sourcesContent":["/**\n * PII redaction — two complementary modes.\n *\n * 1. ONE-WAY scrub (`redactForIngestion`): for production trace payloads. Tool\n * args + results (and once, the LLM span's prompt) cross the wire into the\n * ingestion store, which also feeds the analyst-loop's LLM prompts, so\n * personal identifiers MUST be stripped before they leave the request path.\n * Destructive — the original is gone, replaced by a sentinel.\n *\n * 2. REVERSIBLE redaction (`buildRedactedDocument` / `revealSpan`): for the UI.\n * A document is split into text + redacted segments; each redacted original\n * is kept ENCRYPTED (via a caller-supplied `encrypt` seam → `agent-app/crypto`)\n * so a viewer can reveal a single span on demand, gated by an authorization\n * callback and an audit hook. The mask is presentation; the original is\n * recoverable by an authorized reveal, not lost.\n *\n * Discipline: cheap deterministic string patterns + well-known sensitive object\n * keys (value replaced, key kept, so the shape stays debuggable); recurse arrays\n * + plain objects only; NEVER throw on the one-way path.\n */\n\n/** A named PII pattern. `pattern` is matched case-insensitively at the string\n * level; keep it non-global (global instances are derived where needed). */\nexport interface RedactionPattern {\n kind: string\n pattern: RegExp\n /** Optional predicate over each match — the pattern fires only when it returns\n * true. For matches a regex alone can't decide (e.g. a Luhn check on a\n * card-number candidate). When set, the value is scanned globally and the\n * first match that passes wins; when absent, a plain `pattern.test` decides. */\n validate?: (match: string) => boolean\n}\n\n/** The default deterministic patterns. Extend via the `extraPatterns` /\n * `patterns` options rather than forking this module (the seam that lets a\n * product add e.g. a credit-card matcher without a local copy). */\nexport const DEFAULT_REDACTION_PATTERNS: readonly RedactionPattern[] = [\n { kind: 'ssn', pattern: /\\d{3}-\\d{2}-\\d{4}/ },\n { kind: 'ein', pattern: /\\d{2}-\\d{7}/ },\n]\n\nconst SENSITIVE_KEYS = new Set([\n 'ssn',\n 'ein',\n 'password',\n 'apikey',\n 'token',\n 'secret',\n 'authorization',\n 'email',\n 'phone',\n])\n\n/** Define options to customize sensitive data redaction patterns and key names for ingestion */\nexport interface RedactForIngestionOptions {\n /** Extra patterns appended to {@link DEFAULT_REDACTION_PATTERNS} for the\n * string-level scrub (e.g. credit-card). Additive — defaults still apply. */\n extraPatterns?: readonly RedactionPattern[]\n /** Extra sensitive object-key names (case-insensitive) added to the built-in\n * set, e.g. the snake_case `api_key` an intake form uses. Additive. */\n extraSensitiveKeys?: readonly string[]\n /**\n * How a matched string is rewritten:\n * - `'collapse'` (default) — the whole string becomes `[REDACTED:<kind>]` on\n * the first matching pattern. Safest for telemetry: nothing of the original\n * survives.\n * - `'mask-spans'` — only the matched substrings are replaced (each with\n * `[REDACTED:<kind>]`), preserving surrounding text. Use when a downstream\n * reader needs the non-PII context (e.g. an analyst loop reading prose).\n */\n stringMode?: 'collapse' | 'mask-spans'\n}\n\nfunction redactString(value: string, patterns: readonly RedactionPattern[]): string {\n for (const { kind, pattern, validate } of patterns) {\n if (!validate) {\n if (pattern.test(value)) return `[REDACTED:${kind}]`\n continue\n }\n const g = new RegExp(pattern.source, pattern.flags.includes('g') ? pattern.flags : `${pattern.flags}g`)\n for (const m of value.matchAll(g)) {\n if (m[0].length > 0 && validate(m[0])) return `[REDACTED:${kind}]`\n }\n }\n return value\n}\n\n/**\n * Replace only the PII substrings in `text`, preserving everything around them\n * (the `mask-spans` string mode). Built on {@link detectSpans} so matching,\n * non-overlap, and `validate` predicates behave identically to the reversible\n * path. Each span becomes `[REDACTED:<kind>]`.\n */\nexport function maskSpans(\n text: string,\n patterns: readonly RedactionPattern[] = DEFAULT_REDACTION_PATTERNS,\n): string {\n const spans = detectSpans(text, patterns)\n if (spans.length === 0) return text\n let out = ''\n let pos = 0\n for (const s of spans) {\n if (s.start > pos) out += text.slice(pos, s.start)\n out += `[REDACTED:${s.kind}]`\n pos = s.end\n }\n if (pos < text.length) out += text.slice(pos)\n return out\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (value === null || typeof value !== 'object') return false\n const proto = Object.getPrototypeOf(value)\n return proto === Object.prototype || proto === null\n}\n\n/**\n * One-way PII scrub for telemetry/ingestion. Backward-compatible: called with no\n * options it behaves exactly as before (SSN/EIN strings + sensitive object keys\n * → sentinels). `extraPatterns` lets a product add matchers (e.g. credit-card)\n * without forking this module.\n */\nexport function redactForIngestion(value: unknown, options: RedactForIngestionOptions = {}): unknown {\n const patterns = options.extraPatterns\n ? [...DEFAULT_REDACTION_PATTERNS, ...options.extraPatterns]\n : DEFAULT_REDACTION_PATTERNS\n const sensitiveKeys = options.extraSensitiveKeys\n ? new Set([...SENSITIVE_KEYS, ...options.extraSensitiveKeys.map((k) => k.toLowerCase())])\n : SENSITIVE_KEYS\n const maskString =\n options.stringMode === 'mask-spans'\n ? (s: string) => maskSpans(s, patterns)\n : (s: string) => redactString(s, patterns)\n // Cycle guard: a payload with a circular reference would otherwise recurse\n // forever. On re-encountering an object/array, return it untouched to break\n // the cycle (the same value was already redacted on its first visit).\n const seen = new WeakSet<object>()\n const walk = (v: unknown): unknown => {\n if (typeof v === 'string') return maskString(v)\n if (Array.isArray(v)) {\n if (seen.has(v)) return v\n seen.add(v)\n return v.map(walk)\n }\n if (isPlainObject(v)) {\n if (seen.has(v)) return v\n seen.add(v)\n const out: Record<string, unknown> = {}\n for (const [k, val] of Object.entries(v)) {\n out[k] = sensitiveKeys.has(k.toLowerCase()) ? '[REDACTED:field]' : walk(val)\n }\n return out\n }\n return v\n }\n return walk(value)\n}\n\n// ── Reversible document redaction (the UI path) ─────────────────────────────\n\n/** A detected PII span in a source string. */\nexport interface RedactionSpan {\n /** Stable within a document (index-derived) — used for reveal + audit. */\n id: string\n kind: string\n start: number\n end: number\n text: string\n}\n\n/**\n * Find non-overlapping PII spans in `text`. Matches every pattern, sorts by\n * position, and drops overlaps (first match wins). Deterministic — no ids that\n * vary per call.\n */\nexport function detectSpans(\n text: string,\n patterns: readonly RedactionPattern[] = DEFAULT_REDACTION_PATTERNS,\n): RedactionSpan[] {\n const raw: Array<{ kind: string; start: number; end: number; text: string }> = []\n for (const { kind, pattern, validate } of patterns) {\n const g = new RegExp(pattern.source, pattern.flags.includes('g') ? pattern.flags : `${pattern.flags}g`)\n for (const m of text.matchAll(g)) {\n if (m.index === undefined || m[0].length === 0) continue\n if (validate && !validate(m[0])) continue\n raw.push({ kind, start: m.index, end: m.index + m[0].length, text: m[0] })\n }\n }\n raw.sort((a, b) => a.start - b.start || b.end - a.end)\n const spans: RedactionSpan[] = []\n let cursor = -1\n let i = 0\n for (const s of raw) {\n if (s.start < cursor) continue // overlaps an earlier (higher-priority) span\n spans.push({ id: `span-${i++}`, ...s })\n cursor = s.end\n }\n return spans\n}\n\n/** A redacted document segment: literal text, or a masked span with the\n * original kept ENCRYPTED for an authorized reveal. */\nexport type RedactedDocSegment =\n | { type: 'text'; text: string }\n | { type: 'redacted'; id: string; kind: string; cipher: string }\n\n/** Define a document composed of multiple redacted content segments */\nexport interface RedactedDocument {\n segments: RedactedDocSegment[]\n}\n\n/** Define options to encrypt text and specify patterns for redacting sensitive document content */\nexport interface BuildRedactedDocumentOptions {\n /** Encrypt one original span value. Wire it to `agent-app/crypto`\n * (`encryptWithKey` / `createFieldCrypto`). The cipher is what's stored. */\n encrypt: (plaintext: string) => string | Promise<string>\n /** Patterns to detect (default: {@link DEFAULT_REDACTION_PATTERNS}). */\n patterns?: readonly RedactionPattern[]\n}\n\n/**\n * Split `text` into text + redacted segments, encrypting each redacted span's\n * original. The result carries NO plaintext PII — only the masked structure and\n * ciphertext — so it is safe to ship to a client; reveal happens server-side via\n * {@link revealSpan}.\n */\nexport async function buildRedactedDocument(\n text: string,\n options: BuildRedactedDocumentOptions,\n): Promise<RedactedDocument> {\n const spans = detectSpans(text, options.patterns)\n const segments: RedactedDocSegment[] = []\n let pos = 0\n for (const span of spans) {\n if (span.start > pos) segments.push({ type: 'text', text: text.slice(pos, span.start) })\n segments.push({ type: 'redacted', id: span.id, kind: span.kind, cipher: await options.encrypt(span.text) })\n pos = span.end\n }\n if (pos < text.length) segments.push({ type: 'text', text: text.slice(pos) })\n return { segments }\n}\n\n/** Define options to decrypt, authorize, and audit the reveal of a span segment */\nexport interface RevealSpanOptions {\n /** Decrypt a span cipher. Wire to `agent-app/crypto` (`decryptWithKey`). */\n decrypt: (cipher: string) => string | Promise<string>\n /** Authorization gate — return false to deny the reveal (fail-closed). */\n canReveal: (segment: { id: string; kind: string }) => boolean | Promise<boolean>\n /** Audit hook — invoked only on a granted reveal (the caller records who/when). */\n onReveal?: (segment: { id: string; kind: string }) => void | Promise<void>\n}\n\n/** Describe the outcome of a reveal operation including success status, value, and failure reason */\nexport interface RevealResult {\n ok: boolean\n value?: string\n /** `not_found` | `forbidden` when `ok` is false. */\n reason?: string\n}\n\n/**\n * Reveal one redacted span's original, gated + audited. Fail-closed: an unknown\n * id or a denied `canReveal` returns `{ ok: false }` and never decrypts; a\n * granted reveal decrypts, fires `onReveal` for the audit trail, and returns the\n * value.\n */\nexport async function revealSpan(\n doc: RedactedDocument,\n spanId: string,\n options: RevealSpanOptions,\n): Promise<RevealResult> {\n const seg = doc.segments.find((s): s is Extract<RedactedDocSegment, { type: 'redacted' }> =>\n s.type === 'redacted' && s.id === spanId,\n )\n if (!seg) return { ok: false, reason: 'not_found' }\n const allowed = await options.canReveal({ id: seg.id, kind: seg.kind })\n if (!allowed) return { ok: false, reason: 'forbidden' }\n const value = await options.decrypt(seg.cipher)\n if (options.onReveal) await options.onReveal({ id: seg.id, kind: seg.kind })\n return { ok: true, value }\n}\n"],"mappings":"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","names":[]}
|